Элементы SwiftUI HStack одинаковой высоты

Я хочу, чтобы обе кнопки имели одинаковую высоту, аналогичную ограничению Equal Height в UIKit.

  • Не хочу указывать фрейм, пусть SwiftUI обрабатывает его, но элементы в HStack должны быть одинаковой высоты.
  • Кнопки должны иметь одинаковую ширину и высоту, адаптироваться к более длинному тексту и увеличивать размер рамки.
  • Обе кнопки должны отображать свой полный текст (не следует использовать шрифт для масштабирования / по размеру).

Образец кода


struct SampleView: View {
    var body: some View {
        
        GeometryReader { gr in
            VStack {
                ScrollView {
                    VStack {
                        // Fills whatever space is left
                        Rectangle()
                            .foregroundColor(.clear)

                        Image(systemName: "applelogo")
                            .resizable()
                            .frame(width: gr.size.width * 0.5, height: gr.size.height * 0.3, alignment: .center)
                            //.border(Color.blue)
                            .padding(.bottom, gr.size.height * 0.06)


                        Text("SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME")
                            .fontWeight(.regular)
                            .foregroundColor(.green)
                            .multilineTextAlignment(.center)
                            .padding(.horizontal, 40)
                            .layoutPriority(1)


                        // Fills 15 %
                        Rectangle()
                            .frame(height: gr.size.height * 0.12)
                            .foregroundColor(.clear)

                    DynamicallyScalingView()
                        .padding(.horizontal, 20)
                        .padding(.bottom, 20)

                    }

                    // Makes the content stretch to fill the whole scroll view, but won't be limited (it can grow beyond if needed)
                    .frame(minHeight: gr.size.height)
                }
            }
        }
    }
}

struct DynamicallyScalingView: View {
    @State private var labelHeight = CGFloat.zero     // << here !!

    var body: some View {
        HStack {
            Button(action: {
            }, label: {
                Text("Button 1")
            })
            .foregroundColor(Color.white)
            .padding(.vertical)
            .frame(minWidth: 0, maxWidth: .infinity)
            .frame(minHeight: labelHeight)
            .background(Color.blue)
            .cornerRadius(8)

            Button(action: {
            }, label: {
                Text("Larger Button 2 Text Text2")
            })
            .foregroundColor(Color.white)
            .padding(.vertical)
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(Color.blue)
            .cornerRadius(8)
            .background(GeometryReader {      // << set right side height
                Color.clear.preference(key: ViewHeightKey.self,
                                       value: $0.frame(in: .local).size.height)
            })
        }
        .onPreferenceChange(ViewHeightKey.self) { // << read right side height
            self.labelHeight = $0        // << here !!
        }
        .padding(.horizontal)
    }
}

struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = value + nextValue()
    }
}

struct SampleView_Previews: PreviewProvider {
    static var previews: some View {
        SampleView().previewDevice("iPhone SE (2nd generation)")
    }
}


person Pablo Dev    schedule 07.01.2021    source источник
comment
В SwiftUI родительский просмотр не может заставить дочерний элемент просматривать размер, как в UIKit.   -  person Yodagama    schedule 07.01.2021
comment
Отвечает ли это на ваш вопрос stackoverflow.com/a/62451599/12299030?   -  person Asperi    schedule 07.01.2021
comment
Что должно произойти, если на второй кнопке будет длинный текст, если он будет обрезан?   -  person user1046037    schedule 07.01.2021
comment
@Asperi, Спасибо, что указали мне правильное направление. Да, это работает так, как вы указали, используя ключ предпочтений. К сожалению, в моем представлении больше элементов, позвольте мне обновить вопрос.   -  person Pablo Dev    schedule 07.01.2021


Ответы (1)


Вы можете установить максимальное значение в ключе предпочтений ViewHeightKey:

struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = max(value, nextValue()) // set the `max` value (from both buttons)
    }
}

а затем считайте высоту просмотра с обеих кнопок и установите вертикальное положение fixedSize:

struct DynamicallyScalingView: View {
    @State private var labelHeight = CGFloat.zero

    var body: some View {
        HStack {
            Button(action: {}, label: {
                Text("SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME")
            })
                .foregroundColor(Color.white)
                .padding(.vertical)
                .frame(minWidth: 0, maxWidth: .infinity)
                .frame(minHeight: labelHeight) // min height for both buttons
                .background(Color.blue)
                .cornerRadius(8)
                .fixedSize(horizontal: false, vertical: true) // expand vertically
                .background(GeometryReader { // apply to both buttons
                    Color.clear
                        .preference(
                            key: ViewHeightKey.self,
                            value: $0.frame(in: .local).size.height
                        )
                })

            Button(action: {}, label: {
                Text("jahlsd")
            })
                .foregroundColor(Color.white)
                .padding(.vertical)
                .frame(minWidth: 0, maxWidth: .infinity)
                .frame(minHeight: labelHeight)
                .background(Color.blue)
                .cornerRadius(8)
                .fixedSize(horizontal: false, vertical: true)
                .background(GeometryReader {
                    Color.clear
                        .preference(
                            key: ViewHeightKey.self,
                            value: $0.frame(in: .local).size.height
                        )
                })
        }
        .onPreferenceChange(ViewHeightKey.self) {
            self.labelHeight = $0
        }
        .padding(.horizontal)
    }
}

Примечание: поскольку кнопки теперь похожи, следующим шагом будет их извлечение как другой компонент, чтобы избежать дублирования.

person pawello2222    schedule 07.01.2021
comment
Кнопки должны иметь одинаковую ширину и высоту. Используя fixedSize(), сделайте закрепление рамки кнопок до внутреннего размера. - person Pablo Dev; 08.01.2021
comment
@PabloDev Вы сказали: Кнопка адаптируется к более длинному тексту и увеличивает размер его рамки и Обе кнопки должны отображать свой полный текст. Если текст длиннее половины ширины экрана, одно из этих ограничений недопустимо. - person pawello2222; 08.01.2021
comment
Может я не понял, обновил описание поста. Спасибо, что указали на это. - person Pablo Dev; 08.01.2021
comment
@PabloDev Спасибо, но я все еще не совсем понимаю. Что делать, если на одной кнопке текст размером 2/3 ширины экрана? Как тогда должен выглядеть макет? И чем макет должен отличаться от того, что у вас сейчас (обе кнопки на данный момент равны)? - person pawello2222; 08.01.2021
comment
Высота кнопок увеличится, чтобы вместить текст. В настоящее время он ведет себя так, но рамки (высота) обеих кнопок не равны. Пожалуйста, дайте мне знать, если это непонятно. Пример: мы можем использовать Environment overrides в Xcode для увеличения размера шрифта адаптируемости, обе кнопки должны иметь одинаковую ширину и высоту независимо от текстового содержимого. Как указано в предыдущем требовании, не следует использовать шрифт для масштабирования / фиксированный размер / шрифт для заполнения. Размер шрифта должен соответствовать динамическому размеру шрифта, выбранному на устройстве. - person Pablo Dev; 08.01.2021