SwiftUI: вложенные представления прокрутки на противоположной оси

Я пытаюсь создать интерфейс с представлением вертикальной прокрутки на верхнем уровне, а затем представлением горизонтальной прокрутки (с использованием новой прокрутки с разбиением на страницы из iOS 17), чтобы отобразить несколько дочерних представлений, между которыми пользователь может прокручиваться вбок. Пока что он ведет себя точно так, как я хочу, за исключением того, что высота первого из представлений в представлении с горизонтальной прокруткой, похоже, задает высоту для каждого из других представлений, даже если они имеют более высокий контент. Честно говоря, я не уверен, какое поведение я себе представляю, но мне было интересно, решал ли кто-нибудь подобную проблему или разработал аналогичный макет другим способом.

Вот минимальный воспроизводимый пример:

import SwiftUI

struct ContentView: View {
    @State private var selectedTab: String? = "Tab 1"

    var body: some View {
        ScrollView(.vertical) {
            LazyVStack {
                Image(systemName: "photo.fill")
                    .resizable()
                    .aspectRatio(contentMode: .fill)

                ScrollView(.horizontal) {
                    LazyHStack(spacing: 0) {
                        SampleView(.purple, 5)
                            .id("Tab 1")
                            .containerRelativeFrame(.horizontal)

                        SampleView(.red, 12)
                            .id("Tab 2")
                            .containerRelativeFrame(.horizontal)

                        SampleView(.blue, 20)
                            .id("Tab 3")
                            .containerRelativeFrame(.horizontal)
                    }
                    .scrollTargetLayout()
                }
                .scrollPosition(id: $selectedTab)
                .scrollTargetBehavior(.paging)
            }
        }
    }

    @ViewBuilder
    func SampleView(_ color: Color, _ size: Int) -> some View {
        LazyVGrid(columns: Array(repeating: GridItem(), count: 2), content: {
            ForEach(1...size, id: \.self) { _ in
                RoundedRectangle(cornerRadius: 15)
                    .fill(color.gradient)
                    .frame(height: 150)
            }
        })
    }
}

Как видно из примера, высота горизонтального прокрутки зафиксирована на высоте первого дочернего представления.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
252
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это происходит из-за того, что LazyHStack используется для горизонтальной прокрутки контента.

  • Внешний LazyVStack основывает свою высоту на идеальной высоте вложенного (горизонтального) ScrollView.
  • Вложенный ScrollView использует идеальную высоту LazyHStack.
  • LazyHStack использует высоту первого дочернего элемента, потому что другие подпредставления не видны и поэтому еще не имеют размера.

Если вы измените LazyHStack на HStack, то вертикальная прокрутка будет работать на полный рост самого высокого ребенка. Но, конечно, вы также можете прокрутить вниз за пределы первого дочернего элемента. Вероятно, вы тоже захотите добавить выравнивание .top:

ScrollView(.vertical) {
    LazyVStack {
        Image(systemName: "photo.fill")
            // ...

        ScrollView(.horizontal) {
            HStack(alignment: .top, spacing: 0) { // 👈 HERE
                // ...
            }
            .scrollTargetLayout()
        }
        .scrollPosition(id: $selectedTab)
        .scrollTargetBehavior(.paging)
    }
}

Animation

Если известен рост самого высокого ребенка, альтернативным решением было бы применить это значение как minHeight к LazyHStack:

LazyHStack(alignment: .top, spacing: 0) {
    // ...
}
.frame(minHeight: 1000)
.scrollTargetLayout()

Однако, если вы переоцените высоту, то вертикальная прокрутка просто прокрутит в пустое пространство, а если вы недооцените высоту, то более высокий ребенок будет обрезан, как вы видели изначально.

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

LazyHStack(alignment: .top, spacing: 0) {
    SampleView(.blue, 20)
        .id("Tab 1")
        .containerRelativeFrame(.horizontal)
        .hidden()
        .overlay(alignment: .top) {
            SampleView(.purple, 5)
        }

    SampleView(.red, 12)
        .id("Tab 2")
        .containerRelativeFrame(.horizontal)

    SampleView(.blue, 20)
        .id("Tab 3")
        .containerRelativeFrame(.horizontal)
}
.scrollTargetLayout()

РЕДАКТИРОВАТЬ Ваш комментарий заставил меня задуматься. Поскольку вы используете scrollTargetLayout, изменение подпредставления будет отражено изменением на selectedTab. Используя GeometryReader на заднем плане каждого подпредставления, minHeight для LazyHStack можно обновить, чтобы он соответствовал высоте выбранного подпредставления при каждом изменении содержимого.

@State private var selectedTab: String? = "Tab 1"
@State private var minContentHeight = CGFloat.zero

private func heightReader(target: String) -> some View {
    GeometryReader { proxy in
        Color.clear
            .onChange(of: selectedTab, initial: true) { oldVal, newVal in
                if target == newVal {
                    withAnimation {
                        minContentHeight = proxy.size.height
                    }
                }
            }
    }
}

var body: some View {
    ScrollView(.vertical) {
        LazyVStack {
            Image(systemName: "photo.fill")
                .resizable()
                .aspectRatio(contentMode: .fill)

            ScrollView(.horizontal) {
                LazyHStack(alignment: .top, spacing: 0) {
                    SampleView(.purple, 5)
                        .id("Tab 1")
                        .containerRelativeFrame(.horizontal)
                        .background { heightReader(target: "Tab 1") }

                    SampleView(.red, 12)
                        .id("Tab 2")
                        .containerRelativeFrame(.horizontal)
                        .background { heightReader(target: "Tab 2") }

                    SampleView(.blue, 20)
                        .id("Tab 3")
                        .containerRelativeFrame(.horizontal)
                        .background { heightReader(target: "Tab 3") }
                }
                .frame(minHeight: minContentHeight)
                .scrollTargetLayout()
            }
            .scrollPosition(id: $selectedTab)
            .scrollTargetBehavior(.paging)
        }
    }
}

Animation

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

Brandon Slaght 08.04.2024 18:45

@BrandonSlaght Спасибо, что приняли ответ! Ваш комментарий заставил меня задуматься, ответ дополнен тем, что, по моему мнению, является лучшим решением. Возможно, это то решение, на которое вы намекали.

Benzy Neez 08.04.2024 20:30

Ваше предложение намного чище, чем то, что я пытался сделать, и гораздо более без ошибок, очень ценю, что вы нашли на это время!

Brandon Slaght 10.04.2024 02:49

@BrandonSlaght Эй, спасибо за награду! Рад слышать, что решение оказалось полезным.

Benzy Neez 10.04.2024 09:07

Другие вопросы по теме