Настраиваемое выравнивание SwiftUI расширяет возможности просмотра по сравнению с родительским - PullRequest
1 голос
/ 01 февраля 2020

Я начинаю с этого. Некоторый пользовательский интерфейс с двумя ползунками.

enter image description here

Определено этим кодом:

struct ContentView: View {
    @State private var num1: Double = 0.5
    @State private var num2: Double = 0.5
    let blueGreen = Color(red: 0.2, green: 0.6, blue: 0.6)
    var body: some View {
        VStack {
            Circle().fill(blueGreen).border(Color.blue, width: 1.0).padding(4.0)
            VStack {
                HStack {
                    Text("Value:")
                    Slider(value: $num1, in: 0...1)
                }
                HStack {
                    Text("Opacity:")
                    Slider(value: $num2, in: 0...1)
                }
            }
            Spacer()
        }.border(Color.green, width: 1.0).padding()
    }
}

Я хочу "Значение:" и " Непрозрачность: «метки должны быть выровнены по их задней кромке, оставляя ползунки выровненными и одинаковой ширины. Я слышал, что пользовательские выравнивания могут выравнивать представления, подобные этому, представления, которые не являются братьями и сестрами.

Поэтому я добавляю пользовательское выравнивание:

extension HorizontalAlignment {
    private enum MyAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.trailing]
        }
    }
    static let myAlignment = HorizontalAlignment(MyAlignment.self)
}

struct ContentView: View {
    @State private var num1: Double = 0.5
    @State private var num2: Double = 0.5
    let blueGreen = Color(red: 0.2, green: 0.6, blue: 0.6)
    var body: some View {
        VStack {
            Circle().fill(blueGreen).border(Color.blue, width: 1.0).padding(4.0)
            VStack(alignment: .myAlignment) {
                HStack {
                    Text("Value:").alignmentGuide(.myAlignment) { d in d[.trailing] }
                    Slider(value: $num1, in: 0...1)
                }
                HStack {
                    Text("Opacity:").alignmentGuide(.myAlignment) { d in d[.trailing] }
                    Slider(value: $num2, in: 0...1)
                }
            }
            Spacer()
        }.border(Color.green, width: 1.0).padding()
    }
}

Это частично работает. Задние края надписей выровнены, но теперь пользовательский интерфейс частично переместился за пределы экрана вправо.

enter image description here

При использовании UIKit и автоматической разметки я будет ограничивать задние края этих двух меток равными. Это ничего бы не вытянуло за пределы экрана (при условии, что все ограничено краями экрана, как обычно). Что здесь не так? И как вы можете добиться этого с помощью выравниваний или, как-то лучше, с помощью SwiftUI?

Ответы [ 2 ]

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

Один из способов сделать это - использовать Настройки (подробнее здесь ).

enter image description here

struct ContentView: View {
    @State private var num1: Double = 0.5
    @State private var num2: Double = 0.5
    @State private var sliderWidth: CGFloat = 0

    let blueGreen = Color(red: 0.2, green: 0.6, blue: 0.6)

    var body: some View {
        VStack {
            Circle().fill(blueGreen).border(Color.blue, width: 1.0).padding(4.0)

            VStack(alignment: .trailing) {
                HStack(spacing: 4) {
                    Text("Value:")
                        .fixedSize()
                        .anchorPreference(key: MyPrefKey.self, value: .bounds, transform: { [$0] })

                    Slider(value: $num1, in: 0...1)
                        .frame(width: sliderWidth)
                }
                .frame(maxWidth: .infinity, alignment: .trailing)


                HStack(spacing: 4) {
                    Text("Opacity:")
                        .fixedSize()
                        .anchorPreference(key: MyPrefKey.self, value: .bounds, transform: { [$0] })

                    Slider(value: $num2, in: 0...1)
                        .frame(width: sliderWidth)
                }
                .frame(maxWidth: .infinity, alignment: .trailing)

            }
            .backgroundPreferenceValue(MyPrefKey.self) { prefs -> GeometryReader<AnyView> in

               GeometryReader { proxy -> AnyView in
                    let vStackWidth = proxy.size.width

                    let maxAnchor = prefs.max {
                        return proxy[$0].size.width < proxy[$1].size.width

                    }

                    DispatchQueue.main.async {
                        if let a = maxAnchor {
                            self.sliderWidth = vStackWidth - (proxy[a].size.width + 4)

                        }
                    }

                    return AnyView(EmptyView())

                }
            }

            Spacer()

        }.border(Color.green, width: 1.0).padding(20)
    }

}

struct MyPrefKey: PreferenceKey {
    typealias Value = [Anchor<CGRect>]

    static var defaultValue: [Anchor<CGRect>] = []

    static func reduce(value: inout [Anchor<CGRect>], nextValue: () -> [Anchor<CGRect>]) {
        value.append(contentsOf: nextValue())
    }
}
0 голосов
/ 01 февраля 2020

Что ж, я согласен, что Apple, кажется, забыл дать нам что-то в SwiftUI с / от механизма автоматической разметки, например, приоритет захвата контента, сопротивление сжатию, равные размеры и т. Д. c ...

В выравнивании SwiftUI Направляющие не изменяют размеры видов, они просто смещают виды "как есть", выравнивая их по указанным опорным точкам (расширяя контейнер, если он не ограничен в размерах, потому что по умолчанию он плотно прилегает к контенту, то есть то, что вы видите). Таким образом, для решения рассматриваемой ситуации необходимо каким-то образом ограничить (или изменить) размеры.

Приведенный ниже код не является полным решением (он не учитывает возможные l10n), но показывает направление - поскольку Slider не делает иметь размер по умолчанию, он должен быть немного ограничен, как не расширять контейнер.

В любом случае я бы посчитал это обходным решением ... (вычисления ширины могут быть более точными и сложными, как, например, get frame of Text, например через настройки привязки, а затем вычесть из доступной ширины прокси-сервера геометрии с учетом заполнения, но это все то же самое - указать ширину рамки просмотра явно).

enter image description here

struct HueSaturationView : View {
    @State private var num1: Double = 0.5
    @State private var num2: Double = 0.5
    let blueGreen = Color(red: 0.2, green: 0.6, blue: 0.6)
    var body: some View {
        GeometryReader { gp in
            VStack {
                Circle().fill(self.blueGreen).border(Color.blue, width: 1.0).padding(4.0)
                VStack(alignment: .myAlignment) {
                    HStack {
                        Text("Value:").alignmentGuide(.myAlignment) { d in d[.trailing] }
                        Slider(value: self.$num1, in: 0...1)
                            .frame(width: gp.size.width * 2/3)
                    }
                    HStack {
                        Text("Opacity:").alignmentGuide(.myAlignment) { d in d[.trailing] }
                        Slider(value: self.$num2, in: 0...1)
                            .frame(width: gp.size.width * 2/3)
                    }
                }
                Spacer()
            }
            .border(Color.green, width: 1.0).padding()
        }
    }
}
...