首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >具有双向绑定的自定义SwiftUI视图

具有双向绑定的自定义SwiftUI视图
EN

Stack Overflow用户
提问于 2020-02-18 16:14:41
回答 2查看 4.1K关注 0票数 2

我很难实现一个自定义视图,它可以将绑定作为参数并实现该值的双向更新。

因此,基本上,我正在实现我的自定义滑块,并希望它的初始化器是这样的:

MySlider(value: <Binding<Float>)

我在挣扎的是:

  1. 如何订阅绑定值的远程更新,以便更新视图的状态?
  2. 有什么好的方法可以将绑定绑定到@State property吗?

以下是我目前的实现,这并不完美。

代码语言:javascript
复制
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,而不属于任何能够观察其变化的SwiftUI视图。--模型对象属于UIKit ViewController,而我只传递一个绑定到SwiftUI视图,这是不够的。我现在的解决方案是将模型对象也传递给SwiftUI视图,以便它可以将其标记为@ObservedObject

代码语言:javascript
复制
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
                    }
                }
            }
        }
    }
}
EN

回答 2

Stack Overflow用户

发布于 2020-02-18 18:56:22

@Binding的定义本质上是与底层数据项的双向连接,这是一个属于另一个视图的@State变量。如苹果所述

使用绑定在视图和其底层模型之间创建双向连接。

如果是绑定,如果值发生变化,SwiftUI将自动更新视图;因此(回答第一个问题),您不需要执行任何类型的订阅来更新自定义视图--这将自动发生。

类似地(关于第二个问题),因为绑定是来自另一个视图的状态,所以您不应该将它声明为视图的状态,我也不认为它是可能的。对于您的视图来说,状态应该是一个纯粹的内部值(Apple强烈建议所有@State属性都声明为private)。

这一切都与苹果在发布SwiftUI时强调的“真理的单一来源”概念有关:绑定已经是@State的父视图是拥有这些信息的东西,所以你的视图也不应该声明为一个状态。

对于您的代码,我认为您需要做的就是取出第二个状态属性,因为它不是必需的。只需确保传递的绑定是父视图中任何自定义视图中的@State属性,并使用$语法传递它以创建该绑定。如果需要的话,这篇文章会更详细地介绍这个想法。

代码语言:javascript
复制
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
EN

Stack Overflow用户

发布于 2020-08-24 09:53:14

我的回答可能有点晚,但我偶然发现了一个类似的问题。我就是这样解决这个问题的。

代码语言:javascript
复制
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

代码语言:javascript
复制
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()
        }
    }
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60285177

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档