Om overlays uitgelijnd te houden met een SwiftUI-diagram dat met behoud van de beeldverhouding wordt geschaald, meet je het geschaalde diagram zelf en plaats je markeringen in die lokale coördinatenruimte. Voeg de meetoverlay toe vóór elk buitenste frame dat lege ruimte toevoegt.
In deze tutorial bouw je een kleine macOS-app met een synthetisch diagram en twee genummerde markeringen. De bedoeling is om elke markering gecentreerd te houden op het bijbehorende diagramonderdeel terwijl de vorm van het venster verandert. De implementatie gebruikt genormaliseerde coördinaten, zodat de posities van annotaties onafhankelijk blijven van de afmetingen van het venster.
Waarom raken overlays verschoven ten opzichte van een diagram dat met behoud van de beeldverhouding wordt geschaald?
Bij schalen met behoud van de beeldverhouding blijven de verhoudingen van een diagram behouden terwijl het in de beschikbare ruimte past. Afhankelijk van de verhoudingen van de container kan daardoor lege ruimte boven en onder het diagram ontstaan, of aan de zijkanten. Apple laat dit gedrag zien in Afbeeldingen in beschikbare ruimte passend maken.
Verschuiving ontstaat wanneer de illustratie en de markeringen verschillende rechthoeken gebruiken. De illustratie kan bijvoorbeeld een gecentreerde rechthoek van 600 bij 375 punten beslaan binnen een container van 600 bij 700 punten. Een markering die wordt gepositioneerd met een fractie van de containerhoogte, neemt de omringende lege ruimte mee in de berekening.
De volgorde van modifiers bepaalt welke rechthoek je meet. Apple documenteert dat view-modifiers de view waarop ze worden toegepast omwikkelen en dat een frame zijn onderliggende view binnen een andere rechthoek positioneert. Een overlay die na een vergrotend frame wordt toegevoegd, meet daardoor een andere lay-outgrens dan een overlay die rechtstreeks aan de geschaalde illustratie wordt gekoppeld. Zie Apples View-documentatie en documentatie over flexibele frames.
Gebruik voor deze taak de volgende volgorde:
- Teken flexibele illustratie.
- Pas de expliciete beeldverhouding toe met
.fit. - Voeg een overlay met
GeometryReadertoe. - Positioneer markeringen met de afmetingen van die reader.
- Voeg het buitenste frame en de omliggende opvulling toe.
Apple raadt een overlay aan wanneer de aangepaste view de lay-out moet bepalen. Daardoor is het diagram geschikt als eigenaar van de lay-out, met annotaties erbovenop. Een opstelling met zussen in een ZStack heeft ander lay-outgedrag en vereist meer aandacht voor welke view de afmetingen bepaalt. Zie overlay(alignment:content:).
Bouw een compleet macOS-voorbeeld
Maak in Xcode een macOS-appproject met de interface SwiftUI. Stel het implementatiedoel in op macOS 12 of hoger, omdat de hier gebruikte closure-gebaseerde overlay-API beschikbaar is vanaf macOS 12. Vervang het gegenereerde app-entrypointbestand door het volgende complete programma en zorg dat er in het target maar één @main-appdeclaratie staat. De beschikbaarheid van de API staat beschreven bij overlay(alignment:content:).
De synthetische illustratie stelt een ontwerpvlak van 800 bij 500 voor, met een beeldverhouding van 1.6. De coördinaten van de markeringen zijn fracties van dat vlak: horizontaal is nul de linkerrand, verticaal is nul de bovenrand en één staat voor de tegenoverliggende rand. Er zijn geen assetbestanden of externe pakketten nodig.
import SwiftUI
@main
struct DiagramAlignmentApp: App {
var body: some Scene {
WindowGroup {
DiagramExample()
.frame(minWidth: 360, minHeight: 260)
}
}
}
struct DiagramMarker: Identifiable {
let id: Int
let label: String
let x: CGFloat
let y: CGFloat
}
struct DiagramExample: View {
let markers = [
DiagramMarker(id: 1, label: "1", x: 0.25, y: 0.40),
DiagramMarker(id: 2, label: "2", x: 0.75, y: 0.60)
]
var body: some View {
Rectangle()
.fill(Color.white)
.overlay {
DiagramArtwork()
.stroke(Color.gray, lineWidth: 2)
}
.aspectRatio(800.0 / 500.0, contentMode: .fit)
.overlay {
GeometryReader { geometry in
ForEach(markers) { marker in
Circle()
.fill(Color.blue)
.frame(width: 28, height: 28)
.overlay {
Text(marker.label)
.font(.caption.bold())
.foregroundColor(.white)
}
.position(
x: marker.x * geometry.size.width,
y: marker.y * geometry.size.height
)
}
}
}
.border(Color.gray)
.frame(
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .center
)
.padding(24)
.background(Color.gray.opacity(0.12))
}
}
struct DiagramArtwork: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.addRect(CGRect(
x: rect.minX + 0.10 * rect.width,
y: rect.minY + 0.20 * rect.height,
width: 0.30 * rect.width,
height: 0.40 * rect.height
))
path.addRect(CGRect(
x: rect.minX + 0.60 * rect.width,
y: rect.minY + 0.40 * rect.height,
width: 0.30 * rect.width,
height: 0.40 * rect.height
))
path.move(to: CGPoint(
x: rect.minX + 0.25 * rect.width,
y: rect.minY + 0.40 * rect.height
))
path.addLine(to: CGPoint(
x: rect.minX + 0.75 * rect.width,
y: rect.minY + 0.60 * rect.height
))
return path
}
}
Start de app en wijzig de breedte en hoogte van het venster. Markering 1 moet op het middelpunt van de eerste rechthoek blijven en markering 2 op het middelpunt van de tweede. De dunne rand geeft de grens van het geschaalde diagram aan; het grijze omliggende gebied hoort bij de buitenste lay-out.
Waarom deze volgorde van modifiers werkt
De basisrechthoek levert flexibele illustratie en .aspectRatio(800.0 / 500.0, contentMode: .fit) legt de verhoudingen ervan vast. Apple definieert het argument voor de beeldverhouding als breedte gedeeld door hoogte en geeft aan dat het resultaat bij .fit van toepassing is op herschaalbare inhoud. Zie aspectRatio(_:contentMode:).
De meetoverlay komt direct na die beperking. GeometryReader bepaalt de inhoud aan de hand van zijn eigen afmetingen en coördinatenruimte, terwijl geometry.size de afmetingen van de container rapporteert. Hier omvat die container de geschaalde rechthoek. Door de genormaliseerde coördinaten met de breedte en hoogte ervan te vermenigvuldigen, krijg je dus lokale posities voor de markeringen. Zie GeometryReader en GeometryProxy.size.
Het uiteindelijke vergrotende frame centreert de volledige compositie van diagram en markeringen in het beschikbare gebied. Omdat de markeringen al aan de binnenste rechthoek zijn gekoppeld, hoeven ze de buitenmarges niet te berekenen.
Behoud de contracten voor tekenen en positioneren
DiagramArtwork voldoet aan Shape, en de methode path(in:) retourneert een Path binnen de opgegeven rechthoek. De methode retourneert geen view en voert geen lay-outwijzigingen uit. Dit volgt het gedocumenteerde path(in:)-contract van Apple.
.position(x:y:) van elke markering plaatst het middelpunt op het berekende punt. De linkerbovenhoek wordt daar niet geplaatst. In het voorbeeld wordt het middelpunt van elke genummerde cirkel bewust op een onderdeel vastgezet, overeenkomstig Apples definitie van position(x:y:).
De cirkels blijven 28 punten breed wanneer het venster van grootte verandert, terwijl hun middelpunten de illustratie volgen. Dat is een bewuste presentatiekeuze voor goed leesbare annotaties. Als de afmetingen van markeringen afstanden binnen de tekening voorstellen, bereken die afmetingen dan ook op basis van de geschaalde afmetingen.
Controleer de uitlijning met een uitgewerkte coördinatenberekening
Een numerieke controle helpt een coördinatenfout te onderscheiden van een stijlprobleem. Bij gecentreerd schalen met behoud van de beeldverhouding zijn de positieve bronafmetingen sourceWidth en sourceHeight, en de eindige beschikbare afmetingen containerWidth en containerHeight. Het volgende is wiskundige pseudocode, geen apart Swift-programma:
scale = min(containerWidth / sourceWidth,
containerHeight / sourceHeight)
fittedWidth = sourceWidth * scale
fittedHeight = sourceHeight * scale
originX = (containerWidth - fittedWidth) / 2
originY = (containerHeight - fittedHeight) / 2
containerX = originX + normalizedX * fittedWidth
containerY = originY + normalizedY * fittedHeight
Deze vergelijkingen leiden uit het gedocumenteerde scha lingsgedrag een uniforme schaalfactor en gecentreerde plaatsing af. Met de illustratieve bron van 800 bij 500 en markering 1 op (0.25, 0.40) krijg je de volgende resultaten. De containerafmetingen zijn hier die van het gebied dat na de omliggende opvulling beschikbaar is.
| Beschikbare container | Geschaald diagram | Oorsprong van diagram in container | Middelpunt van markering in container |
|---|---|---|---|
| 1000 × 700 | 1000 × 625 | (0, 37.5) | (250, 287.5) |
| 600 × 700 | 600 × 375 | (0, 162.5) | (150, 312.5) |
| 1000 × 400 | 640 × 400 | (180, 0) | (340, 160) |
In de middelste rij levert vermenigvuldiging van de volledige containerhoogte met 0.40 280 op, wat niet overeenkomt met de juiste verticale positie van 312.5. In de lokale overlay van het voorbeeld is de verticale coördinaat gewoon 0.40 × 375 = 150; de omringende lay-out levert de marge van 162.5 punten.
De rekenkundige waarden in de tabel zijn gecontroleerd met een kleine deterministische berekening in Python. Het SwiftUI-programma is bekeken, maar is in deze omgeving niet gecompileerd; controleer de weergave ervan in Xcode op de doel-Mac.
Pas het patroon toe op een bestaand diagram
Gebruik je een diagram op basis van een afbeelding, maak de Image dan herschaalbaar voordat je de modifiers voor het schalen met behoud van de beeldverhouding en de overlay toepast. Apples gids voor het passend maken van afbeeldingen legt uit dat een afbeelding anders op de oorspronkelijke grootte wordt weergegeven en dat alleen .resizable() de assen onafhankelijk van elkaar kan schalen. Behoud de werkelijke beeldverhouding van de afbeelding en voeg de markeringsoverlay daarna toe, vóór het buitenste frame. Zie Afbeeldingen in beschikbare ruimte passend maken.
Als annotaties beginnen als broncoördinaten, normaliseer ze dan ten opzichte van dezelfde volledige grenzen van de illustratie: deel de horizontale broncoördinaat door de bronbreedte en de verticale broncoördinaat door de bronhoogte. Transparante marges binnen een afbeelding blijven onderdeel van die grenzen. Zijn coördinaten gemeten ten opzichte van een bijgesneden gebied, zet ze dan om naar het coördinatensysteem van de volledige afbeelding voordat je dit patroon toepast.
Houd afbeeldingsspecifieke modifiers op de Image voordat algemene view-modifiers eromheen worden geplaatst. Behoud op dezelfde manier var body: some View voor de samengestelde view en het retourtype Path voor de tekenmethode van de shape. Apple documenteert aangepaste view-compositie en het omwikkelen met modifiers in View.
De berekening gaat uit van uniforme schaling zonder extra bijsnijden, rotatie of onafhankelijke pan- en zoomtransformatie. Voeg je die bewerkingen toe, pas dan een gedeelde transformatie toe op de illustratie en annotaties, of breid de coördinatenmapping expliciet uit. Overschakelen naar .fill verandert de schaalvoorwaarden en kan inhoud buiten het frame weergeven; Apple beschrijft bijsnijden als een afzonderlijke bewerking in Afbeeldingen in beschikbare ruimte passend maken.
Problemen met de uitlijning opsporen
Controleer deze punten in deze volgorde:
- Meetgrens: Houd de markeringsoverlay vóór het buitenste vergrotende frame. De rand ervan moet de illustratie volgen, niet de omringende lege ruimte.
- Coördinatengrenzen: Controleer of genormaliseerde coördinaten de volledige bronrechthoek en de bedoelde oorsprong linksboven gebruiken. In dit voorbeeld lopen de bedoelde waarden binnen de grenzen van nul tot en met één; wijs onverwachte waarden af in plaats van ze stilzwijgend af te kappen.
- Ankerkeuze: Onthoud dat
.position(x:y:)het middelpunt van de markering als anker gebruikt. Als een hoek van een label een onderdeel moet raken, is een expliciete extra verschuiving of een andere verankeringsmethode nodig. - Kleine lay-outs: Een markering kan correct gecentreerd blijven en toch een andere markering overlappen. Kies een aparte regel om labels bij kleine afmetingen te verbergen of te herschikken.
- Gedrag aan de rand: Een markering die op een rand van de illustratie wordt gecentreerd, steekt over die rand heen. Bepaal of dat uitsteeksel aanvaardbaar is voordat je clipping toevoegt.
Leid posities af van de huidige geometrie in plaats van de eerste gemeten afmetingen op te slaan in .onAppear. Apple definieert die callback voor het verschijnen van een view, waarbij het tijdstip afhangt van het viewtype; het is geen callback voor het wijzigen van de venstergrootte. Door de geometrie rechtstreeks te lezen, blijft de positioneringsberekening in dit voorbeeld gekoppeld aan de huidige lay-out. Zie onAppear(perform:).
Voor een geschaald diagram in een Mac-venster waarvan de grootte kan worden aangepast, is één gedeelde rechthoek een betrouwbaar uitgangspunt: laat de illustratie haar geschaalde grenzen bepalen, meet die grenzen en leid elk middelpunt van een annotatie daarvan af. Voeg omliggende lay-out pas toe nadat die relatie is vastgelegd.
Bronnen
- aspectRatio(_:contentMode:) — Apple Developer Documentation.
- overlay(alignment:content:) — Apple Developer Documentation.
- GeometryReader — Apple Developer Documentation.
- size — Apple Developer Documentation.
- position(x:y:) — Apple Developer Documentation.
- Afbeeldingen in beschikbare ruimte passend maken — Apple Developer Documentation.
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple Developer Documentation.
- path(in:) — Apple Developer Documentation.
- View — Apple Developer Documentation.
- onAppear(perform:) — Apple Developer Documentation.