young
(rtSwift)
1
It used like this:
ForEach($model.users) { $user in
VStack {
HStack {
// user is: var user { get set }
// where is it (no $ in front) from?
Text(user.name)
Spacer()
Toggle("User has been contacted", isOn: $user.isContacted)
.labelsHidden()
}
Text("Contacted: \(user.isContacted ? "Yes" : "No")")
}
}
The content closure parameter is declared as $user with this $ prefix. And user (without $) can be used to access the element value. Where is this user come to from?
The content closure parameter type is a Binding<[User]>.Element. Just by the look of the code, I thought $user is just conventional naming with this $ prefix to signify this is a binding. But the plain user variable is used to access the element as a value, seems some kind of code synthesis happen behind the scenes some how?
What's going on? I can use shorthand parameter $0 or omit the $ in the closure parameter name, but then there is no way to refer to the element as value.
doc
My Test
import SwiftUI
struct User: Identifiable {
let id = UUID()
var name: String
var isContacted = false
}
final class Model: ObservableObject {
@Published var users: [User]
init(_ users: [User]) {
self.users = users
}
}
struct ContentView: View {
@EnvironmentObject private var model: Model
var body: some View {
List {
ForEach($model.users) { $user in
VStack {
HStack {
// user is: var user { get set }
// where is it (no $ in front) from?
Text(user.name)
Spacer()
Toggle("User has been contacted", isOn: $user.isContacted)
.labelsHidden()
}
Text("Contacted: \(user.isContacted ? "Yes" : "No")")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let data = [
User(name: "Larry"),
User(name: "Curly"),
User(name: "Moe")
]
ContentView()
.environmentObject(Model(data))
}
}
Lantua
2
It is a recently-extended wrapper: SE-0293. For the most part, if you do { $user in }, you be able to access $user, and user, which is just $user.wrappedValue.
1 Like
young
(rtSwift)
3
Thank you for solving the mystry for me! So this property wrapper magic is not visible by looking at "Jump to Definition"? Can't tell the content closure parameter is a property wrapper.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ForEach where ID == Data.Element.ID, Content : View, Data.Element : Identifiable {
/// Creates an instance that uniquely identifies and creates views across
/// updates based on the identity of the underlying data.
///
/// It's important that the `id` of a data element doesn't change unless you
/// replace the data element with a new data element that has a new
/// identity. If the `id` of a data element changes, the content view
/// generated from that data element loses any current state and animations.
///
/// - Parameters:
/// - data: The identified data that the ``ForEach`` instance uses to
/// create views dynamically.
/// - content: The view builder that creates views dynamically.
public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content)
}
Lantua
4
It is indeed not necessary that content's first argument (aka, Data.Element) is a property wrapper. It is together from the fact that $model.users (Data) is a collection of property wrappers.
young
(rtSwift)
5
From SE-0293 - Closure:
For closures that take in a projected value, the property-wrapper attribute is not necessary if the backing property wrapper and the projected value have the same type, such as the @ Binding
I think I understand now: Binding<Value>'s projectedValue is also Binding<Value>. So since ForEach.init content closure parameter is a Binding<Value>, its projectedValue is also Binding<Value>, which makes $user means take the parameter's projectedValue to initialize the closure parameter with .init(projectedValue:).