首发于SwiftUI
SwiftUI 里的ForEach这么用你知道吗?

SwiftUI 里的ForEach这么用你知道吗?

背景

最近开发的时候使用ForEach 遇见了一个错误:

ForEach<Range<Int>, Int, ZStack<TupleView<(NavigationLink<EmptyView, AnyView>, 
VStack<TupleView<(ModifiedContent<XXXView, 
AddGestureModifier<_EndedGesture<TapGesture>>>, Optional<XXXView>)>>)>>> 
count (16) != its initial count (3). 
`ForEach(_:content:)` should only be used for *constant* data. 
Instead conform data to `Identifiable` 
or use `ForEach(_:id:content:)` and provide an explicit `id`! 

进入正题,对与熟悉一些SwiftUI的同学来说,ForEach 很常见,也就是当你使用List的时候,通常会用他来循环。

举个例子:

struct AView: View {

	let foo = ["a", "b", "c"]

	var body: some View {
		List {
			ForEach(foo, id:\.self) { text in
				Text(text)
			}
		}
	}
}

效果是这样的:

一切都很美好,像极了爱情啊。。。

可是呢,我需要使用到index,这也没啥,也就式是说我要这样式的写不就可以了。

var body: some View {
		List {
			ForEach(foo.indices) { index in
				Text(foo[index])
			}
		}
	}

nice! 就是这么美好。

我还用到了indices, 而不是0..<foo.count 帅不帅,我就是编程界最靓的仔。


下面我要加个按钮,然后点击的时候,就更改数组。这也难不住我:

struct AView: View {

	@State var foo = ["a", "b", "c"]

	var body: some View {
		ZStack {
			Color.white
			List {
				ForEach(foo.indices) { index in
					Text(foo[index])
				}
			}
			Button("Add") {
				foo.append("foo")
			}
		}

	}
}

然。。。这又像极了爱情,她甩脾气。

开头的错误就出现了。。。

ForEach<Range<Int>, Int, Text> count (4) != its initial count (3). `ForEach(_:content:)` should only be used for *constant* data. Instead conform data to `Identifiable` or use `ForEach(_:id:content:)` and provide an explicit `id`!

有错误咱们就改,看看错误人家都表达的很直接了一点不隐讳:

解决方案: `Identifiable` or use `ForEach(_:id:content:)`

先试试用id

ForEach(foo.indices, id:\.self) { index in
	Text(foo[index])
}


到这里呢,问题就解决了。

下面再看看另外一个 Identifiable,

/// A class of types whose instances hold the value of an entity with stable
/// identity.
///
/// Use the `Identifiable` protocol to provide a stable notion of identity to a 
/// class or value type. For example, you could define a `User` type with an `id`
/// property that is stable across your app and your app's database storage. 
/// You could use the `id` property to identify a particular user even if other 
/// data fields change, such as the user's name. 

也很直接,就是唯一标识ID。

然后呢还有:

/// Identities could be any of the following:
///
/// - Guaranteed always unique (e.g. UUIDs).
/// - Persistently unique per environment (e.g. database record keys).
/// - Unique for the lifetime of a process (e.g. global incrementing integers).
/// - Unique for the lifetime of an object (e.g. object identifiers).
/// - Unique within the current collection (e.g. collection index).
///

这个protocol里面有个ID,可以是数据库的记录键值,全局的整形,UUID等吧,那我们现在就明白了。

既然人家需要咱们就提供,于是乎你就可以用extension来实现:

extension String: Identifiable {
 public var id: String { self }
}

注意:这里呢,如果用forEach(foo) 也就是不用indices 是可以工作的,就不用id:\.self. 对于forEach(foo.indices), 是不可以的。

试过让Int conform Identifiable,不工作,发现foo.indicesRange<Int>最终这样就工作了:

forEach(Array(foo.indices))

爱情回到了最初的美好。


有时候可能因为一种天气,或者是一种空气的味道勾起你美好的回忆,想到某人,这就是生活美好的地方吧。

编辑于 2020-11-11 11:24