首页 > 解决方案 > SwiftUI 自定义对齐方式推动视图比父视图更宽

问题描述

我从这个开始。一些带有两个滑块的 UI。

在此处输入图像描述

由此代码定义:

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

它部分有效。标签的后沿对齐,但现在 UI 已部分移出屏幕右侧。

在此处输入图像描述

使用 UIKit 和自动布局,我会将这两个标签的后沿限制为相等。这不会将任何东西推离屏幕(假设事情像往常一样被限制在屏幕边缘)。这里出了什么问题?您如何通过对齐或更好的方式使用 SwiftUI 来实现这一点?

标签: alignmentswiftui

解决方案


一种方法是使用首选项(在此处了解更多信息)。

在此处输入图像描述

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

    private let spacing: CGFloat = 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: .trailing) {
                HStack(spacing: self.spacing) {
                    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: self.spacing) {
                    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 + self.spacing)

                        }
                    }

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

推荐阅读