AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79447439
Accepted
Gargo
Gargo
Asked: 2025-02-18 15:30:12 +0800 CST2025-02-18 15:30:12 +0800 CST 2025-02-18 15:30:12 +0800 CST

Adicionar espaçamento igual aos elementos internos do HStack para preencher o espaço disponível no SwiftUI?

  • 772

Começo com o seguinte código:

import SwiftUI

struct ContentView: View {
    var body: some View {
        HStack(spacing: 20) {
            ExtractedView(text: "Energy")
            ExtractedView(text: "Breath Control")
                
        }
        .padding(.horizontal, 20)
    }
}

#Preview {
    ContentView()
}

struct ExtractedView: View {
    let text: String
    
    var body: some View {
        Button {
            
        } label: {
            HStack(spacing: 8) {
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text(text)
                    .lineLimit(1)
                    .font(.system(size: 18, weight: .bold))
            }
            .padding(.horizontal, 8)
            .padding(.vertical, 8)
            .background {
                Color.yellow
            }
        }
    }
}

insira a descrição da imagem aqui

O resultado aproximado que quero alcançar: insira a descrição da imagem aqui

Em outras palavras, preciso adicionar espaçamento igual dentro de cada elemento após o texto, mas não sei como fazer isso. Tentei um código diferente, mas o tamanho do botão se torna igual ou o iOS adiciona uma nova linha ao segundo rótulo ou tenta encurtar o segundo rótulo mesmo quando há espaço suficiente.

swiftui
  • 1 1 respostas
  • 61 Views

1 respostas

  • Voted
  1. Best Answer
    Benzy Neez
    2025-02-18T17:27:34+08:002025-02-18T17:27:34+08:00

    Uma maneira de resolver é usar um personalizado Layout:

    • A largura ideal é baseada no tamanho ideal das visualizações.
    • Qualquer excesso de largura é compartilhado igualmente entre as visualizações no contêiner.

    Aqui está um exemplo de implementação que funciona desta maneira:

    struct PaddedToFill: Layout {
        typealias Cache = IdealSizes
        let spacing: CGFloat
    
        struct IdealSizes {
            let idealWidths: [CGFloat]
            let idealMaxHeight: CGFloat
    
            var isEmpty: Bool { idealWidths.isEmpty }
            var nWidths: Int { idealWidths.count }
        }
    
        func makeCache(subviews: Subviews) -> IdealSizes {
            var idealWidths = [CGFloat]()
            var idealMaxHeight = CGFloat.zero
            for subview in subviews {
                let idealViewSize = subview.sizeThatFits(.unspecified)
                idealWidths.append(idealViewSize.width)
                idealMaxHeight = max(idealMaxHeight, idealViewSize.height)
            }
            return IdealSizes(idealWidths: idealWidths, idealMaxHeight: idealMaxHeight)
        }
    
        func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout IdealSizes) -> CGSize {
    
            // Consume all the width available
            CGSize(width: proposal.width ?? 10, height: cache.idealMaxHeight)
        }
    
        func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout IdealSizes) {
            if !cache.isEmpty, subviews.count == cache.nWidths {
                let idealContainerWidth = cache.idealWidths.reduce(0) { $0 + $1 } + (CGFloat(cache.nWidths - 1) * spacing)
                let excessWidth = max(0, bounds.width - idealContainerWidth)
                let paddingPerView = excessWidth / CGFloat(cache.nWidths)
                var minX = bounds.minX
                for (index, subview) in subviews.enumerated() {
                    let w = cache.idealWidths[index] + paddingPerView
                    let viewSize = subview.sizeThatFits(ProposedViewSize(width: w, height: bounds.height))
                    let h = viewSize.height
                    let x = minX + ((w - viewSize.width) / 2)
                    let y = bounds.minY + ((bounds.height - h) / 2)
                    subview.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(width: w, height: h))
                    minX += w + spacing
                }
            }
        }
    }
    

    Também é necessária uma alteração em ExtractedView, para que ele se expanda e preencha a largura disponível antes que o fundo amarelo seja adicionado:

    // ExtractedView
    
    Button {
    
    } label: {
        HStack(spacing: 8) {
            // ...
        }
        .padding(.horizontal, 8)
        .padding(.vertical, 8)
        .frame(maxWidth: .infinity) // 👈 added
        .background {
            Color.yellow
        }
    }
    

    Para usar, basta substituir o HStackno seu código original por PaddedToFill:

    PaddedToFill(spacing: 20) {
        ExtractedView(text: "Energy")
        ExtractedView(text: "Breath Control")
    }
    .padding(.horizontal, 20)
    

    Captura de tela


    Na sua captura de tela do resultado aproximado, o padding extra sempre estava no lado final de cada botão. Para atingir esse resultado, basta adicionar um alignmentparâmetro ao definir o maxWidthin ExtractedView:

    HStack(spacing: 8) {
        // ...
    }
    .padding(.horizontal, 8)
    .padding(.vertical, 8)
    .frame(maxWidth: .infinity, alignment: .leading) // 👈 + alignment
    .background {
        Color.yellow
    }
    

    Captura de tela


    O EDIT Layout foi introduzido no iOS 16. Se você ainda precisa dar suporte ao iOS 15, precisará de uma solução de fallback para esta versão. Uma maneira seria medir o tamanho do contêiner usando um GeometryReader, então compartilhar a largura excedente entre os botões.

    Aqui está um exemplo de como isso pode ser resolvido dessa forma. A maneira mais fácil de preencher os botões é permitir que o tamanho do preenchimento extra seja passado como um parâmetro para ExtractedView:

    struct ExtractedView: View {
        let text: String
        var extraHorizontalPadding = CGFloat.zero // 👈 added
    
        var body: some View {
            Button {
    
            } label: {
                HStack(spacing: 8) {
                    // ...
                }
                .padding(.horizontal, 8)
                .padding(.horizontal, extraHorizontalPadding) // 👈 added
                .padding(.vertical, 8)
                .frame(maxWidth: .infinity)
                .background {
                    Color.yellow
                }
            }
        }
    }
    

    O tamanho do acolchoamento extra é calculado da mesma forma que o costume Layout, com base no tamanho ideal do contêiner.

    • O tamanho ideal é encontrado adicionando uma versão oculta do HStackao fundo e usando um GeometryReaderpara medir seu tamanho.
    • A largura disponível é medida envolvendo o visível HStackcom outro GeometryReader.
    struct ContentView: View {
        let spacing: CGFloat = 20
        @State private var idealContainerSize: CGSize?
    
        @ViewBuilder
        private var buttons: some View {
            ExtractedView(text: "Energy")
            ExtractedView(text: "Breath Control")
        }
    
        var body: some View {
            if #available(iOS 16.0, *) {
                PaddedToFill(spacing: spacing) {
                    buttons
                }
                .padding(.horizontal, spacing)
            } else {
                legacyLayout
            }
        }
    
        private var legacyLayout: some View {
            GeometryReader { outer in
                let actualContainerWidth = outer.size.width
                let excessWidth: CGFloat = max(0, actualContainerWidth - (idealContainerSize?.width ?? actualContainerWidth))
                let paddingPerView = excessWidth / 2
                HStack(spacing: spacing) {
                    ExtractedView(text: "Energy", extraHorizontalPadding: paddingPerView / 2)
                        .fixedSize()
                    ExtractedView(text: "Breath Control", extraHorizontalPadding: paddingPerView / 2)
                        .fixedSize()
                }
                .frame(maxWidth: .infinity)
                .background {
                    HStack(spacing: spacing) {
                        buttons
                    }
                    .fixedSize()
                    .hidden()
                    .background {
                        GeometryReader { inner in
                            Color.clear
                                .onAppear {
                                    idealContainerSize = inner.size
                                }
                        }
                    }
                }
            }
            .frame(maxHeight: idealContainerSize?.height)
            .padding(.horizontal, spacing)
        }
    }
    
    @available(iOS 16.0, *)
    struct PaddedToFill: Layout {
        // ... as before
    }
    

    EDIT 2 O costume Layoutfica mais complicado se os rótulos de texto devem ser quebrados quando não couberem em uma linha. Você pode tentar estas mudanças:

    1. Remova o .lineLimitem ExtractedView:
    // ExtractedView
    
    Text(text)
        // .lineLimit(1)
        .font(.system(size: 18, weight: .bold))
    
    1. Altere a função sizeThatFitspara solicitar o dobro da altura se a largura proposta for menor que a largura ideal:
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout IdealSizes) -> CGSize {
        let idealContainerWidth = cache.idealWidths.reduce(0) { $0 + $1 } + (CGFloat(cache.nWidths - 1) * spacing)
        let proposalWidth = proposal.width ?? 10
        return CGSize(
            width: proposalWidth,
            height: proposalWidth >= idealContainerWidth ? cache.idealMaxHeight : 2 * cache.idealMaxHeight
        )
    }
    

    Esta é uma aproximação bastante grosseira da altura que será realmente necessária e permite que o texto seja quebrado em apenas uma linha adicional. Uma implementação mais rigorosa precisaria chamar as subvisualizações com uma proposta de largura reduzida, computada exatamente como está sendo feita em placeSubviews.

    1. Permita que o excesso de largura fique negativoplaceSubviews
    // let excessWidth = max(0, bounds.width - idealContainerWidth)
    let excessWidth = bounds.width - idealContainerWidth
    
    • 1

relate perguntas

  • SwiftUI: Como criar um gradiente com um tom mais claro?

  • SwiftUI - visão comum com variável "binding"

  • Criando uma visualização do dia do calendário

  • Como deixar a animação mais lenta SwiftUI

  • Não entendo o layout de texto do SwiftUI

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve