VStack 中的 SwiftUI 切换未对齐 [英] SwiftUI Toggle in a VStack is misaligned

查看:27
本文介绍了VStack 中的 SwiftUI 切换未对齐的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的 List,其中每一行都是一个 Toggle,其 TextText 作为副标题全部在 VStack 中.一切正常,直到我开始显示或隐藏一些行.不知何故,Toggle 视图的开关未对齐并放置在其标题上.这仅在设备上发生,在模拟器上运行时不会发生.

I have a simple List where each row is a Toggle with its Text and Text as a subtitle all in a VStack. All works fine until I start showing or hiding some rows. Somehow the switch of the Toggle view is misaligned and is placed over its title. This happens only on the device and not when running on the simulator.

在运行 iOS 13.3.1 的设备上,XCode 13.3 和 13.4 beta 都会发生这种情况

It happens with both XCode 13.3 and 13.4 beta on a device running iOS 13.3.1

完整的例子是

import SwiftUI

struct ContentView: View {

    @State var showDetails = false
    @State var firstToggle = false
    @State var secondToggle = false
    var body: some View {
        NavigationView {
            Form {
                ToggleSubtitleRow(title: "Show Advanced",
                                     text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, se",
                                     isOn: $showDetails)

                if showDetails {
                    ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                         text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                                         isOn: $firstToggle)

                    ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                         text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                                         isOn: $secondToggle)
                }

            }
            .listStyle(GroupedListStyle())
            .navigationBarTitle("Settings", displayMode: .inline)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

public struct ToggleSubtitleRow: View {
    let title: String
    let text: String
    @Binding var isOn: Bool

    public init(title: String, text: String,
                isOn: Binding<Bool>) {
        self.text = text
        self.title = title
        self._isOn = isOn

    }

    public var body: some View {
        VStack(alignment: .leading) {
            Toggle(isOn: $isOn) {
                Text(title)
            }
            Text(text)
                .fixedSize(horizontal: false, vertical: true)
                .foregroundColor(Color(.secondaryLabel))
                .frame(alignment: .leading)
        }
        .foregroundColor(Color(.label))
    }
}

推荐答案

这应该可行

import SwiftUI

struct ContentView: View {

    @State var showDetails = false
    @State var firstToggle = false
    @State var secondToggle = false
    var body: some View {
        NavigationView {
            Form {
                ToggleSubtitleRow(title: "Show Advanced",
                                     text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, se",
                                     isOn: $showDetails)

                if showDetails {
                    ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                         text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                        isOn: $firstToggle).id(UUID())

                    ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                         text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                                         isOn: $secondToggle)
                }

            }
            .listStyle(GroupedListStyle())
            .navigationBarTitle("Settings", displayMode: .inline)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

public struct ToggleSubtitleRow: View {
    let title: String
    let text: String
    @Binding var isOn: Bool

    public var body: some View {
        VStack(alignment: .leading) {
            Toggle(isOn: $isOn) {
                Text(title)
            }
            Text(text)
                .fixedSize(horizontal: false, vertical: true)
                .foregroundColor(Color(.secondaryLabel))
                .frame(alignment: .leading)
        }
        .foregroundColor(Color(.label))
    }
}

我把它放在这里是为了表明,如果至少有一个元素需要重新创建,则整个条件部分都会重新创建.

I put it here to show, that the whole conditional part is recreated if at least one of its element need to be recreated.

为了说明,稍微修改一下代码(不带任何 .id 修饰符)

For explanation, change the code a little bit (without any .id modifier)

if showDetails {
    Text("\(showDetails.description)")
    ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                  text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                                  isOn: $firstToggle)

     ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                  text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                                  isOn: $secondToggle)
 }

它按预期"工作,因为在条件部分 SwiftUI 识别出某些东西"已更改.

It works, "as expected", because in the conditional part SwiftUI recognized "something" was changed.

Text("\(showDetails.description)")

具有相同的效果.

.id 修饰符怎么样?为什么有效?

What about .id modifier? Why it works?

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension View {

    /// Returns a view whose identity is explicitly bound to the proxy
    /// value `id`. When `id` changes the identity of the view (for
    /// example, its state) is reset.
    @inlinable public func id<ID>(_ id: ID) -> some View where ID : Hashable

}

基于书面

ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                  text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                    isOn: $firstToggle).id(showDetails)

同样有效!

让我们以这种方式重新排列代码

Lets rearrange the code this way

struct ContentView: View {

    @State var showDetails = false
    @State var firstToggle = false
    @State var secondToggle = false
    var body: some View {
        let g = Group {
            if showDetails {
                ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                  text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                                  isOn: $firstToggle).id(UUID())

                ToggleSubtitleRow(title: "Lorem ipsum dolor sit amet",
                                  text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
                                  isOn: $secondToggle)
            }
        }
        let f = Form {
            ToggleSubtitleRow(title: "Show Advanced",
                                 text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, se",
                                 isOn: $showDetails)
            g

        }
        .listStyle(GroupedListStyle())
        .navigationBarTitle("Settings", displayMode: .inline)
        let v = NavigationView {
            f
        }
        return v
    }
}

并检查 g 的类型

let g: Group<TupleView<(some View, ToggleSubtitleRow)>?>

我们可以看到 SwiftUI 如何处理我们的条件".其实是

we can see how SwiftUI deal with our "conditional". It is in fact

TupleView<(some View, ToggleSubtitleRow)>?

基于讨论的更新,在多个 ToggleSubtitleRow 上应用 .id 修饰符根本不起作用

最好的选择,如何解决这个bug就是重新定义

The best option, how to solve this bug is redefine

public struct ToggleSubtitleRow: View {
    let title: String
    let text: String
    @Binding var isOn: Bool

    public var body: some View {
        VStack(alignment: .leading) {
            Toggle(isOn: $isOn) {
                Text(title)
            }.id(UUID())
            Text(text)
                .fixedSize(horizontal: false, vertical: true)
                .foregroundColor(Color(.secondaryLabel))
                .frame(alignment: .leading)
        }
        .foregroundColor(Color(.label))
    }
}

不修改 ContentView 中的任何内容,而是直接在 ToggleSubtitleRow 中切换自我

by not modifying anything in you ContentView but directly Toggle self in ToggleSubtitleRow

这篇关于VStack 中的 SwiftUI 切换未对齐的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆