Динамически устанавливать высоту горизонтальной прокрутки на основе максимальной высоты контента в SwiftUI

Я пытаюсь создать горизонтальную карусель, используя SwiftUI ScrollView.

Это то, что я сделал до сих пор:

struct CarouselView<Content: View>: View {
    let content: Content
    @State private var currentIndex = 0
    @State private var contentSize: CGSize = .zero
    
    private var showsIndicators: Bool
    private var spacing: CGFloat
    private var offset: CGFloat
    private var shouldSnap: Bool
    
    init(showsIndicators: Bool = true,
         spacing: CGFloat = 0,
         offset: CGFloat = 0,
         shouldSnap: Bool = false,
         @ViewBuilder content: @escaping () -> Content) {
        self.content = content()
        self.showsIndicators = showsIndicators
        self.spacing = spacing
        self.offset = offset
        self.shouldSnap = shouldSnap
    }
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: showsIndicators) {
            LazyHStack(spacing: spacing) {
                content
                    .offset(x: offset)
            }.apply {
                if #available(iOS 17.0, *), shouldSnap {
                    $0.scrollTargetLayout()
                } else {
                    $0
                }
            }
            
        }
        .apply {
            if #available(iOS 17.0, *), shouldSnap {
                $0.scrollTargetBehavior(.viewAligned)
            } else {
                $0
            }
        }
    }
}

extension View {
    func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
}

Затем я использую это следующим образом:

struct ContentView: View {
    
    let imagesNames: [String] = ["img-1", "img-2", "img-3"]
    
    var body: some View {
        VStack(spacing: 10, content: {
            pagingCarousel
            continuousCarousel
        })
    }
    
    private var pagingCarousel: some View {
        CarouselView(showsIndicators: false,
                     spacing: 10,
                     shouldSnap: true) {
            
            ForEach(imagesNames, id: \.self) { image in
                createTile(with: image)
            }
        }
    }
    
    private var continuousCarousel: some View {
        CarouselView(showsIndicators: true,
                     spacing: 20,
                     offset: 20) {
            
            ForEach(imagesNames, id: \.self) { image in
                createTile(with: image)
            }
        }
    }
    
    private func createTile(with image: String) -> some View {
        ZStack {
            Image(image)
                .resizable()
                .scaledToFill()
                .overlay(Color.black.opacity(0.5))
            
            Text("Headline")
                .bold()
                .foregroundStyle(.white)
            
        }
        .frame(height: 200)
        .cornerRadius(30)
        .clipped()
    }
}

С точки зрения функциональности это работает хорошо и дает мне следующее:

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

Хотя в приведенном выше примере это не так ясно, если я добавлю фон в представление карусели, вы увидите, сколько дополнительного места занято.

Итак, теперь обновляем это:

var body: some View {
    VStack(spacing: 10, content: {
        pagingCarousel
            .background(.yellow)
        
        continuousCarousel
            .background(.green)
    })
}

Дает мне это:

Как я могу заставить свой прокрутку устанавливать высоту в зависимости от высоты содержимого?

Я попробовал несколько из этих решений, но из-за этого мой контент просто исчез:

  1. Решение 1
  2. Решение 2

@Sweeper - ты прав, он не нужен и тоже не использовался. Я забыл удалить его из одного из примеров решений, которые я пробовал. Я обновил код, чтобы отразить ваш комментарий - нет средства чтения геометрии... однако результат точно такой же, как показано в моем вопросе, потому что я никогда ни для чего не использовал средство чтения геометрии.

Shawn Frank 30.04.2024 09:28

Ах, это опять лень ленивых стопок. LazyHStack не знает, какой будет максимальная высота его содержимого (потому что ленив). В этом случае ваши изображения имеют постоянную высоту, так как вы это сделали .frame(height: 200). В общем, вам нужно будет найти максимальную высоту другим способом и установить ее как высоту LazyHStack.

Sweeper 30.04.2024 09:38

См. также этот вопрос, который вызван той же ленью. Хотя в этом случае LazyHStack сжимается до высоты первого подпредставления, потому что оно находится в строке списка.

Sweeper 30.04.2024 09:48

@Sweeper Итак, можно ли сделать вывод, что этого невозможно достичь из-за LazyHStack ? Было бы это достижимо, если бы вместо этого мы использовали HStack?

Shawn Frank 30.04.2024 10:30

Да, вы можете изменить это на HStack, и все должно работать так, как вы ожидаете.

Sweeper 30.04.2024 10:31
Стоит ли изучать 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
5
196
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Это потому, что LazyHStack предназначен для ленивого рисования своих видов. Он не будет пытаться все разложить и найти максимальную необходимую ему высоту (это совсем не лениво).

Поэтому в этом случае он пытается заполнить все доступное пространство. В других случаях, когда необходимо использовать идеальный размер, он рассчитает идеальный размер, используя только первое представление в стеке. Вы можете добиться этого, выполнив .fixedSize(horizontal: false, vertical: true).

В вашем примере с игрушкой все изображения имеют постоянную высоту 200, поэтому это легко исправить, установив высоту карусели с помощью .frame(height: 200). В общем, вам нужен другой способ узнать, какая будет максимальная высота, и сделать что-то вроде .frame(height: findMaxHeight(data)). Например, изображения могут быть получены в результате вызова API, который также возвращает размеры изображения. Затем вы можете использовать эту информацию, чтобы найти максимальную высоту, не рисуя все виды.

Если нет другого способа узнать максимальную высоту, кроме рисования видов, вместо этого вам следует просто использовать неленивый HStack.

Спасибо, это имеет смысл. Мне просто интересно узнать о вашем комментарии: Therefore, it tries to fill all the available space in this case. In other cases, where its ideal size needs to be used, it will calculate its ideal size using only the first view in the stack. Есть ли способ хотя бы сделать высоту прокрутки такой же, как у первого элемента в lazyHStack?

Shawn Frank 30.04.2024 13:07

@ShawnFrank Да. Используйте .fixedSize(horizontal: false, vertical: true), чтобы заставить его использовать идеальную высоту.

Sweeper 30.04.2024 13:19

Спасибо за ваше время и помощь, вы также можете добавить этот последний бит к ответу, так как все мои элементы будут иметь фиксированную ширину, поэтому высота первого элемента работает, и поэтому .fixedSize(horizontal: false, vertical: true) работает для меня.

Shawn Frank 30.04.2024 14:11

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