Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/apache-spark/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
UIViewRepresentable无法在模式显示的SwiftUI视图中工作_Swiftui_Combine_Observableobject - Fatal编程技术网

UIViewRepresentable无法在模式显示的SwiftUI视图中工作

UIViewRepresentable无法在模式显示的SwiftUI视图中工作,swiftui,combine,observableobject,Swiftui,Combine,Observableobject,为了定制UISlider,我在UIViewRepresentable中使用它。它公开了一个@Binding var值:Double,以便我的视图模型(observeobject)视图可以观察到更改并相应地更新视图 问题是当@Binding值更改时,视图不会更新。在下面的示例中,我有两个滑块。一个本机滑块和一个自定义SwiftUISlider 两者都将绑定值传递给应更新视图的视图模型。本机滑块会更新视图,但不会更新自定义视图。在日志中,我可以看到正确调用了$sliderValue.sink{…,但

为了定制
UISlider
,我在
UIViewRepresentable
中使用它。它公开了一个
@Binding var值:Double
,以便我的视图模型(
observeobject
)视图可以观察到更改并相应地更新
视图

问题是当
@Binding
值更改时,视图不会更新。在下面的示例中,我有两个滑块。一个本机
滑块
和一个自定义
SwiftUISlider

两者都将绑定值传递给应更新视图的视图模型。本机
滑块
会更新视图,但不会更新自定义视图。在日志中,我可以看到正确调用了
$sliderValue.sink{…
,但视图没有更新

我注意到,当presenting视图具有
@Environment(\.presentationMode)var presentationMode:Binding
属性时,就会发生这种情况。如果我对其进行注释,它会按预期工作

一个完整的示例代码来重现这一点

import SwiftUI
import Combine

struct ContentView: View {
    @State var isPresentingModal = false

    // comment this out
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {
        VStack {
            Button("Show modal") {
                isPresentingModal = true
            }
            .padding()
        }
        .sheet(isPresented: $isPresentingModal) {
            MyModalView(viewModel: TempViewModel())
        }
    }
}

class TempViewModel: ObservableObject {
    @Published var sliderText = ""
    @Published var sliderValue: Double = 0
    private var cancellable = Set<AnyCancellable>()

        init() {
            $sliderValue
                .print("view model")
                .sink { [weak self] value in
                    guard let self = self else { return }
                    print("updating view  \(value)")
                    self.sliderText = "\(value) C = \(String(format: "%.2f" ,value * 9 / 5 + 32)) F"
                }
                .store(in: &cancellable)
        }
}

struct MyModalView: View {
    @ObservedObject var viewModel: TempViewModel

    var body: some View {
        VStack(alignment: .leading) {
            Text("SwiftUI Slider")
            Slider(value: $viewModel.sliderValue, in: -100...100, step: 0.5)
                .padding(.bottom)

            Text("UIViewRepresentable Slider")
            SwiftUISlider(minValue: -100, maxValue: 100, value: $viewModel.sliderValue)
            Text(viewModel.sliderText)
        }
        .padding()
    }
}

struct SwiftUISlider: UIViewRepresentable {
    final class Coordinator: NSObject {
        var value: Binding<Double>
        init(value: Binding<Double>) {
            self.value = value
        }

        @objc func valueChanged(_ sender: UISlider) {
            let index = Int(sender.value + 0.5)
            sender.value = Float(index)
            print("value changed \(sender.value)")
            self.value.wrappedValue = Double(sender.value)
        }
    }

    var minValue: Int = 0
    var maxValue: Int = 0

    @Binding var value: Double

    func makeUIView(context: Context) -> UISlider {
        let slider = UISlider(frame: .zero)
        slider.minimumTrackTintColor = .systemRed
        slider.maximumTrackTintColor = .systemRed
        slider.maximumValue = Float(maxValue)
        slider.minimumValue = Float(minValue)

        slider.addTarget(
            context.coordinator,
            action: #selector(Coordinator.valueChanged(_:)),
            for: .valueChanged
        )

        adapt(slider, context: context)
        return slider
    }

    func updateUIView(_ uiView: UISlider, context: Context) {
        adapt(uiView, context: context)
    }

    func makeCoordinator() -> SwiftUISlider.Coordinator {
        Coordinator(value: $value)
    }

    private func adapt(_ slider: UISlider, context: Context) {
        slider.value = Float(value)
    }
}

struct PresentationMode_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
导入快捷界面
进口联合收割机
结构ContentView:View{
@状态变量isPresentingModal=false
//评论一下
@环境(\.presentationMode)变量presentationMode:绑定
var body:一些观点{
VStack{
按钮(“显示模式”){
isPresentingModal=true
}
.padding()
}
.sheet(isPresented:$isPresentingModal){
MyModalView(视图模型:TempViewModel())
}
}
}
类TempViewModel:ObserveObject{
@已发布的var sliderText=“”
@已发布的变量sliderValue:Double=0
private var cancelable=Set()
init(){
$sliderValue
.print(“视图模型”)
.sink{[弱自我]值在
guard let self=self-else{return}
打印(“更新视图\(值)”)
self.sliderText=“\(值)C=\(字符串(格式:“%.2f”,值*9/5+32))F”
}
.store(位于:&可取消)
}
}
结构MyModalView:视图{
@观察对象变量视图模型:TempViewModel
var body:一些观点{
VStack(对齐:。前导){
文本(“快速用户界面滑块”)
滑块(值:$viewModel.sliderValue,in:-100…100,步长:0.5)
.padding(.bottom)
文本(“UIViewRepresentable滑块”)
SwiftUISlider(最小值:-100,最大值:100,值:$viewModel.sliderValue)
文本(viewModel.sliderText)
}
.padding()
}
}
结构SwiftUISlider:UIViewRepresentable{
最终类协调器:NSObject{
var值:绑定
init(值:绑定){
自我价值=价值
}
@objc func值已更改(\发送方:UISlider){
让索引=Int(sender.value+0.5)
sender.value=Float(索引)
打印(“值已更改\(sender.value)”)
self.value.wrappedValue=Double(sender.value)
}
}
var minValue:Int=0
var maxValue:Int=0
@绑定变量值:双精度
func makeUIView(上下文:context)->UISlider{
let slider=UISlider(帧:.0)
slider.minimumTrackTintColor=.systemRed
slider.maximumTrackTintColor=.systemRed
slider.maximumValue=浮动(maxValue)
slider.minimumValue=浮动(minValue)
slider.addTarget(
背景协调员,
操作:#选择器(Coordinator.valueChanged(#:),
对于:。值已更改
)
自适应(滑块,上下文:上下文)
返回滑块
}
func updateUIView(uiView:UISlider,context:context){
自适应(uiView,上下文:上下文)
}
func makeCoordinator()->SwiftUISlider.Coordinator{
协调员(价值:$value)
}
private func adapt(uslider:UISlider,context:context){
slider.value=浮动(值)
}
}
结构表示模式预览:PreviewProvider{
静态var预览:一些视图{
ContentView()
}
}

我发现了问题。在
UIViewRepresentable
updateUIView
中,我还需要将绑定传递给
SwiftUISlider
的新实例:

func updateUIView(_ uiView: UISlider, context: Context) {
    uiView.value = Float(value)

    // the _value is the Binding<Double> of the new View struct, we pass it to the coordinator 
    context.coordinator.value = _value
}
func updateUIView(uiView:UISlider,context:context){
uiView.value=浮动(值)
//_值是新视图结构的绑定,我们将其传递给协调器
context.coordinator.value=\u值
}

SwiftUI.View
可以在任何时候重新创建,当它发生时调用
updateiview
。一个新的视图结构有一个新的
var值:Binding
,因此我们将它分配给我们的协调器这里发生的是包含
@环境(\.presentationMode)
会使
ContentView
的主体在显示模型后立即重新计算。(我不知道发生这种情况的确切原因;可能是因为显示
工作表
时显示模式发生了变化)

但当这种情况发生时,它会启动
MyModalView
两次,并使用
TempViewModel
的两个独立实例

在第一个
MyModalView
上,创建了带有
SwiftUISlider
的视图层次结构。这是创建
协调器的地方,并存储绑定(绑定到
TempViewModel
的第一个实例)

在第二个
MyModelView
上,视图层次结构是相同的,因此它不调用
makeUIView
(仅在视图第一次出现时调用),只调用
updateUIView
。正如您正确指出的,将绑定更新到现在,
TempViewModel
的第二个实例解决了这个问题

因此,一个解决方案就是您在另一个答案中所做的——基本上将绑定重新分配给新对象的属性(顺便说一句,该属性也会释放旧对象)
// option 1
struct ContentView: View {
    @State var isPresentingModal = false
    @StateObject var tempViewModel = TempViewModel()

    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        // ...
        
        .sheet(isPresented: $isPresentingModal) {
            MyModalView(viewModel: tempViewModel)
        }
    }
}
// option 2
struct ContentView: View {
    @State var isPresentingModal = false
    @StateObject var tempViewModel = TempViewModel()

    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        // ...
    
        .sheet(isPresented: $isPresentingModal) {
            MyModalView()
        }
    }
}

struct MyModalView: View {
   @StateObject var viewModel = TempViewModel()

   // ...
}