【问题标题】:SwiftUI - using variable count within ForEachSwiftUI - 在 ForEach 中使用变量计数
【发布时间】:2022-01-02 09:20:53
【问题描述】:

我有一个通过 ForEach 循环创建的视图,它需要在 ForEach 本身内获取变量计数,即我需要应用对动态计数做出反应并相应地更改 UI。

这是我要修改的视图:

struct AnimatedTabSelector: View {
    let buttonDimensions: CGFloat
    @ObservedObject var tabBarViewModel: TabBarViewModel
    
    var body: some View {
        HStack {
            Spacer().frame(maxWidth: .infinity).frame(height: 20)
                .background(Color.red)
            
            ForEach(1..<tabBarViewModel.activeFormIndex + 1) { _ in
                Spacer().frame(maxWidth: buttonDimensions).frame(height: 20)
                    .background(Color.blue)
                Spacer().frame(maxWidth: .infinity).frame(height: 20)
                    .background(Color.green)
            }
            
            Circle().frame(
                width: buttonDimensions,
                height: buttonDimensions)
                .foregroundColor(
                    tabBarViewModel.activeForm.loginFormViewModel.colorScheme
                )
            
            ForEach(1..<tabBarViewModel.loginForms.count - tabBarViewModel.activeFormIndex) { _ in
                Spacer().frame(maxWidth: .infinity).frame(height: 20)
                    .background(Color.red)
                Spacer().frame(maxWidth: buttonDimensions).frame(height: 20)
                    .background(Color.blue)
            }
            
            Spacer().frame(maxWidth: .infinity).frame(height: 20)
                .background(Color.gray)
        }
    }
}

还有我正在观察的 viewModel:

class TabBarViewModel: ObservableObject, TabBarCompatible {
    var loginForms: [LoginForm]
    @Published var activeForm: LoginForm
    @Published var activeFormIndex = 0
    private var cancellables = Set<AnyCancellable>()
    
    init(loginForms: [LoginForm]) {
        self.loginForms = loginForms
        self.activeForm = loginForms[0] /// First form is always active to begin
        setUpPublisher()
    }
    
    func setUpPublisher() {
        for i in 0..<loginForms.count {
            loginForms[i].loginFormViewModel.$isActive.sink { isActive in
                if isActive {
                    self.activeForm = self.loginForms[i]
                    self.activeFormIndex = i
                }
            }
            .store(in: &cancellables)
        }
    }
}

最后是 loginFormViewModel:

class LoginFormViewModel: ObservableObject {
    @Published var isActive: Bool
    
    let name: String
    let icon: Image
    let colorScheme: Color
    
    init(isActive: Bool = false, name: String, icon: Image, colorScheme: Color) {
        self.isActive = isActive
        self.name = name
        self.icon = icon
        self.colorScheme = colorScheme
    }
}

基本上,登录表单上的按钮本身会将其 viewModel 的 isActive 属性设置为 true。我们在 TabBarViewModel 中监听这个并相应地设置 activeFormIndex。然后在 ForEach 循环中使用该索引。本质上,根据选择的索引,我需要在 AnimatedTabSelector 视图中生成或多或少的分隔符。

然而,虽然 activeIndex 变量被正确更新,但 ForEach 似乎没有反应。

更新:

AnimatedTabSelector 被声明为这个整体视图的一部分:

struct TabIconsView: View {
    
    struct Constants {
        static let buttonDimensions: CGFloat = 50
        static let buttonIconSize: CGFloat = 25
        static let activeButtonColot = Color.white
        static let disabledButtonColor = Color.init(white: 0.8)
        
        struct Animation {
            static let stiffness: CGFloat = 330
            static let damping: CGFloat = 22
            static let velocity: CGFloat = 7
        }
    }
    
    @ObservedObject var tabBarViewModel: TabBarViewModel
    
    var body: some View {
        ZStack {
            AnimatedTabSelector(
                buttonDimensions: Constants.buttonDimensions,
                tabBarViewModel: tabBarViewModel)
            
            HStack {
                Spacer()
                
                ForEach(tabBarViewModel.loginForms) { loginForm in
                    Button(action: {
                        loginForm.loginFormViewModel.isActive = true
                    }) {
                        loginForm.loginFormViewModel.icon
                            .font(.system(size: Constants.buttonIconSize))
                            .foregroundColor(
                                tabBarViewModel.activeForm.id == loginForm.id ? Constants.activeButtonColot : Constants.disabledButtonColor
                            )
                    }
                    .frame(width: Constants.buttonDimensions, height: Constants.buttonDimensions)
                    Spacer()
                }
            }
        }
        .animation(Animation.interpolatingSpring(
            stiffness: Constants.Animation.stiffness,
            damping: Constants.Animation.damping,
            initialVelocity: Constants.Animation.velocity)
        )
    }
}

更新:

我尝试了另一种方法,将另一个发布到 AnimatedTabSelector 本身以检查值是否确实在相应地更新。所以在这个视图的 HStack 末尾我添加了:

.onAppear {
            tabBarViewModel.$activeFormIndex.sink { index in
                self.preCircleSpacers = index + 1
                self.postCircleSpacers = tabBarViewModel.loginForms.count - index
            }
            .store(in: &cancellables)
        }

当然,我在这个视图中添加了以下变量:

@State var preCircleSpacers = 1
@State var postCircleSpacers = 6
@State var cancellables = Set<AnyCancellable>()

然后在 ForEach 循环中我改为:

ForEach(1..<preCircleSpacers)

ForEach(1..<postCircleSpacers)

分别。

我在新的发布者声明中添加了一个断点,它确实正在更新为预期的数字。但观点仍然未能反映价值观的变化

【问题讨论】:

  • 您是否在您的 SwiftUI 代码中设置了断点来验证 tabBarViewModel.loginForms.counttabBarViewModel.activeFormIndex 是您认为的值?
  • 是的,我有 - 他们肯定会返回预期值
  • 所以这些值是正确的,但是 ForEach 没有迭代与这些值相关的正确次数,或者 SwiftUI 主体根本不会再次被调用?我不确定您所说的“似乎没有反应”是什么意思。
  • 抱歉 - 是的,所以不会再次调用 SwiftUI 主体。在第一次加载时一切都很好,但是当这些值发生变化时,视图不会改变,即垫片的数量不会随着值的变化而变化
  • 看起来您没有遗漏任何明显的 SwiftUI 注释。假设您已验证传递给 sink 的闭包正在按预期调用,我的猜测是您的 TabBarViewModel 实例比您预期的要多,并且正在观察一个实例,而另一个正在更新。

标签: swift swiftui combine


【解决方案1】:

好的,所以我似乎找到了解决方案 - 我假设包含范围的 ForEach 不会像包含对象数组的 ForEach 那样动态更新。

所以而不是:

ForEach(0..<tabBarViewModel.loginForms.count)

等等

我改成:

ForEach(tabBarViewModel.loginForms.prefix(tabBarViewModel.activeFormIndex))

这样我仍然会迭代所需的次数,但 ForEach 会使用数组中正确数量的项目动态更新

【讨论】:

  • 看起来the documentation 确实说了这么多:“实例只读取所提供数据的初始值,不需要跨更新识别视图。在动态范围内按需计算视图, 使用 ForEach/init(_:id:content:)。”
猜你喜欢
  • 1970-01-01
  • 2020-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-17
  • 1970-01-01
  • 2020-06-23
  • 1970-01-01
相关资源
最近更新 更多