Para ajustar controles laterais a uma pré-visualização SwiftUI com proporção de aspecto, calcule o tamanho da pré-visualização ajustada a partir do espaço disponível na janela, depois dê aos controles essa mesma altura. Este exemplo para macOS mantém uma pré-visualização sintética proporcional e faz controles que transbordam rolar.

A tarefa é um editor de desktop redimensionável com uma pré-visualização à esquerda e um painel de controles de largura fixa à direita. Aqui, "pré-visualização" significa conteúdo dentro do seu aplicativo em execução. O objetivo é alinhar as bordas de layout superior e inferior dos dois painéis conforme a janela muda de tamanho.

Qual altura os controles devem seguir?

Comece definindo o retângulo que você deseja igualar. Uma pré-visualização ajustada por proporção pode ocupar menos espaço que seu contêiner envolvente. Se os controles seguirem a altura do contêiner, eles podem se estender abaixo da pré-visualização visível mesmo quando ambas as visualizações estão corretamente dispostas.

A Apple define a proporção de aspecto como largura dividida por altura. Para conteúdo redimensionável, aspectRatio(_:contentMode:) com .fit preserva essa proporção enquanto encaixa o conteúdo no espaço proposto. O exemplo da elipse na documentação mostra por que a forma ajustada e um quadro envolvente quadrado podem ter dimensões diferentes. Documentação de proporção de aspecto da Apple.

Esta implementação torna o retângulo ajustado explícito. Ela reserva uma largura fixa para os controles e um espaçamento entre os painéis, depois encaixa a pré-visualização na largura restante e na altura disponível. Ambos os painéis recebem a altura calculada da pré-visualização.

Essa é uma política de layout para este exemplo: a pré-visualização determina a altura da viewport dos controles. Uma lista longa de controles rola dentro dessa viewport em vez de tornar a pré-visualização mais alta. A Apple documenta ScrollView como exibindo uma porção móvel de seu conteúdo subjacente dentro de uma região rolável. Documentação de scroll view da Apple.

Calcule o retângulo ajustado uma vez

Sejam W e H as dimensões de conteúdo disponíveis após preenchimento externo, C a largura dos controles, G o espaçamento, e R a proporção largura-para-altura da pré-visualização. O cálculo é:

remainingWidth = max(0, W - C - G)
previewWidth = min(remainingWidth, H * R)
previewHeight = previewWidth / R
controlsHeight = previewHeight

O primeiro limite impede que a pré-visualização use o espaço horizontal dos controles. O segundo impede que uma janela larga produza uma pré-visualização mais alta que a área disponível. Calcular a altura a partir da largura selecionada preserva a proporção em qualquer caso.

Por exemplo, suponha que a área de conteúdo antes do preenchimento seja 960 por 600 pontos. Com preenchimento de 16 pontos em todas as bordas, um painel de controles de 220 pontos, um espaçamento de 16 pontos, e uma pré-visualização 16:9, a área disponível é 928 por 568 pontos. A largura restante da pré-visualização é 692 pontos, dando uma altura de 389,25 pontos. A viewport dos controles torna-se portanto 220 por 389,25 pontos.

Estas são entradas ilustrativas, não resultados medidos da aplicação. Elas também mostram por que igualar os controles à altura total disponível de 568 pontos produziria o alinhamento errado.

Use CGFloat para as dimensões de layout e uma proporção positiva e finita. Em Swift, let ratio: CGFloat = 16.0 / 9.0 torna o cálculo de ponto flutuante pretendido explícito. Se seu aplicativo posteriormente aceitar proporções configuráveis, valide-as antes da divisão.

Construa o exemplo completo para macOS

Crie um aplicativo SwiftUI para macOS no Xcode e defina seu alvo de implantação para macOS 13 ou posterior. Substitua as declarações de aplicativo e visualização de conteúdo geradas com este arquivo único, deixando exatamente uma declaração @main no alvo. O exemplo não precisa de ativos ou pacotes externos.

O requisito de implantação vem da API de dimensionamento de janela usada aqui. A cena usa .contentMinSize, que a Apple documenta como derivando o mínimo da janela a partir de seu conteúdo sem impor tamanho máximo. Documentação de redimensionabilidade de janela da Apple, Documentação de dimensionamento mínimo de conteúdo da Apple.

import SwiftUI

@main
struct MatchedPreviewApp: App {
    var body: some Scene {
        WindowGroup {
            PreviewEditor()
                .frame(minWidth: 640, minHeight: 360)
        }
        .windowResizability(.contentMinSize)
    }
}

struct PreviewEditor: View {
    @State private var intensity: Double = 0.5
    @State private var showsLabel = true

    private let ratio: CGFloat = 16.0 / 9.0
    private let controlsWidth: CGFloat = 220
    private let gap: CGFloat = 16

    var body: some View {
        GeometryReader { geometry in
            let availableHeight = max(0, geometry.size.height)
            let remainingWidth = max(
                0,
                geometry.size.width - controlsWidth - gap
            )
            let previewWidth = min(
                remainingWidth,
                availableHeight * ratio
            )
            let previewHeight = previewWidth / ratio

            HStack(alignment: .top, spacing: gap) {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.blue.opacity(0.2 + intensity * 0.7))
                    .aspectRatio(ratio, contentMode: .fit)
                    .overlay {
                        Text("Sample composition")
                            .font(.headline)
                            .opacity(showsLabel ? 1 : 0)
                    }
                    .frame(
                        width: previewWidth,
                        height: previewHeight
                    )

                ScrollView(.vertical) {
                    VStack(alignment: .leading, spacing: 16) {
                        Text("Preview controls")
                            .font(.headline)

                        Toggle("Show label", isOn: $showsLabel)

                        VStack(alignment: .leading, spacing: 8) {
                            Text("Color intensity")
                            Slider(value: $intensity, in: 0...1)
                        }

                        ForEach(1...8, id: \.self) { index in
                            Text("Sample setting \(index)")
                                .font(.caption)
                        }
                    }
                    .padding(16)
                    .frame(maxWidth: .infinity, alignment: .leading)
                }
                .frame(
                    width: controlsWidth,
                    height: previewHeight
                )
                .background(Color.gray.opacity(0.08))
            }
            .frame(
                width: geometry.size.width,
                height: geometry.size.height,
                alignment: .center
            )
        }
        .padding(16)
    }
}

GeometryReader fornece o tamanho da região alocada ao editor. A Apple o descreve como definindo conteúdo a partir de seu próprio tamanho e espaço de coordenadas, com tamanho preferido flexível. Colocar o preenchimento fora do leitor significa que o cálculo usa a área de conteúdo com recuo. Documentação de geometry reader da Apple.

Os dois quadros fixos estabelecem a altura compartilhada. A Apple documenta frame(width:height:alignment:) como criando um quadro invisível com as dimensões especificadas. O fundo dos controles vem depois desse quadro, então ele cobre a viewport dos controles, incluindo qualquer espaço abaixo de uma lista curta. Documentação de quadro fixo da Apple.

O quadro final centraliza a linha completa dentro do leitor. Ele não amplia nenhum painel. Consequentemente, uma janela alta pode conter espaço não utilizado acima e abaixo da linha enquanto os controles ainda igualam a pré-visualização.

Preserve estado e comportamento de ciclo de vida durante redimensionamento

Mantenha valores editáveis pelo usuário separados da geometria calculada. No exemplo, intensity e showsLabel são estado; largura e altura da pré-visualização são valores locais derivados do tamanho disponível atual.

A Apple documenta estado como armazenamento gerenciado pelo SwiftUI cujas mudanças atualizam partes dependentes da hierarquia de visualizações. Isso suporta manter os valores do controle deslizante e da alternância no editor que os possui. Documentação de estado da Apple.

O cálculo de layout não contém atribuições de estado, callbacks de aparência, trabalho assíncrono, ou feedback de medição. Redimensionar muda as dimensões usadas pela mesma estrutura de visualização. O rótulo permanece presente e muda opacidade quando a alternância muda.

Ao adaptar isso a um editor existente, mantenha sua propriedade de modelo e tipos de vinculação. Aplique a política de dimensionamento ao redor da pré-visualização e controles; não realoque a criação do modelo para dentro do cálculo de geometria. A aritmética deste exemplo requer apenas as dimensões disponíveis e a proporção escolhida.

Verifique redimensionamento, transbordamento e ordem de modificadores

Use as seguintes dimensões como verificações de cálculo. Elas descrevem a área de conteúdo antes do preenchimento externo do exemplo, não o quadro externo da janela. Os valores esperados são arredondados a duas casas decimais.

Área de conteúdo Tamanho da pré-visualização Viewport dos controles
960 × 600 pontos 692 × 389,25 220 × 389,25
640 × 360 pontos 372 × 209,25 220 × 209,25
1200 × 400 pontos 654,22 × 368 220 × 368
700 × 900 pontos 432 × 243 220 × 243

A aritmética foi verificada com um pequeno cálculo em Python, incluindo asserções de que a pré-visualização ajustada permanece dentro da altura disponível e que ambos os painéis mais o espaçamento cabem horizontalmente. Swift e Xcode não estavam disponíveis no ambiente de pesquisa, então o programa SwiftUI completo foi inspecionado mas não compilado ou executado.

No Xcode, execute o aplicativo e redimensione-o através de formas largas, curtas e altas. Confirme que as bordas dos painéis permanecem alinhadas, que os controles rolam quando seu conteúdo excede a viewport, e que o controle deslizante e a alternância retêm seus valores durante o redimensionamento.

Se o alinhamento ainda diferir após adaptar o exemplo, inspecione estas condições específicas:

  • Preenchimento extra do painel: Preenchimento aplicado fora apenas do quadro fixo de um painel aumenta a pegada total desse painel. Mantenha o preenchimento dos controles dentro da visualização de rolagem, como mostrado.
  • Um wrapper de pré-visualização maior: Se você adicionar um quadro maior ao redor da pré-visualização, decida se os controles devem igualar esse wrapper ou o conteúdo ajustado. O cálculo aqui iguala o retângulo ajustado.
  • Conteúdo personalizado transbordando: Um quadro estabelece dimensões de layout mas não oculta automaticamente desenhos fora deles. A Apple documenta .clipped() para recorte retangular; use-o quando transbordamento intencional da pré-visualização deve ser ocultado. Mantenha os controles roláveis para que todas as configurações permaneçam acessíveis. Documentação de recorte da Apple.
  • Espaço de host insuficiente: O exemplo depende de seu tamanho mínimo de conteúdo. Se incorporado em um host menor, sua largura fixa de controles pode não deixar largura útil de pré-visualização. Escolha um painel menor, um arranjo vertical, ou um mínimo explícito para esse host.

Quando você deve usar um layout personalizado?

Esta abordagem serve a um editor que recebe uma região de janela limitada e usa uma proporção de aspecto conhecida. Sua política de dimensionamento é explícita e não depende de medir o conteúdo interno da pré-visualização.

Se seu componente reutilizável deve determinar seu tamanho preferido a partir de seus filhos, considere um Layout personalizado. O contrato documentado da Apple separa o relato do tamanho composto através de sizeThatFits(proposal:subviews:cache:) do posicionamento de filhos através de placeSubviews(in:proposal:subviews:cache:). Proxies de subvisualização fornecem operações de medição e posicionamento. Documentação de layout da Apple.

Para o editor de desktop limitado mostrado aqui, um retângulo ajustado compartilhado é suficiente: reserve espaço horizontal para os controles, ajuste a pré-visualização contra ambas as dimensões disponíveis, e atribua sua altura à viewport dos controles. Isso lhe dá uma regra de redimensionamento clara enquanto deixa o conteúdo dos controles livre para rolar.

Fontes