我正在努力实现一个自定义视图,该视图可以将Binding作为参数并对该值进行双向更新。
所以基本上我正在实现我的自定义滑块,并希望其初始化程序像这样:
MySlider(value: <Binding<Float>)
我正在努力的事情:
到目前为止,这是我目前的实现方式,
struct MySlider: View {
@Binding var selection: Float?
@State private var selectedValue: Float?
init(selection: Binding<Float?>) {
self._selection = selection
// https://stackoverflow.com/a/58137096
_selectedValue = State(wrappedValue: selection.wrappedValue)
}
var body: some View {
HStack(spacing: 3) {
ForEach(someValues) { (v) in
Item(value: v,
isSelected: v == self.selection)
.onTapGesture {
// No idea how to do that other way so I don't have to set it twice
self.selection = v
self.selectedValue = v
}
}
}
}
}
修改1:
我想我的问题是底层模型对象来自Core Data,并且不属于任何观察其变化的SwiftUI视图。该模型对象归UIKit ViewController所有,我只传递了绑定到SwiftUI视图还不够。
我现在的解决方案是将模型对象也传递给SwiftUI视图,以便可以将其标记为@ObservedObject
。
struct MySlider<T>: View where T: ObservableObject {
@ObservedObject var object: T
@Binding var selection: Float?
var body: some View {
return ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 3) {
ForEach(values) { (v) in
Item(value: v,
isSelected: v == self.selection)
.onTapGesture {
self.selection = v
}
}
}
}
}
}
答案 0 :(得分:0)
@Binding
的定义本质上是与基础数据项的双向连接,例如另一个视图所拥有的@State
变量。如Apple所述:
使用绑定在视图及其基础模型之间创建双向连接。
如果是绑定,则SwiftUI将在值更改时自动更新其视图;因此(回答第一个问题),您无需进行任何订阅即可更新您的自定义视图-这将自动发生。
类似地(关于您的第二个问题),因为绑定是另一个视图的状态,所以您也不应将其声明为视图的状态,我也不会相信这是可能的。状态应该是视图的纯粹内部值(Apple强烈建议将所有@State
属性都声明为private
)。
这一切都与苹果在发布SwiftUI时强调的“单一真相”概念有关:该绑定已经@State
的父视图才是拥有信息的主体,因此您的视图也不应该声明为状态。
对于您的代码,我想您需要做的就是删除第二个状态属性,因为这不是必需的。只要确保您传递的绑定在任何拥有您的自定义视图的父视图中都是@State
属性,然后使用$
语法将其传递来创建该绑定即可。 This article会在需要时更详细地介绍这个想法。
struct MySlider: View {
@Binding var selection: Float?
init(selection: Binding<Float?>) {
self._selection = selection
}
var body: some View {
HStack(spacing: 3) {
ForEach(someValues) { (v) in
Item(value: v, isSelected: v == self.selection)
.onTapGesture {
self.selection = v
}
}
}
}
}
答案 1 :(得分:0)
我的回答可能有点晚了,但是我偶然发现了一个类似的问题。所以这就是我的解决方法。
struct Item : Identifiable {
var id : Int
var isSelected : Bool
}
class MySliderObserver : ObservableObject {
@Published var values = [Item]()
init() {
values.append(Item(id: 0, isSelected: false))
values.append(Item(id: 1, isSelected: false))
values.append(Item(id: 2, isSelected: false))
}
func toggleSelectForItem(id:Int) -> Void {
guard let index = values.firstIndex(where: { $0.id == id }) else {
return
}
var item = values[index]
item.isSelected.toggle()
values[index] = item
}
}
struct MySlider: View {
@EnvironmentObject var observer : MySliderObserver
var body: some View {
return ScrollView(.horizontal, showsIndicators: false) {
VStack(spacing: 3) {
ForEach(self.observer.values) { v in
Text("Selected: \(v.isSelected ? "true" : "false")").onTapGesture {
self.observer.toggleSelectForItem(id: v.id)
}
}
}
}
}
}
,在SceneDelegate
中,您需要设置EnvironmentObject
,例如:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = MySlider()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(MySliderObserver()))
self.window = window
window.makeKeyAndVisible()
}
}