代码之家  ›  专栏  ›  技术社区  ›  joshuakcockrell

SwiftUI-更改父@State不会更新子视图

  •  0
  • joshuakcockrell  · 技术社区  · 4 年前

    具体问题:

    SwiftUI doesn't like us initializing @State using parameters from the parent 但是如果父母拿着它呢 @陈述 导致重大性能问题?

    例子:

    如何使点击顶部文本将滑块更改为满/空?

    正确拖动滑块可以进行通信 向上地 当滑块从满变为空,但点击 [Overview] full: 文本无法沟通 向下 滑块应更改为满/空。

    我可以储存潜在的 Double 在父视图中,但它会导致严重滞后,似乎没有必要。

    import SwiftUI
    
    // Top level View. It doesn't know anything about specific slider percentages,
    // it only knows if the slider got moved to full/empty
    struct SliderOverviewView: View {
    
        // Try setting this to true and rerunning.. It DOES work here?!
        @State var overview = OverviewModel(state: .empty)
    
        var body: some View {
            VStack {
                Text("[Overview] full: \(overview.state.rawValue)")
                    .onTapGesture { // BROKEN: should update child..
                        switch overview.state {
                        case .full, .between: overview.state = .empty
                        case .empty: overview.state = .full
                        }
                    }
                SliderDetailView(overview: $overview)
            }
        }
    }
    
    
    // Bottom level View. It knows about specific slider percentages and only
    // communicates upwards when percentage goes to 0% or 100%.
    struct SliderDetailView: View {
    
        @State var details: DetailModel
    
        init(overview: Binding<OverviewModel>) {
            details = DetailModel(overview: overview)
        }
    
        var body: some View {
            VStack {
                Text("[Detail] percentFull: \(details.percentFull)")
    
                Slider(value: $details.percentFull)
                    .padding(.horizontal, 48)
            }
        }
    }
    
    
    // Top level model that only knows if slider went to 0% or 100%
    struct OverviewModel {
        var state: OverviewState
        
        enum OverviewState: String {
            case empty
            case between
            case full
            
        }
    }
    
    
    // Lower level model that knows full slider percentage
    struct DetailModel {
        @Binding var overview: OverviewModel
        var percentFull: Double {
            didSet {
                if percentFull == 0 {
                    overview.state = .empty
                } else if percentFull == 1 {
                    overview.state = .full
                } else {
                    overview.state = .between
                }
            }
        }
    
        init(overview: Binding<OverviewModel>) {
            _overview = overview
            
            // set inital percent
            switch overview.state.wrappedValue {
            case .empty:
                percentFull = 0.0
            case .between:
                percentFull = 0.5
            case .full:
                percentFull = 1.0
            }
        }
    }
    
    struct SliderOverviewView_Previews: PreviewProvider {
        static var previews: some View {
            SliderOverviewView()
        }
    }
    

    为什么我不直接储存 percentFull OverviewModel ?

    我正在寻找一种模式,这样我的最高水平 @陈述 struct不需要知道 每一个 特定于某些视图的低级别细节。

    运行代码示例是了解我的问题的最清晰的方式。

    这个问题使用了一个人为的例子 Overview 只知道滑块是否正确 full empty ,但是 Detail 知道吗 百分率 滑块实际上是。这个 细节 对滑块有非常详细的控制和了解,并且只向上与 概述 当滑块 0% 100%

    我为什么需要这样做的具体原因是什么?

    对于那些好奇的人来说,我的应用程序遇到了 性能问题 因为我有几个手势可以让用户控制进度。我想要我的最高水平 ViewModel 如果手势是 完成 也可以不知道,但它不需要知道用户刷了多远。我试图掩盖这一具体进展 双重的 从我的上级 视图模型 以提高应用程序性能。

    1 回复  |  直到 4 年前
        1
  •  0
  •   Leo Dabus    4 年前

    以下是针对您的问题的有效、简化和重构的答案:

    struct ContentView: View {
        var body: some View {
            SliderOverviewView()
        }
    }
    

    struct SliderOverviewView: View {
        @State private var overview: OverviewModel = OverviewModel(full: false) 
        var body: some View {
            VStack { 
                Text("[Overview] full: \(overview.full.description)")
                    .onTapGesture {
                        overview.full.toggle()
                    }
                SliderDetailView(overview: $overview)
            }
        }
    }
    

    struct SliderDetailView: View {
        @Binding var overview: OverviewModel
        var body: some View {
            VStack {  
                Text("[Detail] percentFull: \(tellValue(value: overview.full))") 
                Slider(value: Binding(get: { () -> Double in   
                    return tellValue(value: overview.full) 
                }, set: { newValue in  
                    if newValue == 1 { overview.full = true }
                    else if newValue == 0 { overview.full = false } 
                }))
            }
        }
        
        func tellValue(value: Bool) -> Double {
            if value { return 1 }
            else { return 0 }
        }
    }
    

    struct OverviewModel {
        var full: Bool
    }
    

    更新:

    struct SliderDetailView: View {
        @Binding var overview: OverviewModel
        @State private var sliderValue: Double = Double()
        var body: some View {
            VStack {   
                Text("[Detail] percentFull: \(sliderValue)")
                Slider(value: $sliderValue, in: 0.0...1.0)
            }
            .onAppear(perform: { sliderValue = tellValue(value: overview.full) })
            .onChange(of: overview.full, perform: { newValue in
                sliderValue = tellValue(value: newValue)
            })
            .onChange(of: sliderValue, perform: { newValue in
                if newValue == 1 { overview.full = true }
                else { overview.full = false }
            })
        }
        func tellValue(value: Bool) -> Double {
            value ? 1 : 0
        }
    }
    
        2
  •  0
  •   workingdog support Ukraine    4 年前

    在这里,我使用2 ObservableObject ,高水平 OverviewModel 那个 只有当滑块变为0%或100%,并且 DetailModel 这只涉及滑块百分比。

    当滑块从满变为空时,向上拖动滑块可以正确通信,并且 点击[Overview]全文:文本向下传达滑块应更改为full/empty。

    import Foundation
    import SwiftUI
    
    @main
    struct TestApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }     
    
    struct ContentView: View {
        @StateObject var overview = OverviewModel()
        
        var body: some View {
            SliderOverviewView().environmentObject(overview)
        }
    }
    
    // Top level View. It doesn't know anything about specific slider percentages,
    // it only cares if the slider got moved to full/empty
    struct SliderOverviewView: View {
        @EnvironmentObject var overview: OverviewModel
        
        var body: some View {
            VStack {
                Text("[Overview] full: \(overview.state.rawValue)")
                    .onTapGesture {
                        switch overview.state {
                        case .full, .between: overview.state = .empty
                        case .empty: overview.state = .full
                        }
                    }
                SliderDetailView()
            }
        }
    }
    
    // Bottom level View. It knows about specific slider percentages and only
    // communicates upwards when percentage goes to 0% or 100%.
    struct SliderDetailView: View {
        @EnvironmentObject var overview: OverviewModel
        @StateObject var details = DetailModel()
        
        var body: some View {
            VStack {
                Text("[Detail] percentFull: \(details.percentFull)")
                Slider(value: $details.percentFull).padding(.horizontal, 48)
                    .onChange(of: details.percentFull) { newVal in
                        switch newVal {
                        case 0: overview.state = .empty
                        case 1: overview.state = .full
                        default: break
                        }
                    }
            }
            // listen for the high level OverviewModel changes
            .onReceive(overview.$state) { theState in
                details.percentFull = theState == .full ? 1.0 : 0.0
            }
        }
    }
    
    enum OverviewState: String {
        case empty
        case between
        case full
    }
    
    // Top level model that only knows if slider went to 0% or 100%
    class OverviewModel: ObservableObject {
        @Published var state: OverviewState = .empty
    }
    
    // Lower level model that knows full slider percentage
    class DetailModel: ObservableObject {
        @Published var percentFull = 0.0
    }