Progettare per iPhone Duo, dal layout al codice
Dodici capitoli, SwiftUI e UIKit per la stessa situazione. Un giudizio verificabile per capitolo, poi perché i pannelli restano in basso.
Prima fai in modo che l'interfaccia stia bene a qualsiasi larghezza. L'hardware pieghevole arriva dopo, come un vantaggio in più. iPhone Duo ha uno schermo esterno, uno schermo interno e una cerniera. Non è un nuovo idiom e non deve far nascere una seconda app. Il testo segue la curva di apprendimento: ogni capitolo consegna un solo giudizio che puoi verificare. Prima guardi la scrittura che si rompe, poi il motivo di questo disegno, e infine lo stesso scenario in SwiftUI e in UIKit.
Le firme sono state confrontate con l'SDK iOS 27.1 di Xcode 27.1. Il comportamento viene da Designing for iPhone Duo, da Preparing your app for iPhone Duo e da Three steps. Il corpo del testo è una riformulazione, non una copia letterale della documentazione.
Come leggerlo. Ogni capitolo apre con «Dopo questo capitolo sai», poi l'esempio che si rompe, la scrittura corretta e «Quando usarlo / Quando non usarlo». Rispondi prima tu ai punti di controllo; le risposte stanno in fondo. Nei capitoli 0–6 non interroghi la piega, non usi Arrangement e non leggi l'angolo della cerniera. Quello viene dopo.
Capitolo 0 · Sei posture sono la verifica, non sei interfacce
Dopo questo capitolo sai. Quando un Duo si chiude, si apre, si piega a metà e ruota, dire secondo quale larghezza va pensata l'interfaccia, invece di scrivere una versione per ogni postura.
Esempio che si rompe
Non organizzare il prodotto così: verticale da chiuso, orizzontale da chiuso, verticale da aperto, orizzontale da aperto, semi-piegato come un libro, semi-piegato in piedi sul tavolo, ciascuno con un proprio albero di viste. A ogni apertura e chiusura cambi navigazione, cambi pulsanti e perdi la posizione di scorrimento.
SwiftUI che si biforca sulla postura:
enum Pose { case closed, open, half }
struct Root: View {
let pose: Pose
var body: some View {
switch pose {
case .closed: PhoneHome()
case .open: TabletHome()
case .half: BookHome()
}
}
}
UIKit che si biforca sulla postura:
func installRoot(for pose: String) {
let root: UIViewController
switch pose {
case "closed": root = PhoneHomeController()
case "open": root = TabletHomeController()
default: root = BookHomeController()
}
// 三套控制器,三份选中项
}
Perché Apple dà solo due larghezze
Duo ha uno schermo esterno e uno schermo interno. La cerniera può chiudersi, aprirsi del tutto oppure fermarsi a metà. La persona lo tiene in verticale, in orizzontale, lo apre come un libro o lo appoggia sul tavolo. Sono variazioni continue dentro lo stesso utilizzo. La richiesta delle HIG è questa: lo schermo esterno usa la larghezza compact, lo schermo interno usa la larghezza regular, e il layout che hai già cresce insieme allo spazio disponibile. Non rifare l'app per ogni modo di tenerla in mano. Stai ancora progettando un iPhone. Le regole di layout, barre e aree sicure di iOS restano valide.
| Quello che vedi | Che cos'è | Come pensarlo in fase di progetto |
|---|---|---|
| Chiuso | Schermo esterno. Più largo e più basso di un iPhone comune | Larghezza compact. In verticale l'altezza resta regular |
| Completamente aperto | Grande schermo interno, regular × regular | Estensione naturale di un layout da iPad, ma resta un'app per iPhone |
| Semi-piegato | Lo schermo interno è diviso in due lati dalla piega | È ancora la stessa gerarchia. In questo capitolo la piega non si tratta |
| Rotazione, Split View | Contenitori diversi sullo stesso dispositivo | La larghezza passa per valori intermedi. Non esistono solo le due fasce «schermo esterno / schermo interno intero» |
Sulla direzione delle barre tieni a mente una frase. Lo schermo interno in verticale è l'unica postura che conserva le barre orizzontali in alto e in basso. Su schermo esterno, e sullo schermo interno in orizzontale, le barre di sistema si spostano di lato. In Split View la barra di ciascun lato sta sul proprio bordo esterno. Questi dettagli diventano codice nel Capitolo 5.
Il modo giusto di organizzarlo
Una sola interfaccia. Lo stato sta fuori dal layout. Se cambia la larghezza, cambia solo la disposizione, non i dati.
struct Root: View {
@State private var selection: Item.ID?
var body: some View {
Home(selection: $selection)
}
}
final class RootController: UIViewController {
let model = HomeModel()
// 开合时只让同一个控制器重新布局
}
Le parole da tenere da questo capitolo
Non c'è ancora un'API Duo. Prima usa le parole giuste, altrimenti dopo le colleghi nel posto sbagliato.
Schermo esterno / schermo interno. Da chiuso guardi l'esterno, da aperto guardi l'interno. La scena principale cambia schermo da sola. Quando usarli: per descrivere l'hardware e, in verifica, per dire con chiarezza quale faccia stai misurando. Quando non usarli: non trattare le due facce come due tele che gestisci tu.
Postura. Chiuso, aperto, semi-piegato, moltiplicati per l'orientamento. Quando usarla: nel Device Hub le apri una per una e controlli che sia ancora la stessa interfaccia. Quando non usarla: non fare
switch poseper scegliere il layout.compact / regular. Lo schermo esterno si progetta in larghezza compact, lo schermo interno in larghezza regular. Quando usarli: per decidere se si vede un solo livello alla volta oppure due livelli insieme. Quando non usarli: non leggere regular come «questo è un iPad». Come si leggono è nel Capitolo 3.
Barra orizzontale / barra verticale. Lo schermo interno in verticale tiene le barre in alto e in basso. Nelle altre posture comuni, navigazione, toolbar e tab bar vanno di lato. Quando usarle: quando disegni le barre, disegnale in entrambi gli orientamenti. Quando non usarle: non disegnare una falsa barra laterale per imitarle.
Punto di controllo
Con lo schermo interno in verticale, le barre di sistema stanno in alto e in basso, oppure di lato?
Per sei posture, quante interfacce vanno costruite?
Dopo aver aperto il Duo, è diventata un'app per iPad?
In questo capitolo non farlo ancora. Non scrivere if isDuo, non leggere l'angolo della cerniera, non interrogare la piega. Adesso ti basta rifiutare «una UI per ogni postura».
Fonte: HIG · Designing for iPhone Duo, Tech Talk 111466.
Capitolo 1 · Con più larghezza mostra il livello successivo, non allungare le frasi
Dopo questo capitolo sai. Davanti a un'area che si allarga, decidere se aggiungere una colonna, aggiungere una colonna di griglia, oppure se stai solo stirando l'interfaccia stretta di prima. Sai anche dire che cosa può attraversare la piega e che cosa no.
Esempio che si rompe
La pagina delle impostazioni, sullo schermo esterno, è una colonna comoda. Aperto lo schermo interno, ogni riga della stessa lista si allunga fin quasi a tutto lo schermo. Titolo e interruttore finiscono alle due estremità, e in mezzo resta un grande vuoto. Un altro modo di romperla: mettere il pulsante principale, un codice QR o un volto esattamente al centro dello schermo. A metà piega la piega li taglia.
Quattro principi da portare in revisione
Funzione e stato non si interrompono. Quello che si può fare da chiuso resta disponibile da aperto. Scorrimento, selezione e contenuto in modifica non si azzerano perché il dispositivo si apre o si chiude. Quello che cambia è quanti livelli riesci a vedere insieme.
La larghezza in più serve a mostrare la gerarchia. La posta, sullo schermo esterno, è «lista, e la lettera si vede solo dopo il tap». Sullo schermo interno lista e lettera possono stare affiancate. È un livello in più che diventa visibile. Non è la riga della lettera che diventa larga 900 pt.
La piega è come il dorso di un libro. Sfondi, sfumature e articoli che scorrono possono attraversarla. Pulsanti, frasi, volti, codici QR e maniglie di trascinamento non devono cavalcare il centro. Questo capitolo costruisce solo questo giudizio. Come si interroga la piega è il Capitolo 7.
Il controllo sta vicino al contenuto su cui agisce. I pulsanti che governano la lista restano sopra la lista. Non spostarli tutti lontano solo perché di lato c'è una barra verticale.
Un gioco deve riempire la postura corrente. Prima cambia le proporzioni dell'immagine, invece di lasciare bande nere sopra e sotto oppure a sinistra e a destra. Le dimensioni fisiche di testo e pulsanti restano il più possibile stabili.
La stessa pagina di impostazioni, due larghezze
A un testo lungo e a un modulo dai un limite di lettura. Lo spazio in più resta per la gerarchia accanto, non per frasi più lunghe.
Form {
Toggle("提醒", isOn: $reminders)
Text("说明保持在容易扫读的宽度里。")
}
.frame(maxWidth: 700)
.frame(maxWidth: .infinity)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let readable = view.readableContentGuide.layoutFrame
formWidth.constant = min(readable.width, 700)
}
Se quest'area è già abbastanza larga da tenere insieme «lista» e «dettaglio», il capitolo successivo lo fa con la colonna di sistema. Adesso non scrivere a mano una barra laterale in HStack, e non ingrandire il corpo del testo di un giro solo perché c'è più spazio.
Domande da portare in revisione, non ancora API
Prima di allargare, chiedi. Navigazione persistente, dettaglio supplementare, inspector, anteprima, più controlli, densità di informazione più alta. Quando usarlo: quando passi da compact a molto largo, cerca in questo ordine la struttura che può restare visibile insieme. Quando non usarlo: non riempire il vuoto prima aumentando spaziature e corpo del testo.
Prova del dorso. Immagina al centro una fessura su cui non si riesce a toccare con precisione. Può attraversare: sfondo fotografico, texture, liste e articoli che scorrono in continuo. Non deve attraversare: pulsanti, frasi brevi che vanno lette per intero, volti, codici QR, campi di testo, icone piccole.
Raggruppa vicino. Le azioni frequenti restano accanto al blocco che influenzano. Quando usarlo: il filtro della lista resta sulla lista; la condivisione del dettaglio resta sul dettaglio. Quando non usarlo: non spostare l'azione principale in un angolo lontano dal contenuto solo per fare simmetria.
Il gioco riempie. Se cambia la postura, cambiano le proporzioni dell'immagine. Quando usarlo: puoi bloccare il verticale o l'orizzontale, ma la postura corrente deve essere piena. Quando non usarlo: non arrangiarti con le bande nere. Se proprio devi lasciare un bordo, riempilo con l'immagine, non con un colore piatto.
Punto di controllo
Sullo schermo interno stirare il modulo delle impostazioni fino a 900 pt di larghezza: dove sta il problema?
A metà piega, la sfumatura di sfondo e il pulsante «Paga»: quale dei due può restare al centro?
«Seleziona tutto» della lista messaggi deve seguire la lista, oppure la barra di sistema più a destra?
In questo capitolo non farlo ancora. Non rifare l'intera pagina per la mezza piega, e non scrivere adesso il codice che interroga la piega. Sul bozzetto ti basta segnare gli elementi che «non possono cavalcare il centro».
Capitolo 2 · Quale SDK usi per compilare decide quanto si riempie
Dopo questo capitolo sai. Distinguere la destinazione di deployment dall'SDK di compilazione, e capire perché un pacchetto che su Duo si apre già lascia comunque bande vuote e tiene la barra in alto.
Esempio che si rompe
Un pacchetto compilato con l'SDK iOS 26 si può installare. Aperto lo schermo interno, la finestra sta al centro e intorno resta vuoto. Da chiuso, il contenuto si ferma a sinistra della barra di stato e della fotocamera. Qualcuno allora scrive un'altra patch: «se è un Duo, stira il frame fino a riempire».
Tre fasce di SDK, nessun interruttore Duo separato
Riempire lo schermo e la barra verticale arrivano con l'SDK di compilazione, non con un booleano in Info.plist. La destinazione di deployment può restare su un sistema più vecchio, così i dispositivi precedenti si installano ancora. Ma se su Duo vuoi usare tutto lo schermo e far entrare le barre di sistema nella forma verticale, questo pacchetto va compilato con l'SDK iOS 27.1 o con uno più recente. Prima di spedire un pacchetto 27.1, verifica che il contenuto resti toccabile dopo che le barre sono diventate verticali.
| SDK usato per compilare | Schermo interno all'apertura | Barre |
|---|---|---|
| iOS 26 o precedente | Centrato, con vuoto intorno. Sullo schermo esterno il contenuto sta a sinistra della barra di stato e della fotocamera | Restano le tue barre orizzontali di prima |
| iOS 27 | Riempie gran parte dello schermo interno, ma evita ancora la barra di stato a destra | La toolbar resta in alto |
| iOS 27.1 o successivo | Usa tutta l'area di visualizzazione | Toolbar e tab bar si dispongono in una barra verticale sotto la barra di stato |
La destinazione di deployment può essere più bassa; le chiamate vanno protette
I due frammenti qui sotto non chiamano ancora nessuna API di piegatura. Costruiscono solo l'abitudine che ogni capitolo successivo deve rispettare: i simboli del 27.1 stanno dentro un controllo di disponibilità. Su Mac Catalyst, il problema noto di Xcode 27.1 è che questi simboli del 27.1 possono non compilare, e vanno separati ancora.
var body: some View {
Home()
.modifier(DuoReadyChrome())
}
struct DuoReadyChrome: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 27.1, *) {
content // 第 5 章才把垂直栏调优放进来
} else {
content
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 27.1, *) {
// 同一个控制器。不要在这里换 root。
}
}
#if !targetEnvironment(macCatalyst)
// 27.1 专有调用放这里,避免 Catalyst 编不过
#endif
Fatti legati alla compilazione
Destinazione di deployment
IPHONEOS_DEPLOYMENT_TARGET. Quando usarla: per decidere il sistema più vecchio su cui si può ancora installare. Quando non usarla: alzarla a 27.1 non è la condizione per riempire lo schermo. Il riempimento dipende dall'SDK di compilazione.SDK iOS 27.1. L'interruttore di schermo pieno più barra verticale. Quando usarlo: quando sei pronto a consegnare su Duo la forma completa, compila con questo SDK. Quando non usarlo: non spedire un pacchetto 27.1 prima di aver adattato la barra verticale.
#available(iOS 27.1, *). Se la destinazione di deployment è sotto 27.1, i simboli nuovi vanno avvolti. Quando usarlo: ogni chiamata alle API 27.1 dal Capitolo 5 in poi. Quando non usarlo: non avvolgerci le size class ordinarie. Quelle esistono già sui sistemi precedenti.Mac Catalyst
#if !targetEnvironment(macCatalyst). Quando usarlo: se un'API esclusiva del 27.1 impedisce la compilazione del target Catalyst, separa quelle chiamate. Quando non usarlo: non cancellare per questo l'adattamento su iPhone. L'accessorio fotocamera, di per sé, su Catalyst non è disponibile.
Punto di controllo
La destinazione di deployment è ancora iOS 18, ma compili con l'SDK 27.1. La barra verticale compare?
Il pacchetto parte già su Duo e intorno c'è vuoto. Il passo successivo è rilevare il modello e stirare, oppure cambiare SDK?
Perché una chiamata nuova del 27.1 non si può scrivere direttamente nel percorso di codice di iOS 18?
In questo capitolo non farlo ancora. Non leggere le dimensioni dello schermo per riempirlo. Il capitolo successivo sostituisce UIScreen.main. Anche le regole delle voci nella barra verticale non sono ancora arrivate.
Fonte: Three steps to make your app shine on iPhone Duo. Xcode 27.1 richiede macOS Tahoe 26.6 o successivo. Il simulatore, nel Device Hub, si apre, si chiude, ruota e si piega.
Capitolo 3 · Chiedi quanto è larga quest'area, non se è un Duo
Dopo questo capitolo sai. Trasformare i rami di layout fatti per dispositivo, orientamento e UIScreen.main in un ricalcolo basato su size class e sulla dimensione del contenitore corrente.
Esempio che si rompe
Lo stesso Duo può comparire su schermo esterno, a schermo interno intero e in Split View sullo schermo interno. Cambiano anche area sicura e barre. Leggere lo schermo una volta all'avvio: alla prima piega quel valore è già scaduto. Lo schermo interno ignora anche gli orientamenti che hai dichiarato in Info.
let tallPhone = UIScreen.main.bounds.height == 844
let scale = UIScreen.main.scale
if UIDevice.current.userInterfaceIdiom == .pad
|| horizontalSizeClass == .regular {
IPadOnlyLayout()
}
let window = UIWindow(frame: UIScreen.main.bounds)
if UIDevice.current.orientation == .landscape { wide() }
if view.window?.windowScene?.effectiveGeometry
.interfaceOrientation.isLandscape == true { wide() }
let width = view.bounds.width
- view.safeAreaInsets.left * 2
Due fatti facili da ricordare al contrario
La larghezza regular non è più sinonimo di iPad. Lo schermo interno è largo regular e alto regular. Il codice che un tempo diceva «se è regular, vai alla pagina esclusiva per iPad» adesso parte su iPhone. La richiesta di Apple è questa: lo schermo interno deve sembrare l'estensione naturale del tuo layout per iPad, quindi sistema quel layout. Non aggiungere un altro controllo sull'idiom per spegnerlo.
L'orientamento non è la forma. Per sapere se adesso è più largo che alto, oppure più alto che largo, confronta larghezza e altezza dell'area che possiedi. Lo schermo esterno in verticale è larghezza compact più altezza regular. Lo schermo esterno in orizzontale è compact in entrambe le direzioni. Lo schermo interno è regular in entrambe le direzioni. Da qui in poi un'app per iPhone può incontrare qualsiasi combinazione di size class.
Le dimensioni cambiano a metà sessione. Non annotarle una volta in viewIsAppearing per poi usarle fino alla fine.
Ricalcola sul contenitore
struct Stage: View {
@Environment(\.horizontalSizeClass) private var widthClass
@Environment(\.verticalSizeClass) private var heightClass
@Environment(\.displayScale) private var displayScale
@State private var size: CGSize = .zero
var body: some View {
content
.onGeometryChange(for: CGSize.self) { proxy in
proxy.size
} action: { newSize in
size = newSize
}
}
private var isWiderThanTall: Bool { size.width > size.height }
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let bounds = view.bounds
let wider = bounds.width > bounds.height
let scale = traitCollection.displayScale
let usable = bounds.inset(by: view.safeAreaInsets)
layout(for: usable.size, wider: wider, scale: scale)
}
override func viewWillTransition(
to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator
) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { _ in self.view.setNeedsLayout() }
}
let window = UIWindow(windowScene: scene)
Quando ti serve davvero UIScreen, prendilo in quel momento da view.window?.windowScene?.screen. Non salvarlo in una proprietà. La finestra si crea dalla scene. Non usare i bounds dello schermo principale come frame.
API
horizontalSizeClass. SwiftUI:@Environment(\.horizontalSizeClass). UIKit:traitCollection.horizontalSizeClass. Quando usarlo: per distinguere larghezza compact e regular, insieme alla size class verticale. Quando non usarlo: non tradurre.regularin «pagina per iPad».onGeometryChange. In SwiftUI, al cambiare della dimensione, ricevi ilCGSizedel contenitore. In UIKit corrispondonoviewDidLayoutSubviewseviewWillTransition(to:with:). Quando usarlo: per confrontare larghezza e altezza, o per una misura locale sul contenitore corrente. Quando non usarlo: non usarlo per leggereUIScreen.main.bounds.displayScale. SwiftUI:@Environment(\.displayScale). UIKit:traitCollection.displayScale. Quando usarlo: quando ti serve il rapporto in pixel. Quando non usarlo: non leggere piùUIScreen.main.scale. Il riferimento allo schermo principale verrà deprecato.UIWindow(windowScene:). Crea la finestra dalla scene. Quando usarlo: per sostituireUIWindow(frame: UIScreen.main.bounds). Quando non usarlo: il calcolo del layout guarda comunque i bounds della view o della window, non l'intero schermo fisico.
Punto di controllo
horizontalSizeClass == .regularadesso può essere vero anche su iPhone?Lo schermo interno è stato ruotato in orizzontale. Usare
interfaceOrientationper dire «adesso è più largo» è affidabile?Perché
safeAreaInsets.left * 2calcola male la larghezza utilizzabile?
In questo capitolo non farlo ancora. Non usare una soglia di larghezza if width > 700 per scambiare due alberi di navigazione. Il capitolo successivo risolve «stretto allora push, largo allora affiancato» con il contenitore di sistema.
Fonte: Step 2 di Three steps, e le resizing considerations in Preparing. Tech Talk 111461.
Capitolo 4 · Lascia che il contenitore di sistema decida se affiancare o fare push
Dopo questo capitolo sai. Tenere la selezione con un contenitore a colonne, decidere il numero di colonne di una griglia con la larghezza minima di un elemento, e rifare un arrangiamento locale in base a «quale disposizione ci sta», non in base a «su quale dispositivo sono».
Esempio che si rompe
In compact usi un NavigationStack. In regular usi una barra laterale scritta a mano con HStack. La selezione vive in due posti diversi. La griglia è scritta come «tre colonne su Duo, altrimenti una». Un gruppo di pulsanti, con if width > 700, si spezza in due viste completamente diverse tra orizzontale e verticale.
Ordine di preferenza
Se puoi usare un contenitore di navigazione di sistema, usa il contenitore. Per le carte ripetute usa una griglia adattiva. Per il caso locale «in orizzontale non ci sta, allora mettilo in verticale» usa un layout che prova lo spazio rimasto. La size class viene dopo. La geometria precisa resta per ultima. L'architettura dell'informazione della colonna è «collezione, selezione, dettaglio». Quando è stretto, la lista fa push del dettaglio. Quando è largo, stanno affiancati. La selezione deve sopravvivere a questo cambiamento. La larghezza in più serve a mostrare questo livello. Il testo lungo resta comunque a larghezza limitata.
Lista e dettaglio
NavigationSplitView {
List(items, selection: $selection) { item in
Text(item.title)
}
} detail: {
if let selection {
Detail(id: selection)
.frame(maxWidth: 700)
} else {
Text("选择一项")
}
}
let split = UISplitViewController(style: .doubleColumn)
split.setViewController(list, for: .primary)
split.setViewController(detail, for: .secondary)
// 选中项放在 list 与 detail 共用的 model 上
// preferredPrimaryColumnWidth 用 min / ideal / max
// 不要写死侧栏点数
Griglia di carte: larghezza minima, non numero di colonne
private let columns = [
GridItem(.adaptive(minimum: 220))
]
LazyVGrid(columns: columns, spacing: 12) {
ForEach(cards) { Card($0) }
}
func makeLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { _, env in
let width = env.container.effectiveContentSize.width
let count = max(1, Int(width / 220))
let item = NSCollectionLayoutItem(
layoutSize: .init(
widthDimension: .fractionalWidth(1 / CGFloat(count)),
heightDimension: .estimated(160)))
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: .init(
widthDimension: .fractionalWidth(1),
heightDimension: .estimated(160)),
subitem: item, count: count)
return NSCollectionLayoutSection(group: group)
}
}
Quando lo Split View ti taglia a una larghezza intermedia, il numero di colonne cade da solo a 2. Non salta di colpo da 3 a 1. Se a metà piega vuoi un numero pari di colonne, quello è il Capitolo 7. Adesso non scriverlo.
Cambia la disposizione dello stesso gruppo di viste, non la loro identità
ViewThatFits(in: .horizontal) {
HStack { Preview(); Inspector() }
VStack { Preview(); Inspector() }
}
let layout = isWiderThanTall
? AnyLayout(HStackLayout())
: AnyLayout(VStackLayout())
layout {
Chart()
Legend()
}
UIKit non ha ViewThatFits. L'idea corrispondente è: in fase di layout chiedi «in orizzontale ci sta ancora?», poi cambi l'asse dello stesso UIStackView. Non distruggere e ricreare i view controller figli, altrimenti il testo in modifica si perde.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let horizontal = stack.bounds.width
> preview.intrinsicContentSize.width
+ inspector.intrinsicContentSize.width
stack.axis = horizontal ? .horizontal : .vertical
}
API
NavigationSplitView. UIKit:UISplitViewController. Quando usarlo: collezione, selezione, dettaglio. Stretto allora push, largo allora affiancato. Quando non usarlo: non sostituirlo con una barra laterale inHStackscritta a mano. Non è l'arrangiamento a due contenuti del Capitolo 8.GridItem(.adaptive(minimum:)). UIKit: dividi la larghezza del contenitore per la larghezza minima di un elemento e ottieni il numero di colonne. Quando usarlo: carte, album, dashboard e altri contenuti ripetuti. Quando non usarlo: non scriverlo comeisDuo ? 3 : 1.ViewThatFits. Prova, lungo l'asse dato, quale sottoalbero ci sta. Quando usarlo: gruppi di pulsanti, metadati, anteprima più inspector, cioè scelte locali. Quando non usarlo: non usarlo per cambiare la navigazione dell'intera app.AnyLayout. Cambia il contenitore di layout e conserva l'identità delle viste figlie. Quando usarlo: grafico e legenda, anteprima e proprietà. Cambia la disposizione, ma sono ancora quelle due viste. Quando non usarlo: non riscrivere le viste figlie una volta in ciascun ramo di unif.
Punto di controllo
Quando da affiancato torni a una sola colonna, dove deve vivere la selezione?
Dopo lo Split View la larghezza basta solo per due carte da 220 pt. Da dove arriva il numero di colonne?
ViewThatFitsrisponde a «quale dispositivo», oppure a «quale disposizione ci sta»?
In questo capitolo non farlo ancora. Se la tab bar debba diventare navigazione laterale, resta per il capitolo successivo. Se due contenuti devono evitare la piega, resta per il Capitolo 8. Adesso non introdurre Arrangement.
Capitolo 5 · Le barre di sistema si mettono di lato da sole; una barra finta no
Dopo questo capitolo sai. Far riconoscere navigazione, toolbar e tab bar sia nella forma orizzontale sia in quella verticale, e affidare le azioni secondarie al menu di overflow di sistema.
Esempio che si rompe
In cima alla pagina c'è un HStack disegnato a mano, oppure una UINavigationBar, una UIToolbar o una UITabBar creata da sola con new. Il sistema non raccoglie questi contenuti nella barra verticale. Un altro problema frequente: il pulsante di condivisione ha solo il titolo "Share". La barra verticale mostra solo l'icona. Una voce senza icona non si mette in verticale. Se poi disegni tu un menu «…», compete per il posto con il menu di overflow di sistema.
Quando la barra è verticale, e chi ha il diritto di entrarci
Dopo la compilazione con l'SDK 27.1, lo schermo esterno fonde barra di navigazione, toolbar e tab bar in una sola striscia laterale. Lo fa anche lo schermo interno in orizzontale, e alcune viste che stanno in posizione leading o trailing. Lo schermo interno in verticale continua a usare barre orizzontali. Dentro una vista a colonne, solo la colonna di dettaglio partecipa alla barra verticale. La colonna laterale e la colonna di contenuto restano orizzontali. L'inspector resta orizzontale.
La barra si allinea al bordo hardware. Anche una lingua da destra a sinistra non la sposta sull'altro lato. In Split View, la barra dell'app a sinistra sta a sinistra e la barra dell'app a destra sta a destra.
Ogni voce deve avere insieme titolo e icona. Nella forma verticale si usa l'icona. Nella forma orizzontale l'icona ha la priorità, ma si può usare anche il titolo. Nel menu di overflow icona e titolo compaiono insieme. Una vista personalizzata, per impostazione predefinita, resta nella barra orizzontale, a meno che tu non dichiari che è adatta al posizionamento verticale. Un pulsante di solo testo resta nella barra orizzontale. Il pulsante Edit di sistema fa proprio questo: passa tra simbolo e testo.
In cima alla barra verticale resta il posto per Indietro o Chiudi, poi per un'azione evidente come Done. Le altre voci conservano il raggruppamento di prima. L'overflow, per impostazione predefinita, si raccoglie dal basso verso l'alto. Una pagina di navigazione protegge prima la tab bar. Una pagina in cui si compie un'azione protegge prima le voci della toolbar.
Lo stesso gruppo di azioni, utilizzabile in entrambe le barre
SwiftUI, appeso dentro un NavigationStack:
NavigationStack {
DetailView()
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Close", systemImage: "xmark") { dismiss() }
}
ToolbarItem(placement: .topBarPinnedTrailing) {
Button("Done", systemImage: "checkmark") { save() }
}
ToolbarItem(placement: .bottomBar) {
Button("Share", systemImage: "square.and.arrow.up") { share() }
}
.visibilityPriority(.high)
ToolbarOverflowMenu {
Button("Duplicate", systemImage: "plus.square.on.square") {
duplicate()
}
}
}
.toolbarVerticalCompressionBehavior(.automatic)
}
UIKit, appeso al view controller figlio dentro il navigation controller:
let close = UIBarButtonItem(
title: "Close", image: UIImage(systemName: "xmark"),
target: self, action: #selector(closeTapped))
let done = UIBarButtonItem(
title: "Done", image: UIImage(systemName: "checkmark"),
target: self, action: #selector(doneTapped))
let share = UIBarButtonItem(
title: "Share", image: UIImage(systemName: "square.and.arrow.up"),
target: self, action: #selector(shareTapped))
share.visibilityPriority = .high
share.axisBehavior = .automatic
navigationItem.leadingItemGroups = [
UIBarButtonItemGroup.fixedGroup(items: [close])
]
navigationItem.pinnedTrailingGroup = UIBarButtonItemGroup.fixedGroup(items: [done])
toolbarItems = [share]
navigationItem.verticalBarCompressionBehavior = .automatic
Il pulsante Indietro lo mette il navigation controller. La chiusura personalizzata va sul lato leading. Done va in pinnedTrailingGroup. Quando la ricerca occupa lo spazio, le voci fissate sono le ultime a entrare nell'overflow. additionalOverflowItems serve a raccogliere il tuo «altro» dentro il menu di sistema. I puntini di sospensione restano per l'overflow. Gli altri menu usano un simbolo diverso.
Una voce che passa avanti e indietro tra simbolo e testo si imposta su .horizontalOnly: quando non c'è una barra orizzontale si nasconde, invece di infilarsi nella barra verticale. Se una vista personalizzata ha davvero un disegno verticale, usa .verticalPreferred. Lo spazio flessibile, sull'asse verticale, ha dimensione 0. I gruppi usano ToolbarItemGroup / UIBarButtonItemGroup. Non inserire a mano una spaziatura fissa. Il badge usa badge. Non scrivere il numero nel titolo, altrimenti quella voce fa fatica a diventare un simbolo puro.
Uscire dalla barra verticale è un'eccezione stabile
NavigationStack {
CalculatorView()
.toolbarVerticalBehavior(.disabled)
}
override var preferredVerticalBarBehavior: UIVerticalBarBehavior {
.disabled
}
Il valore predefinito è .automatic. Esci solo in queste interfacce: video a schermo intero, un layout a tutta larghezza che non scorre come una calcolatrice, uno sheet con un solo pulsante di chiusura. È una scelta stabile. Non alternarla in viewWillAppear in base allo stato della pagina. Se vuoi solo nascondere la barra su una certa pagina, usa l'API di visibilità, non spegnere il comportamento verticale. In UIKit, se la preferenza è davvero cambiata, chiama setNeedsUpdateOfVerticalBarConfiguration(). Il navigation controller, per impostazione predefinita, affida la decisione alla cima dello stack. La tab bar la affida alla pagina selezionata.
Sapere da quale lato sta la barra, e come lo sfondo ci passa sotto
@Environment(\.toolbarVerticalEdge) private var verticalEdge
// HorizontalEdge?;这个上下文从不用垂直栏时为 nil
Image("hero")
.backgroundExtensionEffect()
let edge = traitCollection.verticalBarEdge
// .leading / .trailing / .unspecified
// 表示系统偏好的边,不表示栏此刻是否可见
// 从不用垂直栏的上下文返回 .unspecified
let backdrop = UIBackgroundExtensionView()
backdrop.contentView = hero
Per osservare questo bordo in UIKit: passa UITraitCollection.systemTraitsAffectingVerticalBarEdge a registerForTraitChanges. Anche se il trait cambia, il bordo può restare lo stesso.
Ingresso di primo livello: lo schermo interno può diventare navigazione laterale
Su iPhone, scrivere solo .sidebarAdaptable lascia ancora la barra in basso. Per passare alla navigazione laterale dove è supportata, aggiungi defaultTabBarPlacement(.sidebar) di iOS 27. Non usare defaultAdaptableTabBarPlacement di iOS 18: quello è per iPad. L'interfaccia legata alla barra laterale si guarda con isTabViewSidebarAvailable. Non indovinare più dalla size class.
TabView {
Tab("Home", systemImage: "house") { HomeView() }
Tab("Library", systemImage: "books.vertical") { LibraryView() }
}
.tabViewStyle(.sidebarAdaptable)
.defaultTabBarPlacement(.sidebar)
tabBarController.sidebar.preferredPlacement = .sidebar
Il prodotto può anche decidere in modo esplicito di tenere la barra in basso, e lasciare lo spazio allargato alle colonne dentro il contenuto. Entrambe le scelte reggono. Non disegnare tu una falsa barra laterale in base alla larghezza.
API
toolbarVerticalBehavior(iOS 27.1)..automatic/.disabled. UIKit: sovrascrivipreferredVerticalBarBehavior, tipoUIVerticalBarBehavior. Quando usarlo: calcolatrice, video a schermo intero, sheet con un solo pulsante di chiusura. Quando non usarlo: non alternarlo in base alla navigazione o allo stato di una vista.axisBehavior(iOS 27.1). SwiftUI:.automatic/.horizontalOnly/.verticalPreferred. UIKit:UIBarButtonItem.axisBehavior, gli stessi tre valori. Quando usarlo: horizontalOnly per le voci che passano tra simbolo e testo; verticalPreferred per una vista personalizzata che ha davvero un disegno verticale. Quando non usarlo: un pulsante ordinario con titolo e icona resta automatic.visibilityPriority(iOS 27.0). SwiftUI:.automatic/.low/.high, piùinit(lowerThan:)/init(higherThan:). UIKit:.standard/.low/.high. Quando usarlo: prima dai la priorità all'intero gruppo, poi alla singola voce dentro il gruppo. Le azioni frequenti e quelle con badge restano per ultime prima di andare in overflow. Quando non usarlo: non costruire tu un secondo menu «…».ToolbarOverflowMenueadditionalOverflowItemssono il menu di sistema. In UIKitadditionalOverflowItemsè disponibile da iOS 16.Comportamento di compressione (iOS 27.1). SwiftUI:
.automatic/.prefersToolbarItems/.prefersTabBar. UIKit:navigationItem.verticalBarCompressionBehaviorcon.automatic/.prefersBarItems/.prefersTabBar. Quando usarlo: per la navigazione usa il valore predefinito (prima si raccolgono le voci della toolbar, si protegge la tab). Per un compito, cambia in modo da raccogliere prima la tab bar. Quando non usarlo: non scrivere due set di barre per simulare la compressione.topBarPinnedTrailing(iOS 27.0). In UIKit la proprietà corrispondentepinnedTrailingGroupè disponibile da iOS 16. La chiusura usa.cancellationAction. In UIKit va inleadingItemGroups. Quando usarlo: per un'azione primaria come Done, che deve stare vicina a Indietro. Quando non usarlo: le azioni secondarie non si fissano qui. Devono poter andare in overflow.toolbarVerticalEdge(iOS 27.1). SwiftUI:HorizontalEdge?, nil quando questo contesto non usa mai la barra verticale. UIKit:traitCollection.verticalBarEdge, valori leading, trailing o unspecified. Riflette il bordo preferito, anche se la barra in questo momento non è visibile. Quando usarlo: quando un contenuto personalizzato deve allinearsi a questa barra. Quando non usarlo: non leggere unspecified come «la barra è appena stata nascosta». Significa che questo contesto non userà la barra verticale.backgroundExtensionEffect()(iOS 26). UIKit:UIBackgroundExtensionView. MetticontentViewnell'area sicura e riempi i bordi con l'estensione. Quando usarlo: un visual principale o uno sfondo deve passare sotto la barra verticale. Quando non usarlo: un primo piano toccabile non deve usarlo per uscire dall'area sicura.defaultTabBarPlacement(iOS 27.0)..sidebarAdaptablepiù.sidebar. UIKit:sidebar.preferredPlacement = .sidebar. Anche i valori d'ambienteisTabViewSidebarAvailableetabBarPlacementsono iOS 27. Quando usarlo: quando le sezioni di primo livello, su un layout largo, meritano una navigazione sempre presente. Quando non usarlo: non usaredefaultAdaptableTabBarPlacementper Duo. Quel modificatore esiste da iOS 18 e agisce su iPad.
Punto di controllo
Un pulsante che ha solo il titolo e non ha un'icona entra nella barra verticale?
Tra le tre colonne di una vista a colonne, la barra di quale colonna diventa verticale?
Su una pagina di navigazione, se lo spazio non basta, per impostazione predefinita si raccolgono prima le voci della toolbar o prima la tab bar?
Perché una
UIToolbardisegnata a parte non si mette di lato?
In questo capitolo non farlo ancora. Non interrogare la piega per decidere se la barra sta a sinistra o a destra. Il bordo della barra lo mette il sistema nel trait e nell'environment. Da quale lato sta lo sheet è il capitolo successivo.
Fonte: HIG · Vertical controls; Preparing · Optimize bars. Tech Talk 111462.
Capitolo 6 · I quattro bordi si calcolano separati; lo sheet dichiara a quale lato si attacca
Dopo questo capitolo sai. Scegliere la posizione di uno sheet, calcolare l'area utilizzabile con un'area sicura asimmetrica, e tenere il soggetto di un'immagine a tutta superficie con angoli concentrici allo schermo e con un punto di fuoco.
Esempio che si rompe
Usare l'inset sinistro moltiplicato per due come area sicura totale. Split View o barra verticale rendono sinistra e destra diverse, e il contenuto si sposta. Un'altra classe di problemi: costruire da soli una maschera a schermo intero e usarla come sheet. Sheet di sistema, avvisi, menu e popover evitano la piega. La tua maschera no. Il visual principale ritagliato con .fill: il volto visibile sullo schermo esterno, sullo schermo interno viene tagliato via.
La barra dello sheet segue il posizionamento
Sullo schermo esterno, uno sheet con toolbar usa per impostazione predefinita la barra verticale. Sullo schermo interno, lo sheet per impostazione predefinita è centrato e la barra è orizzontale. Sul lato leading non c'è barra verticale. Sul lato trailing c'è. Solo lo sheet rispetta il posizionamento. Non costruire per questo un'altra transizione personalizzata. Uno sheet con un solo pulsante di chiusura è il caso in cui il capitolo precedente permette di spegnere la barra verticale. Se è impostato sourceView, UIKit ignora preferredPlacement.
.sheet(item: $selection) { item in
ItemDetail(item: item)
.presentationPlacement(.trailing)
}
let sheet = detail.sheetPresentationController
sheet?.preferredPlacement = .trailing
// .automatic / .leading / .center / .trailing
// sourceView 非 nil 时,这个属性被忽略
I quattro bordi dell'area sicura sono indipendenti
content
.safeAreaPadding(.horizontal)
background
.ignoresSafeArea()
let usable = view.bounds.inset(by: view.safeAreaInsets)
// 分别使用 left、right、top、bottom
// 折痕不是 safe area inset
La decorazione può uscire dall'area sicura. Contenuto toccabile e testo che va letto restano dentro. Fotocamera, barra verticale e Split View rendono diversi sinistra e destra, oppure sopra e sotto. Una forma personalizzata che segue gli angoli dello schermo usa un rettangolo concentrico. Non scrivere il raggio a mano.
RoundedRectangle(cornerRadius: 12) // 不要写死去贴屏幕角
ConcentricRectangle()
view.cornerConfiguration = .corners(radius: .containerConcentric())
Un'immagine a tutta superficie deve tenere il soggetto
Image("cover")
.resizable()
.aspectRatio(contentMode: isWide ? .fit : .fill)
.frame(maxWidth: .infinity, maxHeight: isWide ? 420 : 280)
.clipped()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let wide = view.bounds.width > view.bounds.height
hero.contentMode = wide ? .scaleAspectFit : .scaleAspectFill
hero.clipsToBounds = true
}
Se il soggetto non sta nel centro geometrico, dai al ritaglio un punto di fuoco, invece di limitarti a passare tra fill e fit. Un gioco invece riempie e cambia le proporzioni: vedi il Capitolo 1.
API
presentationPlacement(iOS 27.0)..automatic/.center/.leading/.trailing. UIKit:UISheetPresentationController.preferredPlacement. Quando usarlo: ogni sheet va visto da chiuso, da aperto e a metà piega, e solo dopo decidi se sta sul bordo o al centro. Quando non usarlo: non applicare questo modificatore a popover e avvisi. Se è impostatosourceView, UIKit lo ignora.Inset asimmetrico.
bounds.inset(by: safeAreaInsets). Quando usarlo: ovunque calcoli a mano larghezza o altezza utilizzabile. Quando non usarlo: non specchiare l'inset di un lato sul lato opposto. Nemmeno la piega va infilata nell'area sicura.ConcentricRectangle(iOS 26). UIKit: raggio concentrico al contenitore diUICornerConfiguration. EsisteUIView.cornerConfiguration. Quando usarlo: un pannello personalizzato deve seguire gli angoli dello schermo o del contenitore corrente. Quando non usarlo: il piccolo raggio di una carta può ancora essere un raggio fisso. Non usarlo per evitare la piega.fill / fit. Scegli in base alle proporzioni correnti o alla size class, oppure imposta un punto di fuoco. Quando usarlo: visual principale e copertina video, dal piccolo schermo esterno al largo schermo interno, devono tenere il soggetto. Quando non usarlo: non scegliere
contentModeuna sola volta all'avvio.
Punto di controllo
Sullo schermo interno, la toolbar di uno sheet trailing è orizzontale o verticale?
Perché non si può ottenere la larghezza del contenuto con
left * 2?Lo sheet di sistema evita la piega. Una maschera a schermo intero disegnata da te lo fa?
In questo capitolo non farlo ancora. Quando un canvas personalizzato, un codice QR o un controllo di trascinamento devono evitare la piega, il capitolo successivo interroga le regioni riservate. Adesso non indovinare le coordinate della piega.
Capitolo 7 · Piega e fotocamera sono due regioni riservate, non l'area sicura
Dopo questo capitolo sai. In un layout personalizzato trovare la piega attiva e la fotocamera, e spostare solo il pezzo che è coperto, invece di rifare l'intera pagina.
Esempio che si rompe
Appena è piegato a metà, sostituisci tutto con un altro albero di interfaccia. Oppure scrivi a mano «la piega sta al 50% della larghezza». Dopo Split View e rotazione, quella percentuale non è più il centro della tua vista. L'altro estremo è far evitare la piega a ogni paragrafo di un articolo: il contenuto che scorre può già passare sopra quella fessura.
Due tipi di regione; i componenti di sistema, in gran parte, già le evitano
Una regione riservata è il rettangolo che qualcos'altro occupa nel tuo sistema di coordinate. frame include margins. Ogni regione ha un tipo, se è attiva, e un identificatore. La query restituisce le regioni che intersecano la vista corrente. Se la tua vista occupa solo metà dello schermo interno, non dare per scontato di vedere la piega dell'intero schermo.
division, divisione. A metà piega, la cerniera divide un grande pezzo in due lati. Quando è completamente disteso, questa regione non è attiva e la larghezza è 0.
occlusion, occlusione. La fotocamera frontale dello schermo esterno è sempre presente, e si allarga anche in Dynamic Island. La fotocamera frontale dello schermo interno di solito sta nascosta sotto il vetro. Solo mentre è in uso occupa un'area e fa spostare l'interfaccia.
Avvisi, menu, sheet e viste a colonne si adattano da soli. Canvas personalizzato, codice QR, volto, frase breve che deve restare continua, oggetto che stai trascinando: sono questi i casi in cui leggi questi rettangoli. Sposta solo l'elemento coperto, e porta con lui i controlli collegati. Non spedirlo all'altra estremità dello schermo. Articoli e liste che scorrono non si spostano per intero.
Prima filtra ciò che è in vigore, poi disponi il contenuto personalizzato
reservedRegions di SwiftUI accetta anche layoutDirectionBehavior:, con valore predefinito .mirrors. Passa .fixed solo se il mirroring lo hai fatto tu, altrimenti specchi due volte. La posizione fisica della fotocamera non si ribalta con la lingua. SwiftUI, per impostazione predefinita, specchia prima la geometria per te. Anche il proxy di onGeometryChange può interrogare.
GeometryReader { proxy in
let folds = proxy.reservedRegions(kind: .division)
.filter(\.isActive)
let cameras = proxy.reservedRegions(kind: .occlusion)
.filter(\.isActive)
CanvasPage(avoiding: folds + cameras)
}
override func layoutSubviews() {
super.layoutSubviews()
let folds = reservedRegions(kind: .division)
.filter(\.isActive)
let cameras = reservedRegions(kind: .occlusion)
.filter(\.isActive)
placeControls(avoiding: folds + cameras)
}
Una decisione di struttura come «numero pari di colonne» deve restare visibile anche quando il dispositivo è disteso in piano e la regione di divisione non è ancora attiva. Solo allora passi .includeInactive, e usi comunque isActive per decidere se spostare subito i controlli.
let divisions = proxy.reservedRegions(
kind: .division,
options: .includeInactive)
let evenColumns = !divisions.isEmpty
let divisions = reservedRegions(
kind: .division,
options: .includeInactive)
let evenColumns = !divisions.isEmpty
API
reservedRegions(kind:options:)(iOS 27.1). In SwiftUI sta suGeometryProxy. UIKit:view.reservedRegions(kind:options:), restituisce[UIView.ReservedRegion]. Quando usarlo: contenuto personalizzato che il contenitore di sistema non sposta per te. Quando non usarlo: liste ordinarie, viste a colonne, sheet, avvisi. Non trattare il risultato come un inset dell'area sicura.ReservedRegion(iOS 27.1).id,kind,frame,margins,isActive. I tipi sono.divisione.occlusion. Quando usarlo: disponi conframe.frameinclude già i margini. Quando non usarlo: una division non attiva non va usata per spingere via un pulsante. Da disteso in piano non deve cambiare il punto di tocco..includeInactive(iOS 27.1).ReservedRegion.QueryOptions. Quando usarlo: decisioni del tipo «se esiste una piega, la griglia usa un numero pari di colonne», indipendenti dal fatto che adesso sia piegato a metà. Quando non usarlo: non spostare in anticipo un pulsante solo perché hai trovato una regione non attiva.layoutDirectionBehavior..mirrorsoppure.fixed. Quando usarlo: tieni il valore predefinito, così le viste figlie in layout automatico e le regioni usano la stessa direzione. Quando non usarlo: passa.fixedsolo se specchi a mano.
Punto di controllo
Dopo che è completamente disteso, division è ancora attiva? Qual è la larghezza?
Un articolo che si può scorrere: bisogna spostare ogni paragrafo via dalla piega?
La fotocamera dello schermo interno occupa di solito un'occlusion?
In questo capitolo non farlo ancora. Contenuti in coppia, come un player più una playlist, nel capitolo successivo li affidi ad Arrangement. Non usare l'angolo della cerniera per decidere se evitare la piega.
Fonte: ReservedRegion; Preparing · Adapt to reserved regions. Tech Talk 111463.
Capitolo 8 · Quando due contenuti sono un'unica esperienza, il contenitore li scambia di posto
Dopo questo capitolo sai. Decidere se una coppia di viste usa split oppure overlay, e lasciare la navigazione fuori da questo contenitore.
Esempio che si rompe
Sostituire TabView o NavigationSplitView con Arrangement. Oppure mettere Arrangement dentro List, ScrollView o una colonna della vista a colonne: metà del contenuto non si riesce a scorrere e non si riesce a toccare.
Se già assomiglia a due stack, mappalo lì
Arrangement ha solo una vista primaria e una vista secondaria. Guarda lo spazio, il rapporto tra i lati, la size class e la regione di divisione. Non guarda se sei su un Duo. Il contenitore di navigazione sta fuori. L'esempio di Apple è proprio un Arrangement dentro uno stack di navigazione. Non annidare una vista a colonne dentro Arrangement.
split. Corrisponde al vecchio
HStack/VStack, o a un rapporto primario-secondario in cui nessuno dei due deve coprire l'altro. Quando è più largo che alto, si divide a sinistra e a destra. Quando è più alto che largo, si impila sopra e sotto. Quando compare la regione di divisione, regola i due lati ed evita la piega. Dopo averlo limitato a un solo asse, se quell'asse non si può usare, resta solo la vista primaria..split.axes(.horizontal), quando sull'asse orizzontale non ci sta, mostra solo il contenuto primario.overlay. Corrisponde al vecchio
ZStack. Quando non c'è una regione di divisione attiva, la vista primaria copre la secondaria (chiuso oppure completamente aperto). A metà piega, la vista primaria va sul lato posteriore o inferiore della piega, e la vista secondaria va sul lato anteriore o superiore.
Player e lista: split
NavigationStack {
ArrangementView {
Player()
} secondary: {
Queue()
}
.arrangementViewStyle(.split)
}
let arrangement = UIArrangementViewController()
arrangement.setViewController(player, for: .primary)
arrangement.setViewController(queue, for: .secondary)
arrangement.updateArrangement(.split)
navigationController.setViewControllers([arrangement], animated: false)
Solo sinistra e destra; in verticale resta solo il player:
.arrangementViewStyle(.split.axes(.horizontal))
arrangement.updateArrangement(.split.axes(.horizontal))
Controlli sopra l'immagine: overlay
ArrangementView {
TransportControls()
} secondary: {
Stage()
}
.arrangementViewStyle(.overlay)
arrangement.setViewController(controls, for: .primary)
arrangement.setViewController(stage, for: .secondary)
arrangement.updateArrangement(.overlay)
Ci sono ancora queste regolazioni di rapporto e di livello, tutte iOS 27.1: splitArrangementLayoutRatio, splitArrangementLayoutSize, splitArrangementFixedLayoutSize, overlayArrangementEdge. I valori d'ambiente splitArrangementAxis e overlayArrangementZIndex servono alle viste figlie per sapere in quale arrangiamento si trovano adesso. UIKit legge zIndex, splitAxis e isHidden con state(for:). L'intervallo di dimensioni delle viste primaria e secondaria sta in UISplitArrangement.ViewProperties. Il bordo di occlusione sta in UIOverlayArrangement.ViewProperties.edge.
API
ArrangementView(iOS 27.1).initriceve primary e secondary. UIKit:UIArrangementViewController. Le posizioni disetViewController(_:for:animated:)sono.primary/.secondary. Quando usarlo: player e coda, editor e inspector, canvas e proprietà. I due pezzi sono la stessa esperienza. Quando non usarlo: la navigazione di primo livello dell'app. Non metterlo dentro una vista a colonne, una lista o una vista a scorrimento..split(iOS 27.1).arrangementViewStyle(.split),.axes(.horizontal)oppure.vertical. UIKit:updateArrangement(.split.axes(...)). Quando usarlo: entrambi i lati hanno bisogno di uno spazio proprio, e nessuno copre l'altro. Quando non usarlo: i controlli devono già galleggiare sul contenuto. Quello è overlay..overlay(iOS 27.1). Senza regione di divisione, la vista primaria sta sopra. A metà piega, la vista primaria va sul lato posteriore o inferiore, e la secondaria sul lato anteriore o superiore. Quando usarlo: primo piano / sfondo, quando di solito è ammesso che si coprano. Quando non usarlo: la posta a due colonne. Quella è la vista a colonne del Capitolo 4, oppure lo split di qui.updateArrangement(_:animated:)(iOS 27.1). Cambia split / overlay, oppure cambia solo l'asse. Quando usarlo: il prodotto vuole bloccare in modo esplicito un asse, oppure passare tra i due stili. Quando non usarlo: non ricalcolare tu il frame a ogni rotazione per poi chiamarlo. Lo stile predefinito segue già il contenitore.
Punto di controllo
Prima era uno
ZStacke, a metà piega, deve dividersi sui due lati della piega. Usi split oppure overlay?Arrangement deve avvolgere la navigazione, oppure essere avvolto dalla navigazione?
Che succede se lo metti dentro uno
ScrollView?
In questo capitolo non farlo ancora. Non leggere l'angolo della cerniera per decidere tra split e overlay. L'angolo è un input di interazione del Capitolo 10.
Fonte: gli esempi SwiftUI / UIKit in Preparing; HIG · Arrangement views. Firme confrontate con ArrangementView e UIArrangementViewController dell'SDK iOS 27.1.
Capitolo 9 · Aprire e chiudere è un cambio di layout, non un riavvio dell'app
Dopo questo capitolo sai. Tenere navigazione, scorrimento, selezione, modifica e riproduzione sopra il layout, e conoscere i limiti di Split View e delle finestre nuove su Duo.
Esempio che si rompe
Da chiuso, la lista ha selezionato la terza lettera. Da aperto, un altro albero di viste riparte dalla prima, e anche la bozza è vuota. Un altro modo di romperlo: hai misurato solo lo schermo esterno a tutto schermo e lo schermo interno a tutto schermo. A metà Split View le barre si sovrappongono e il testo viene tagliato. Dai anche per scontato che «se è aperto, allora è per forza molto largo».
Una larghezza continua, più due limiti hardware
Sullo schermo interno tutte le app partecipano allo Split View, e anche un video può sovrapporsi a un'app. La barra verticale di ogni app sta sul proprio bordo esterno, quindi la tua barra può stare a sinistra oppure a destra. Vanno misurati entrambi i lati.
Un layout molto largo deve poter tornare a una larghezza intermedia, e poi tornare a compact. Lo stato — percorso di navigazione, scorrimento, selezione, editor, avanzamento della riproduzione, modulo non salvato — sta sopra il livello che decide se c'è una colonna sola o due.
Duo è il primo iPhone che supporta più scene. Un'app che su iPad apre una finestra nuova, qui lo fa anche. Una finestra nuova non si può creare sullo schermo esterno. Se la richiesta fallisce, resti nella finestra corrente. La closure di configurazione di UIWindowScene.ActivationAction deve restituire un ActivationConfiguration. Non modificare un oggetto di richiesta. Quando la piattaforma non supporta più finestre, segui l'alternate che hai fornito. Non dare per scontato che un tap sullo schermo esterno apra per forza una seconda finestra. UIRequiresFullScreen resta in vigore, ma all'apertura e alla chiusura del dispositivo l'interfaccia cambia comunque dimensione.
La selezione vive fuori dal layout
struct Mailbox: View {
@State private var path: [Message.ID] = []
@State private var selection: Message.ID?
@State private var draft = ""
var body: some View {
NavigationSplitView {
MessageList(selection: $selection)
} detail: {
Editor(text: $draft, messageID: selection)
}
}
}
let action = UIWindowScene.ActivationAction(
title: "新窗口",
image: UIImage(systemName: "plus.rectangle")
) { _ in
let activity = NSUserActivity(activityType: "mailbox.message")
return UIWindowScene.ActivationConfiguration(userActivity: activity)
}
La posizione di scorrimento usa il salvataggio della vista a scorrimento di sistema, oppure un identificatore tenuto nel model. Non fare scrollTo verso l'inizio quando si chiude. La riproduzione usa lo stesso oggetto player. Non fare replace quando cambia la dimensione.
Fatti da verificare
Sollevare lo stato. Selezione, percorso, bozza e player stanno fuori dal contenitore adattivo. Quando usarlo: qualsiasi pagina che passa tra compact e regular. Quando non usarlo: non creare un oggetto di stato per lo schermo esterno e un altro per lo schermo interno.
Split View. La barra sta sul bordo esterno di quell'app. La metà sinistra ha la barra a sinistra, la metà destra ha la barra a destra. Quando usarlo: nel Device Hub mettiti una volta a sinistra e una volta a destra. Quando non usarlo: non dare per scontato che la barra verticale stia sempre in trailing.
UIWindowScene.ActivationAction(iOS 15). La closure restituisceUIWindowScene.ActivationConfiguration(userActivity:). Quando la piattaforma non supporta più finestre, usaalternate. Quando usarlo: su iPad supporti già più finestre e vuoi la stessa possibilità sullo schermo interno di Duo. Quando non usarlo: non forzare una nuova scena sullo schermo esterno senza gestire il fallimento. Una finestra nuova non si può creare sullo schermo esterno.UIRequiresFullScreen. Il requisito di schermo intero viene ancora rispettato. Quando usarlo: se ti serve già lo schermo in esclusiva, tieni l'impostazione di prima. Quando non usarlo: non credere che impedisca il cambio di dimensione portato da apertura e chiusura. Il layout deve comunque seguirlo.
Punto di controllo
Dopo aver aperto lo schermo interno, da quale stato deve riprendersi la bozza?
Se l'app sta nella metà destra dello Split View, da quale lato è più probabile che stia la barra verticale?
Una finestra nuova si può creare sullo schermo esterno?
In questo capitolo non farlo ancora. Non usare l'angolo della cerniera per azzerare la navigazione. Apertura e chiusura sono già espresse da dimensioni e regioni riservate. L'angolo resta solo per un'interazione fisica vera.
Fonte: le HIG sulla continuità tra gli schermi e sulle barre esterne in Split View; i requisiti di test multi-dimensione in Preparing. Tech Talk 111464.
Capitolo 10 · L'angolo guida l'effetto, non le barre e il numero di colonne
Dopo questo capitolo sai. Quando c'è una cerniera, trasformare l'angolo in altezza di un suono, in un'inquadratura o in una meccanica di gioco, e tornare a un valore sicuro quando la cerniera non c'è oppure quando l'interazione esce dalla gerarchia.
Esempio che si rompe
if hinge.angle > someRadians { showSidebar = true }
Disteso in piano, in Split View e su un iPhone ordinario, l'angolo non arriva come ti aspetti. Se la barra laterale debba comparire, i Capitoli 3 e 4 hanno già risposto con lo spazio.
Il layout guarda lo spazio; l'interazione guarda la cerniera
L'angolo è adatto quando lo tratti come input fisico, come un meccanismo di gioco, come uno strumento musicale, come l'assetto di una fotocamera, come un'interazione da tavolo, o come un effetto legato in modo esplicito all'apertura e alla chiusura. Non è adatto per: barra laterale, numero di colonne, numero di celle di una griglia, se la navigazione è compressa.
In SwiftUI, DeviceHingeContext.hinge uguale a nil significa che questo dispositivo non ha una cerniera. Non significa «è chiuso». Chiuso è .closed. angle è un Angle. status è .closed, .partiallyOpen, .fullyOpen.
Il nil di UIKit ha un altro significato: quando l'interazione ha lasciato la gerarchia che può fornire aggiornamenti della cerniera, update.hinge è nil. L'angolo è in radianti. UIHinge.Status ha in più .unknown. Frequenza e precisione degli aggiornamenti le decide il sistema. Non dipendere da un certo frame rate. Se ti interessa solo aperto, semi-aperto o completamente aperto, leggi status.
A metà apertura l'angolo diventa l'altezza del suono; negli altri casi torna a zero
.onHingeChange { _, context in
if let hinge = context.hinge, hinge.status == .partiallyOpen {
pitch = bend(angle: hinge.angle)
} else {
pitch = 0
}
}
let interaction = UIHingeInteraction { [weak self] _, update in
guard let self else { return }
guard let hinge = update.hinge, hinge.status == .partiallyOpen else {
self.pitch = 0
return
}
self.pitch = self.bend(radians: hinge.angle)
}
view.addInteraction(interaction)
isEnabled di onHingeChange(isEnabled:_:) è true per impostazione predefinita. UIKit usa interaction.isEnabled: mentre è spento non chiama e non accoda gli aggiornamenti persi. Quando lo riaccendi, se la gerarchia corrente ha ancora una cerniera, consegna subito un'altra volta lo stato corrente. Questa closure è escaping. Usa [weak self] per evitare un ciclo di riferimenti.
API
onHingeChange(iOS 27.1).(isEnabled:_:). I parametri sono il vecchio e il nuovoDeviceHingeContext. Quando usarlo: l'effetto deve seguire in continuo la cerniera. Quando non usarlo: evitare la piega, le colonne, la griglia. Quelli usano le regioni riservate e Arrangement.DeviceHinge(iOS 27.1).statuspiùangle: Angle.hingesul contesto è opzionale. Quando usarlo: se è nil, porta l'effetto a zero. Non andare in crash. Quando non usarlo: non leggere nil come «è chiuso». Chiuso è.closed.UIHingeInteraction(iOS 27.1).init { interaction, update in }.update.hingeè unUIHingeopzionale.angleè in radianti. Lo stato include.unknown. Quando usarlo: aggiungilo alla vista che deve osservare la cerniera. L'esempio ufficiale faaddInteractioninviewDidLoad. Quando non usarlo: qui nil significa che l'interazione ha lasciato la gerarchia che fornisce gli aggiornamenti. Non significa per forza «questo telefono non ha una cerniera».Frequenza degli aggiornamenti. L'header lo scrive: ritmo e granularità cambiano con lo stato del sistema. Quando usarlo: tratta l'angolo come un input continuo e tollera i frame persi. Quando non usarlo: non usare «devono essere N volte al secondo» per una calibrazione o per un'animazione di layout.
Punto di controllo
Per decidere se la barra laterale compare, si può guardare
hinge.angle?In SwiftUI, che cosa significa
hinge == nil? E il nil di UIKit?Se vuoi solo sapere se è semi-aperto, leggi l'angolo oppure status?
In questo capitolo non farlo ancora. Non trattare lo schermo esterno come una seconda finestra che posizioni tu. Quello è il capitolo successivo, e vale solo durante la ripresa.
Capitolo 11 · Il contenuto extra sullo schermo esterno lo mostra il sistema, e solo durante la ripresa
Dopo questo capitolo sai. Spiegare come la scena principale si sposta tra i due schermi, e quando puoi mettere un contenuto supplementare sullo schermo esterno.
Esempio che si rompe
All'apertura spezzi l'interfaccia in due UIWindow: una disegna lo schermo interno, l'altra lo schermo esterno. Il sistema non lascia la tua seconda tela sulla faccia che si chiude. Nel simulatore non c'è nemmeno la fotocamera. Questo codice non si può verificare nel Device Hub.
L'esperienza principale segue l'unica scena principale
All'apertura e alla chiusura, la scena principale passa da sola sull'altra faccia. Tutti i pulsanti importanti restano sull'interfaccia principale. Il contenuto supplementare sullo schermo esterno può essere interattivo, ma deve essere poco.
L'unico modo supportato è l'accessorio di ripresa della fotocamera. Il sistema può mostrarlo sullo schermo esterno solo quando il dispositivo è aperto, l'app è in primo piano, lo schermo interno è a tutto schermo e c'è una sessione di ripresa in corso. Il sistema decide quando e dove, e può riprenderselo in qualsiasi momento. Per spegnerlo usa isEnabled. Non deregistrare per accendere e spegnere. Lo stato passa dallo stesso model.
Il simulatore non ha la fotocamera. Questa sezione vuole un dispositivo vero. Dopo apertura, chiusura e rotazione, la direzione della fotocamera può invertirsi. Quando scegli la fotocamera, fallo in base a «verso quale faccia è rivolta». Vedi Choosing a camera by the direction it faces.
ExternalNonInteractiveAccessory è il monitor esterno e AirPlay. Non è lo schermo esterno di Duo.
Gobbo: l'interfaccia principale può comunque scattare
CameraCaptureAccessory nell'SDK è segnato come non disponibile su macOS, tvOS, watchOS, visionOS e Mac Catalyst. sceneAccessory e registerSceneAccessory(_:) esistono da iOS 27.0. cameraCapture(sceneConfiguration:) e il ruolo di sessione .windowCameraCaptureAccessory sono iOS 27.1. Quando devi consegnare un contesto al delegato della scena, usa il metodo di fabbrica con userInfo e rileggilo in UISceneConnectionOptions.sceneAccessoryUserInfo.
CameraView(model: model)
.sceneAccessory {
CameraCaptureAccessory(isEnabled: $model.prompterEnabled) {
Prompter(model: model)
}
.onAvailabilityChange { model.prompterAvailable = $0 }
}
let configuration = UISceneConfiguration(
name: "Capture Accessory",
sessionRole: .windowCameraCaptureAccessory)
let accessory = UISceneAccessory.cameraCapture(
sceneConfiguration: configuration)
registration = registerSceneAccessory(accessory)
// 配件场景的 delegate 画提词器。
// 快门仍在主场景里。
API
CameraCaptureAccessory(iOS 27.1).init(isEnabled:content:), appeso dentrosceneAccessory.onAvailabilityChangedice se il sistema è ancora disposto a mostrarlo. Quando usarlo: gobbo, conto alla rovescia, far vedere l'inquadratura a chi viene ripreso. La registrazione sta sulla stessa vista dell'interfaccia di ripresa. Quando non usarlo: scatto, messa a fuoco e impostazioni sul percorso principale. Lo schermo esterno può sparire in qualsiasi momento.registerSceneAccessory(iOS 27.0). UIKit:UISceneAccessory.cameraCapture(sceneConfiguration:), ruolo.windowCameraCaptureAccessory. La registration restituita serve aunregisterSceneAccessory. Quando usarlo: una scena supplementare per la durata della sessione di ripresa. Quando non usarlo: non fare unregister per nasconderlo in modo temporaneo. Usa lo stato di abilitazione.ExternalNonInteractiveAccessory. Il monitor a cui sei connesso, oppure AirPlay. Quando usarlo: se già vuoi supportare un grande schermo esterno, e il contenuto non ha bisogno di interazione. Quando non usarlo: la faccia esterna di Duo quando è chiuso.Simulatore. Il Device Hub può aprire, chiudere, ruotare e piegare. Non ha la fotocamera. Anche la maggior parte delle estensioni di app non gira nel simulatore Duo. Quando usarlo: layout, barre, sheet, Split View. Quando non usarlo: non dichiarare, dal simulatore, che l'accessorio di ripresa è già stato verificato.
Punto di controllo
Quando non stai riprendendo, puoi fissare una dashboard sullo schermo esterno?
Quando il gobbo non è disponibile, dove deve stare lo scatto?
Perché questo pezzo non si può chiudere solo nel simulatore?
Fin qui. Se il prodotto non ha una fotocamera, in questo capitolo puoi non scrivere codice. I capitoli 0–9 coprono già il percorso principale, dal disegno al layout adattivo.
Fonte: Preparing · Improve your app’s camera handling. Tech Talk 111465.
Appendice · Confronti, segnali d'allarme e risposte
I dodici capitoli precedenti costruiscono il giudizio in ordine. Qui stanno insieme i tre strati di API, le scritture che non devono comparire e le risposte ai punti di controllo, così dopo un capitolo puoi tornare a confrontare.
Tre strati, non saltarli
| Strato | Quando lo scrivi | SwiftUI | UIKit |
|---|---|---|---|
| 1 · Qualsiasi dimensione | Capitoli 3–6. Ne beneficiano tutte le finestre Apple | NavigationSplitView, TabView, GridItem(.adaptive), ViewThatFits, AnyLayout, size class, onGeometryChange, .toolbar di sistema |
UISplitViewController, UINavigationController, UITabBarController, larghezza del contenitore, trait, layoutSubviews, viewWillTransition |
| 2 · Conosci piega e barra verticale | Nei capitoli 5–8, quando il contenitore di sistema non basta | reservedRegions, ArrangementView, toolbarVerticalBehavior, axisBehavior, visibilityPriority, presentationPlacement, toolbarVerticalEdge |
reservedRegions(kind:options:), UIArrangementViewController, preferredVerticalBarBehavior, axisBehavior, verticalBarEdge, preferredPlacement |
| 3 · Solo l'hardware stesso | Capitoli 10–11, quando il prodotto ne ha davvero bisogno | onHingeChange, CameraCaptureAccessory, sceneAccessory |
UIHingeInteraction, UISceneAccessory.cameraCapture, ruolo .windowCameraCaptureAccessory |
Le regole delle voci nella barra verticale stanno nel Capitolo 5, perché l'SDK 27.1 mette in piedi le barre di sistema, non perché tu debba interrogare la piega. L'interrogazione della piega comincia solo dal Capitolo 7.
Disponibilità, per non confondere le date
| Sistema | Quello che compare solo a questa fascia |
|---|---|
| iOS 26 | ConcentricRectangle, backgroundExtensionEffect(), UIBackgroundExtensionView, UICornerConfiguration |
| iOS 27.0 | visibilityPriority, ToolbarOverflowMenu, topBarPinnedTrailing, presentationPlacement, defaultTabBarPlacement, isTabViewSidebarAvailable, sceneAccessory / registerSceneAccessory |
| iOS 27.1 | Comportamento e bordo della barra verticale, axisBehavior, comportamento di compressione, regioni riservate, Arrangement, cerniera, CameraCaptureAccessory |
In UIKit, leadingItemGroups, pinnedTrailingGroup e additionalOverflowItems sono disponibili da iOS 16. Su Duo governano ordine e overflow dentro la barra verticale, ma le proprietà stesse non nascono con il 27.1.
La priorità di visibilità in SwiftUI è .automatic / .low / .high. I valori corrispondenti in UIKit sono .standard / .low / .high. Anche il comportamento di compressione differisce di un nome: SwiftUI ha .prefersToolbarItems, UIKit ha .prefersBarItems.
Appena apri un file, cerca prima questi
Se li usi per un layout ordinario, vanno cambiati:
UIScreen.main.bounds,UIScreen.main.scale, unUIScreensalvatoUIWindow(frame: UIScreen.main.bounds)userInterfaceIdiom == .padoppure.phoneUIDevice.current.orientation,statusBarOrientation,interfaceOrientation, compresoeffectiveGeometry.interfaceOrientationhorizontalSizeClass == .regularche entra in una pagina «solo iPad»bounds.height == 844oppure una tabella di altezze di modelli notisafeAreaInsets.left * 2if isDuo,if isFolded, usare l'angolo della cerniera per decidere la barra laterale o il numero di colonneun frame grande scritto a mano, una larghezza di barra laterale scritta a mano, un numero di colonne di griglia scritto a mano
una barra superiore disegnata a mano, oppure
UIToolbar/UINavigationBar/UITabBarcreate da solepulsanti di toolbar con solo il titolo e senza icona; un menu «…» disegnato da te
contenuto importante centrato e premuto sulla piega; uno stato per compact e un altro per lo stato aperto
un visual principale in
.fillsenza punto di fuoco, e senza una scelta tra fill e fit al cambiare delle proporzionitoolbarVerticalBehavior(.disabled)fuori dalle eccezioni scritte nel testo, oppure alternarlo in base allo stato della pagina
Posture da aver visto almeno una volta nel Device Hub
| Postura | Atteso |
|---|---|
| Chiuso, verticale | Schermo esterno, larghezza compact + altezza regular, barra verticale |
| Chiuso, orizzontale | Doppio compact, barra verticale |
| Aperto, verticale | Schermo interno regular × regular, barre orizzontali. È l'unica postura comune che tiene le barre in alto e in basso |
| Aperto, orizzontale | regular × regular, barra verticale |
| Semi-piegato | La stessa gerarchia. Il contenuto preciso non cavalca la piega. Il contenuto che scorre non viene dirottato per l'intera pagina |
| Split View sullo schermo interno, tu a sinistra e tu a destra | La barra sta sul proprio bordo esterno. Lo stato è ancora lì |
Ogni sheet e ogni popover va aperto una volta in queste posture. Gli screenshot per lo store seguono le Screenshot specifications di App Store Connect. Un'organizzazione frequente è schermo esterno 1398×2034 e schermo interno 2007×2853; in orizzontale si scambiano i due lati. Questo gruppo di numeri non va scritto nel layout. Se un materiale dice «dal aprile 2027 l'invio richiede screenshot Duo», la data esatta è quella dell'annuncio Apple di quel momento.
Risposte ai punti di controllo
Capitolo 0. Le barre di sistema dello schermo interno in verticale stanno in alto e in basso. Sei posture restano una sola interfaccia. Dopo l'apertura è ancora un'app per iPhone. Lo schermo interno è diventato regular × regular.
Capitolo 1. Un modulo di impostazioni largo 900 pt ha stirato le frasi. Etichette e interruttori sono stati separati dal vuoto. Va limitata la larghezza, e lo spazio in più va lasciato al livello successivo della gerarchia. La sfumatura può restare al centro. Il pulsante di pagamento no. «Seleziona tutto» della lista segue la lista, non la barra di sistema più a destra.
Capitolo 2. La destinazione di deployment può restare iOS 18. Dopo la compilazione con l'SDK 27.1, la barra verticale compare. Se intorno resta vuoto, cambia l'SDK di compilazione. Non rilevare il modello per stirare il frame. I simboli nuovi non esistono sui sistemi vecchi, quindi vanno dentro #available.
Capitolo 3. La larghezza regular è vera sullo schermo interno di Duo, quindi non è più uguale a iPad. Lo schermo interno ignora la dichiarazione di orientamento. Se è largo o alto si decide confrontando larghezza e altezza del contenitore. Gli inset sinistro e destro possono essere diversi. left * 2 sbaglia l'altro lato.
Capitolo 4. La selezione sta nella stessa copia di stato, fuori dalla vista a colonne. Il numero di colonne è la larghezza del contenitore divisa per la larghezza minima di un elemento. Qui cade a 2. ViewThatFits risponde a quale disposizione ci sta.
Capitolo 5. Un pulsante con solo il titolo non entra nella barra verticale. In una vista a colonne, solo la barra della colonna di dettaglio è verticale. Una pagina di navigazione, per impostazione predefinita, raccoglie prima le voci della toolbar nell'overflow e protegge la tab bar. Una UIToolbar creata da sola non è una barra fornita dal contenitore di navigazione. Il sistema non la mette in piedi.
Capitolo 6. Sullo schermo interno, la toolbar di uno sheet trailing è verticale. Le aree sicure sinistra e destra sono asimmetriche e non si specchiano. Una maschera a schermo intero disegnata da te non evita la piega come fa lo sheet di sistema.
Capitolo 7. Dopo che è completamente disteso, division non è attiva e la larghezza è 0. Un articolo che si può scorrere non va spostato paragrafo per paragrafo. La fotocamera dello schermo interno di solito non occupa un'occlusion. La occupa solo mentre è in uso.
Capitolo 8. Uno ZStack si mappa su overlay. Arrangement è avvolto dalla navigazione. Non il contrario. Metterlo in uno ScrollView rende uno dei due pezzi irraggiungibile.
Capitolo 9. La bozza si riprende dalla stessa copia di stato, non da un altro albero di viste. La barra verticale della metà destra dello Split View sta a destra, cioè sul bordo esterno di quell'app. Una finestra nuova non si può creare sullo schermo esterno.
Capitolo 10. La barra laterale non guarda l'angolo della cerniera. Il nil di SwiftUI significa che questo dispositivo non ha una cerniera. Il nil di UIKit significa che l'interazione ha lasciato la gerarchia che può fornire aggiornamenti. Se vuoi solo sapere se è semi-aperto, leggi status.
Capitolo 11. Quando non stai riprendendo, non puoi fissare una dashboard sullo schermo esterno. Lo scatto resta sull'interfaccia principale. Il simulatore non ha la fotocamera. L'accessorio di ripresa vuole un dispositivo vero.
Ingressi ufficiali
ReservedRegion, e
UIView.ReservedRegiondi UIKitArrangementView, e
UIArrangementViewControllerTech Talks: 111466 progettazione, 111461 preparazione, 111462 barre verticali, 111463 posture e Arrangement, 111464 più schermi e scene, 111465 fotocamera
La pagina Preparing elenca anche WWDC 2026 · 278 e WWDC 2025 · 282
Buone pratiche · Gli ingressi in basso restano fermi, la larghezza va al contenuto
Dopo aver letto sai. Confrontare questa app con le regole precedenti, e distinguere che cosa è già atterrato nel modo di Apple, che cosa è una scelta di prodotto, e che cosa non si può ancora dare per verificato.
Questa app non ha una versione Duo. La destinazione di deployment è ancora iOS 18. Solo compilando con l'SDK 27.1 si riempie lo schermo e le barre di sistema possono diventare verticali. Apertura e chiusura non cambiano la route e non cambiano il model della pagina. La larghezza regular in più sta nelle colonne sinistra e destra interne di «Oggi» e di «Respira», non nel trasformare i tre ingressi di primo livello in una barra laterale. Angolo della cerniera, schermo esterno della fotocamera e più finestre non hanno una ragione di prodotto, quindi non si scrivono.
Confronto
| Giudizio del tutorial | Come fa questa app |
|---|---|
| Capitoli 0–2: una sola interfaccia, l'SDK è l'interruttore | Nessuna biforcazione sul nome del dispositivo. Le API nuove sono avvolte da #available. Sui sistemi vecchi resta un HStack / VStack della stessa relazione |
| Capitoli 3–4: in base alla larghezza del contenitore si mostra il livello successivo | Un guscio compact, per quanto si allarghi, resta una sola colonna. In regular, a 800 pt è ancora una sola colonna. Da 801 pt in poi si affiancano sinistra e destra. Solo quando ci stanno la colonna principale massima da 480 pt più il dettaglio leggibile da 720 pt (totale 1201 pt) si entra nella fascia più ariosa. A 1024 pt è ancora affiancato. Non è una frase stirata |
| Capitolo 5: barre di sistema, ma la barra laterale non è obbligatoria | .sidebarAdaptable più defaultTabBarPlacement(.tabBar). I tre Tab restano in basso |
| Capitolo 6: lo sheet dichiara la posizione; una maschera disegnata a mano non evita la piega | Lo sheet di sistema, in regular, segue l'intenzione e va in trailing oppure in center. Lo strato di pagamento in compact è ancora una maschera sulla radice e non evita la piega da solo |
| Capitoli 7–8: si spostano solo i due contenuti in coppia | Respirazione, barra orizzontale del pagamento e colonne primaria e secondaria di uguale larghezza usano lo split di ArrangementView. La home e il dettaglio che scorrono non cambiano percorso per la piega |
| Capitoli 9–11: stato continuo; le API hardware sono facoltative | Selezione e route stanno fuori dal layout. Non si fanno onHingeChange, accessorio di ripresa e più finestre |
La navigazione radice tiene tre ingressi
Lo schermo interno può diventare una barra laterale. Questa scelta di prodotto non lo fa. «Oggi», l'andamento e la respirazione restano Tab in basso. Le impostazioni si raggiungono con un push da «Oggi». Il percorso iPad usa defaultAdaptableTabBarPlacement(.tabBar). Su iOS 27 si dichiara di nuovo defaultTabBarPlacement(.tabBar). Dopo l'apertura dello schermo interno, lo spazio in più compare dentro la pagina: Oggi ha le carte a sinistra e il dettaglio a destra, la respirazione ha il modo a sinistra e l'esercizio a destra.
L'affiancamento deve soddisfare due condizioni insieme: il guscio di questa pagina è regular, e anche la size class orizzontale corrente è regular. Così l'albero stretto non ruba la selezione condivisa. Se il guscio è compact, anche se il contenitore è molto largo, la fascia resta una sola colonna.
TabView(selection: adaptiveSectionSelection) {
// 三个 Tab
}
.tabViewStyle(.sidebarAdaptable)
.defaultAdaptableTabBarPlacement(.tabBar)
// iOS 27:content.defaultTabBarPlacement(.tabBar)
adaptiveShellRole == .regular && horizontalSizeClass == .regular
Arrangement descrive solo una relazione che esiste già
La navigazione resta fuori. Il layout delle versioni precedenti e l'Arrangement del 27.1 sono la stessa relazione, non un'altra pagina.
| Scenario | Relazione di prima | 27.1 |
|---|---|---|
| Esercizio di respirazione | Il visual principale sta sopra, i controlli stanno sotto | .split. Alto allora sopra e sotto, largo allora sinistra e destra, ed evita la piega. Non blocca l'asse e non guarda l'angolo della cerniera |
| Pagina di pagamento in orizzontale | Visual principale e acquisto affiancati | .split. Sui sistemi vecchi resta un HStack |
| Colonne primaria e secondaria di uguale larghezza | Entrambe le colonne hanno bisogno di uno spazio proprio | .split.axes(.horizontal), rapporto 0.5. Lo sfondo può attraversare la piega. Il contenuto toccabile sta dentro Arrangement |
Quello che fa il sistema, e quello che non si può ancora dare per finito
L'area sicura si legge sui quattro bordi. Le immagini dentro liste e dettagli che scorrono non si spostano per la mezza piega. Sheet di sistema: in compact va .automatic, in regular si sceglie trailing oppure center secondo l'intenzione.
In modo esplicito non si fa: non si usa l'angolo della cerniera per disporre la respirazione o la barra laterale. Non c'è una fotocamera, quindi non si registra un accessorio di ripresa. Non si crea una finestra nuova sullo schermo esterno. Non si duplica il model della pagina per sei posture. Si torna a considerare solo se il prodotto deve davvero trattare apertura e chiusura come input, oppure se compare un'interfaccia di ripresa.
Non si può ancora dare per finito: le sei posture vanno ancora viste con le proprie mani nel Device Hub. I comandi pubblici del simulatore non aprono, non chiudono e non piegano a metà. La maschera di pagamento in compact non è uno sheet di sistema. Per uno sheet di spiegazione con un solo pulsante di chiusura, se spegnere la barra verticale è ancora da decidere. La barra superiore fissa usata dalla transizione di elementi condivisi resta ancora fuori dal livello di scala. Compilare con successo non significa aver visto piega e barra verticale.
Come si aggancia una pagina nuova. Prima mettila in un Tab esistente o nello stack di navigazione, e lascia lo stato fuori. Poi, in base alla fascia del contenitore, decidi una colonna sola oppure l'affiancamento. Il testo lungo si ferma intorno a 720 pt. Arrangement si usa solo quando due contenuti devono già scambiarsi di posto, e con #available(iOS 27.1, *) si lascia il layout vecchio. Prima si scrivono toolbar di sistema, size class e larghezza di lettura. Dopo si scrivono regioni riservate e Arrangement. Cerniera e schermo secondario continuano a non scriversi, finché il prodotto non ne ha bisogno.
Prova su questa app
Dopo che lo schermo interno si allarga, i tre Tab devono salire a barra laterale, oppure restare in basso?
Quando un contenitore regular ha solo 800 pt, «Oggi» deve essere una sola colonna oppure affiancato?
Sfera e controlli dell'esercizio di respirazione: perché usano
.splite nononHingeChange?Se la maschera di pagamento in compact, a metà piega, preme sulla piega, si può dire «lo sheet di sistema la evita»?
Risposte. I tre Tab restano in basso. A 800 pt è ancora una sola colonna. L'affiancamento comincia a 801 pt. Sfera e controlli sono il rapporto sopra-sotto dello stesso esercizio: si affidano a split. L'angolo non decide il layout. La maschera di pagamento non è uno sheet di sistema e non evita la piega da sola.
Continua a leggere
Prova queste idee in Stress HRV
Scarica dall'App StoreQueste note condividono pensiero di prodotto per il benessere. Non sono consiglio medico.