SwiftUI Toggle в VStack смещен - PullRequest
       27

SwiftUI Toggle в VStack смещен

0 голосов
/ 16 февраля 2020

У меня есть простой List, где каждая строка представляет собой Toggle с Text и Text в качестве субтитра, все в VStack. Все работает нормально, пока я не начну показывать или скрывать некоторые строки. Каким-то образом переключатель вида Toggle не выровнен и размещен над его заголовком. Это происходит только на устройстве, а не при работе на симуляторе.

enter image description here

Это происходит с бета-версиями XCode 13.3 и 13.4 на устройстве, работающем 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))
    }
}

1 Ответ

1 голос
/ 16 февраля 2020

это должно сработать

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))
    }
}

Я положил это здесь, чтобы показать, что вся условная часть воссоздается, если хотя бы один из ее элементов должен быть воссоздан.

Для объяснения, немного изменить код (без какого-либо модификатора .id)

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 распознал «что-то», было изменено.

Text("\(showDetails.description)")

имеет тот же эффект.

А как насчет модификатора .id? Почему это работает?

@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)

тоже работает!

Позволяет перестроить код таким образом

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 справляется с нашим «условным». Фактически это

TupleView<(some View, ToggleSubtitleRow)>?

ОБНОВЛЕНИЕ, основанное на обсуждении, применение модификатора .id к нескольким ToggleSubtitleRow просто не работает

Лучший вариант, как решить эту ошибку - переопределить

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

...