iPhone Duo için arayüz: yerleşimden koda
On iki bölüm, aynı durum için SwiftUI ve UIKit. Her bölümde doğrulanabilir bir yargı, sonda sekmelerin altta kalma nedeni.
Önce arayüzü her genişlikte düzgün görünecek biçimde kur, katlanan donanımı ancak ondan sonra artı puan olarak ekle. iPhone Duo'nun bir dış ekranı, bir iç ekranı ve bir menteşesi vardır. Bu yeni bir idiom değildir ve ikinci bir uygulama doğurmamalıdır. Aşağıdaki metin öğrenme eğrisine göre yazıldı: her bölüm, doğrulayabileceğin tek bir yargı teslim eder. Önce bozulan yazımı gör, sonra neden böyle tasarlandığını oku, ardından aynı sahne için hem SwiftUI hem UIKit karşılığını al.
İmzalar Xcode 27.1 ile gelen iOS 27.1 SDK'ya göre kontrol edildi. Davranış Designing for iPhone Duo, Preparing your app for iPhone Duo ve Three steps belgelerinden gelir. Gövde bir aktarımdır; özgün metnin birebir kopyası değildir.
Nasıl okunur: her bölüm önce «bitirdiğinde şunları yapabilirsin» ile açılır; ardından bozulan örnek, doğru yazım ve «ne zaman kullanılır / ne zaman kullanılmaz» gelir. Kontrol noktalarını önce kendin yanıtla. Yanıtlar metnin sonundadır. Bölüm 0–6 kat çizgisini sorgulamaz, Arrangement kullanmaz ve menteşe açısını okumaz. Bunlar sonraki bölümlerin işidir.
Bölüm 0 · Altı duruş kabul ölçütüdür, altı ayrı arayüz değil
Bitirdiğinde şunları yapabilirsin. Duo kapalıyken, açıkken, yarı katlıyken ve dönerken arayüzün hangi genişliğe göre düşünmesi gerektiğini söyleyebilirsin. Her duruş için ayrı bir sürüm yazmak yerine genişliği temel alırsın.
Bozulan örnek
Ürünü şöyle bölme: kapalı dikey, kapalı yatay, açık dikey, açık yatay, kitap gibi yarı katlı ve masada dik duran yarı katlı. Her biri için ayrı bir görünüm ağacı kurma. Bir kez açıp kapatınca gezinme değişir, düğmeler değişir, kaydırma konumu kaybolur.
SwiftUI'de duruşa göre dallanma:
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'te duruşa göre dallanma:
func installRoot(for pose: String) {
let root: UIViewController
switch pose {
case "closed": root = PhoneHomeController()
case "open": root = TabletHomeController()
default: root = BookHomeController()
}
// 三套控制器,三份选中项
}
Apple neden yalnızca iki genişlik veriyor
Duo'nun bir dış ekranı ve bir iç ekranı vardır. Menteşe kapanabilir, tamamen açılabilir ya da ortada durabilir. İnsan cihazı dik tutar, yatay tutar, kitap gibi açar ya da masaya diker. Bunların hepsi aynı kullanım içindeki kesintisiz değişimlerdir. HIG'in istediği şudur: dış ekranda compact genişlik, iç ekranda regular genişlik kullan ve mevcut yerleşimin kullanılabilir alanla birlikte büyümesine izin ver. Her tutuş için uygulamayı baştan tasarlama. Hâlâ bir iPhone tasarlıyorsun. iOS'un yerleşim, çubuk ve güvenli alan kuralları yürürlükte kalır.
| Gördüğün | Ne olduğu | Tasarlarken nasıl düşünülür |
|---|---|---|
| Kapalı | Dış ekran. Yaygın iPhone modellerinden daha geniş ve daha alçak | Compact genişlik. Dikey duruşta yükseklik yine regular kalır |
| Tamamen açık | Büyük iç ekran, regular × regular | iPad yerleşiminin doğal uzantısı gibi durur, ama yine bir iPhone uygulamasıdır |
| Yarı katlı | İç ekran kat çizgisiyle iki yana ayrılır | Aynı hiyerarşi sürer. Bu bölüm kat çizgisini henüz ele almaz |
| Dönüş ve bölünmüş ekran | Aynı cihaz üzerindeki farklı kaplar | Genişlik ara değerlerden geçer. Yalnızca «dış ekran / tam iç ekran» diye iki kademe yoktur |
Çubukların yönü için şimdilik tek cümle yeter: iç ekranın dikey duruşu, üstte ve altta yatay çubukları koruyan tek duruştur. Dış ekranda ve iç ekranın yatay duruşunda sistem çubukları kenara taşınır. Bölünmüş ekranda her tarafın çubuğu kendi dış kenarına yapışır. Bu ayrıntılar Bölüm 5'te koda dökülür.
Doğru düzenleme
Tek bir arayüz. Durum, yerleşimin dışında durur. Genişlik değişince yalnızca diziliş değişir; veri değişmez.
struct Root: View {
@State private var selection: Item.ID?
var body: some View {
Home(selection: $selection)
}
}
final class RootController: UIViewController {
let model = HomeModel()
// 开合时只让同一个控制器重新布局
}
Bu bölümde akılda tutulacak terimler
Henüz bir Duo API'si yok. Terimleri doğru kullanırsan sonraki bölümlerde yanlış yere bağlanmazsın.
Dış ekran / iç ekran. Kapalıyken dış ekrana, açıkken iç ekrana bakarsın. Ana sahne ekranı kendisi değiştirir. Ne zaman kullanılır: donanımı anlatırken ve kabul testinde hangi yüze baktığını söylerken. Ne zaman kullanılmaz: iki yüzü, senin yönettiğin iki ayrı tuval sanma.
Duruş. Kapalı, açık ve yarı katlı; bunların her biri yönle çarpılır. Ne zaman kullanılır: Device Hub'da tek tek açıp aynı arayüzün hâlâ orada olup olmadığına bakarken. Ne zaman kullanılmaz: yerleşim seçmek için
switch poseyazma.compact / regular. Dış ekranı compact genişliğe, iç ekranı regular genişliğe göre tasarla. Ne zaman kullanılır: «bir kerede tek katman» mı yoksa «iki katman aynı anda» mı görüleceğine karar verirken. Ne zaman kullanılmaz: regular değerini «bu bir iPad» diye okuma. Okuma biçimi Bölüm 3'tedir.
Yatay çubuk / dikey çubuk. İç ekranın dikey duruşu üst ve alt çubukları korur. Diğer yaygın duruşlar gezinme çubuğunu, araç çubuğunu ve sekme çubuğunu kenara toplar. Ne zaman kullanılır: çubuk çizerken iki yönü de bir kez çiz. Ne zaman kullanılmaz: bunu taklit etmek için sahte bir kenar çubuğu çizme.
Kontrol noktası
İç ekran dikeyken sistem çubukları üstte ve altta mı durur, yoksa yanda mı?
Altı duruş için kaç arayüz yapılmalıdır?
Duo açıldıktan sonra bir iPad uygulamasına mı dönüşür?
Bu bölümde henüz yapılmayacaklar. if isDuo yazma, menteşe açısını okuma, kat çizgisini sorgulama. Şimdilik yapman gereken tek şey «her duruş için bir UI» fikrini reddetmektir.
Kaynak: HIG · Designing for iPhone Duo, Tech Talk 111466.
Bölüm 1 · Genişleyince sonraki katmanı göster, cümleyi ekrana yayma
Bitirdiğinde şunları yapabilirsin. Genişleyen bir bölge eline geçtiğinde bir sütun mu ekleyeceğine, bir kolon mu ekleyeceğine, yoksa eski dar arayüzü yalnızca gerecek misin karar verebilirsin. Kat çizgisinin üzerinden neyin geçebileceğini ve neyin geçemeyeceğini de söyleyebilirsin.
Bozulan örnek
Ayarlar sayfası dış ekranda rahat bir tek sütundur. İç ekran açılınca aynı listenin her satırı neredeyse tüm ekrana uzar. Başlık ve anahtar iki uca çekilir, ortada geniş bir boşluk kalır. Diğer bozuk biçim şudur: ana düğme, karekod ya da bir yüz tam ekranın ortasına konur ve yarı katlıyken kat çizgisi onu keser.
İncelemeye götürebileceğin dört ilke
İşlev ve durum kesilmez. Kapalıyken yapılabilen iş, açıkken de durur. Kaydırma, seçim ve düzenlenmekte olan içerik açılıp kapanınca sıfırlanmaz. Değişen şey, bir kerede kaç katman görülebildiğidir.
Fazladan genişlik hiyerarşiyi ortaya çıkarmak içindir. Posta dış ekranda «liste, mektup için içeri gir» biçimindedir. İç ekranda liste ile mektup yan yana durabilir. Bu, bir katman daha görmektir; mektup satırını 900 pt genişliğe çekmek değildir.
Kat çizgisi kitap sırtı gibidir. Arka plan, gradyan ve kayan bir yazı üzerinden geçebilir. Düğme, cümle, yüz, karekod ve sürükleme tutamacı tam ortada oturmamalıdır. Bu bölüm yalnızca bu yargıyı kurar. Kat çizgisinin nasıl sorgulanacağı Bölüm 7'dedir.
Denetim, etkilediği içeriğin yanında durur. Listeyi yöneten düğmeler listenin üstünde kalır. Yanda dikey bir çubuk var diye hepsini uzağa sürme.
Oyun, geçerli duruşu doldurmalıdır. Üstte ve altta ya da solda ve sağda siyah şerit bırakmak yerine görüntü oranını değiştirmeyi öne al. Yazının ve düğmelerin fiziksel boyutunu olabildiğince sabit tut.
Aynı ayarlar sayfası, iki genişlik
Uzun metne ve forma okunabilir bir üst sınır ver. Artan alanı daha uzun cümlelere değil, yanındaki hiyerarşiye bırak.
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)
}
Bu bölge «liste» ile «ayrıntı»yı aynı anda alacak kadar genişse, sonraki bölüm bunu sistemin bölünmüş görünümüyle yapar. Şimdilik elle bir HStack kenar çubuğu yazma. Genişledi diye yazı puntosunu da bir tur büyütme.
İncelemeye götürülecek sorular; henüz API değil
Genişletmeden önce sor. Kalıcı gezinme, ek ayrıntı, denetçi, önizleme, daha fazla denetim, daha yüksek bilgi yoğunluğu. Ne zaman kullanılır: compact'tan çok genişe giderken, aynı anda açılabilecek yapıyı bu sırayla ara. Ne zaman kullanılmaz: boşluğu doldurmak için önce aralığı ve puntoyu büyütme.
Kitap sırtı testi. Ortada, tam isabetle dokunulamayan bir yarık olduğunu düşün. Üzerinden geçebilir: fotoğraf arka planı, doku, sürekli kayan liste ve yazı. Üzerinden geçmemeli: düğme, sonuna kadar okunması gereken kısa cümle, yüz, karekod, giriş alanı, küçük simge.
Yakın gruplama. Sık kullanılan işlem, etkilediği parçanın yanında kalır. Ne zaman kullanılır: listenin süzgeci listede kalır; ayrıntının paylaşımı ayrıntıda kalır. Ne zaman kullanılmaz: simetri uğruna ana işlemi içerikten çok uzak bir köşeye atma.
Oyun ekranı doldurur. Duruş değişince görüntü oranını değiştir. Ne zaman kullanılır: dikey ya da yatay kilitlenebilir, ama geçerli duruş dolmalıdır. Ne zaman kullanılmaz: siyah şeritle idare etme. Kenar bırakmak zorundaysan düz renkle değil, görüntüyle doldur.
Kontrol noktası
İç ekranda ayarlar formunu 900 pt genişliğe çekmenin sorunu nerededir?
Yarı katlıyken gradyan arka plan ile «Öde» düğmesinden hangisi ortada kalabilir?
Posta listesindeki «tümünü seç», listeyle mi gitmeli, yoksa en sağdaki sistem çubuğuyla mı?
Bu bölümde henüz yapılmayacaklar. Yarı katlı duruş için sayfanın tamamını yeniden kurma ve kat çizgisini sorgulayan kodu şimdi yazma. Taslakta yalnızca «tam ortada oturamaz» öğeleri işaretlemen yeter.
Bölüm 2 · Hangi SDK ile derlediğin, ekranın ne kadar dolacağını belirler
Bitirdiğinde şunları yapabilirsin. Dağıtım hedefini derleme SDK'sından ayırabilirsin. Duo üzerinde açılabilen bir paketin neden hâlâ siyah kenar bıraktığını ve çubukların neden üstte kaldığını da söyleyebilirsin.
Bozulan örnek
iOS 26 SDK ile derlenen paket kurulabilir. İç ekran açılınca pencere ortada durur, dört yanı boş kalır. Kapanınca içerik, durum çubuğu ile kameranın solunda kalır. Bunun üzerine birisi «Duo ise frame'i doldur» diye ikinci bir yama yazar.
Üç SDK basamağı, ayrı bir Duo anahtarı yok
Ekranı doldurmak ve dikey çubuk, derleme SDK'sının getirdiği şeylerdir. Info.plist içindeki bir boole değeri değildir. Dağıtım hedefi daha eski bir sistemde kalabilir; böylece eski cihazlar uygulamayı kurmaya devam eder. Duo'da tam ekranı kullanmak ve sistem çubuklarının dikey biçime girmesini sağlamak için paket, iOS 27.1 SDK ya da daha yeni bir SDK ile derlenmelidir. 27.1 paketini yayınlamadan önce, çubuklar dikey olduktan sonra içeriğin hâlâ dokunulabilir olduğunu doğrula.
| Derlemede kullanılan SDK | Açıldığında iç ekran | Çubuklar |
|---|---|---|
| iOS 26 veya daha eski | Ortalanır, dört yanı boş kalır. Dış ekran içeriği durum çubuğu ile kameranın solunda durur | Bildiğin yatay çubuklar yerinde kalır |
| iOS 27 | İç ekranın büyük bölümünü doldurur, yine de sağdaki durum çubuğundan kaçınır | Araç çubuğu üstte kalır |
| iOS 27.1 veya daha yeni | Görüntü alanının tamamını kullanır | Araç çubuğu ve sekme çubuğu, durum çubuğunun altında dikey çubuk olarak dizilir |
Dağıtım hedefi daha düşük olabilir; çağrıların kapısı olmalıdır
Aşağıdaki iki parça henüz hiçbir katlanma API'sini çağırmaz. Yalnızca sonraki her bölümün uyacağı alışkanlığı kurar: 27.1 simgeleri bir kullanılabilirlik denetiminin içine alınır. Mac Catalyst üzerinde Xcode 27.1'in bilinen sorunu, bu 27.1 simgelerinin derlenemeyebilmesidir; bu yüzden ayrıca ayrılmaları gerekir.
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
Derlemeyle ilgili olgular
Dağıtım hedefi
IPHONEOS_DEPLOYMENT_TARGET. Ne zaman kullanılır: kurulabilecek en eski sistemi belirlerken. Ne zaman kullanılmaz: onu 27.1'e yükseltmek, ekranı doldurmanın ön koşulu değildir. Doldurma, derleme SDK'sına bakar.iOS 27.1 SDK. Tam ekran ile dikey çubuğun anahtarıdır. Ne zaman kullanılır: Duo üzerinde tam biçimi teslim etmeye hazırlanırken bu SDK ile derle. Ne zaman kullanılmaz: dikey çubuğa uyarlamadan 27.1 paketini yayınlama.
#available(iOS 27.1, *). Dağıtım hedefi 27.1'in altındayken yeni simgeler sarılmalıdır. Ne zaman kullanılır: Bölüm 5'ten sonraki her 27.1 API çağrısında. Ne zaman kullanılmaz: sıradan size class değerlerini bununla sarma. Onlar daha eski sistemlerde de vardır.Mac Catalyst
#if !targetEnvironment(macCatalyst). Ne zaman kullanılır: 27.1'e özgü API, Catalyst hedefini derlenemez hale getiriyorsa bu çağrıları ayır. Ne zaman kullanılmaz: bu yüzden iPhone üzerindeki uyarlamayı silme. Kamera aksesuarının kendisi Catalyst üzerinde kullanılamaz.
Kontrol noktası
Dağıtım hedefi hâlâ iOS 18, ama derleme 27.1 SDK ile yapılıyor. Dikey çubuk görünür mü?
Paket Duo üzerinde açılabiliyor, dört yanı boş. Sonraki adım cihaz modelini tespit edip doldurmak mı, yoksa SDK'yı değiştirmek mi?
27.1'in yeni çağrıları neden iOS 18 kod yoluna doğrudan yazılamaz?
Bu bölümde henüz yapılmayacaklar. Ekranı doldurmak için ekran boyutunu okuma. UIScreen.main ancak sonraki bölümde değiştirilir. Dikey çubuğun öğe kuralları da henüz gelmedi.
Kaynak: Three steps to make your app shine on iPhone Duo. Xcode 27.1, macOS Tahoe 26.6 veya daha yenisini ister. Simülatör Device Hub içinde açılır, kapanır, döner ve katlanır.
Bölüm 3 · Bu bölge ne kadar geniş diye sor, bu Duo mu diye sorma
Bitirdiğinde şunları yapabilirsin. Cihaza, yöne ve UIScreen.main değerine bağlı yerleşim dallarını, size class ile geçerli kap boyutuna göre yeniden hesaplayan bir düzene çevirebilirsin.
Bozulan örnek
Aynı Duo, dış ekranda, iç ekranın tam ekranında ve iç ekranın bölünmüş görünümünde belirebilir. Güvenli alan ve çubuklar da değişir. Açılışta ekranı bir kez okursan, bir katlamadan sonra bu okuma geçersiz kalır. İç ekran, Info içinde bildirdiğin yönü de dikkate almaz.
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
Kolayca ters hatırlanan iki olgu
regular genişlik artık iPad demek değildir. İç ekran regular genişlik ve regular yüksekliktedir. Eskiden «regular ise iPad'e özel sayfaya git» diyen kod, artık iPhone üzerinde de çalışır. Apple'ın istediği şudur: iç ekran, iPad yerleşiminin doğal uzantısı gibi görünsün. Bu yüzden o yerleşimi düzelt; bir idiom denetimi ekleyip onu kapatma.
Yön, şekil demek değildir. Şu an yatay mı yoksa dikey mi uzadığını bilmek için elindeki bölgenin genişliği ile yüksekliğini karşılaştır. Dış ekranın dikey duruşu compact genişlik artı regular yüksekliktir. Dış ekranın yatay duruşunda iki yön de compact'tır. İç ekranda iki yön de regular'dır. Bir iPhone uygulaması bundan böyle her size class birleşimiyle karşılaşabilir.
Boyut, oturumun ortasında değişir. viewIsAppearing içinde bir değer kaydedip onu sonuna kadar kullanma.
Kaba göre yeniden hesapla
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)
Gerçekten UIScreen gerektiğinde, onu kullanacağın anda view.window?.windowScene?.screen üzerinden al. Özellik olarak saklama. Pencereyi scene'den oluştur. Ana ekranın bounds değerini frame diye kullanma.
API
horizontalSizeClass. SwiftUI:@Environment(\.horizontalSizeClass). UIKit:traitCollection.horizontalSizeClass. Ne zaman kullanılır: compact ile regular genişliği ayırmak ve dikey size class ile birlikte okumak için. Ne zaman kullanılmaz:.regulardeğerini «iPad sayfası» diye çevirme.onGeometryChange. SwiftUI, boyut değişince kabınCGSizedeğerini alır. UIKit karşılığıviewDidLayoutSubviewsileviewWillTransition(to:with:)yöntemleridir. Ne zaman kullanılır: genişlik ile yüksekliği karşılaştırmak ya da geçerli kap üzerinde yerel ölçüm yapmak için. Ne zaman kullanılmaz: bununlaUIScreen.main.boundsokuma.displayScale. SwiftUI:@Environment(\.displayScale). UIKit:traitCollection.displayScale. Ne zaman kullanılır: piksel oranı gerektiğinde. Ne zaman kullanılmaz:UIScreen.main.scaleokumaya devam etme. Ana ekran başvurusu kullanımdan kalkacaktır.UIWindow(windowScene:). Pencereyi scene ile oluşturur. Ne zaman kullanılır:UIWindow(frame: UIScreen.main.bounds)yerine. Ne zaman kullanılmaz: yerleşim hesabı yine view ya da window bounds değerine bakar, tüm fiziksel ekrana bakmaz.
Kontrol noktası
horizontalSizeClass == .regularartık iPhone üzerinde de doğru olabilir mi?İç ekran yatay döndü. «Şimdi daha geniş» yargısı için
interfaceOrientationgüvenilir midir?safeAreaInsets.left * 2neden kullanılabilir genişliği yanlış hesaplar?
Bu bölümde henüz yapılmayacaklar. İki gezinme ağacını if width > 700 eşiğiyle değiştirme. Sonraki bölüm, «darsa ileri it, genişse yan yana koy» sorununu sistem kabıyla çözer.
Kaynak: Three steps içindeki Step 2 ve Preparing belgesindeki resizing considerations. Tech Talk 111461.
Bölüm 4 · Yan yana mı yoksa ileri iterek mi olacağına sistem kabı karar versin
Bitirdiğinde şunları yapabilirsin. Seçimi koruyan bir bölünmüş kap kullanabilir, ızgara sütun sayısını en küçük öğe genişliğinden türetebilir ve yerel yeniden dizilişi «hangi cihazda olduğum» ile değil, «hangi diziliş sığıyor» ile yapabilirsin.
Bozulan örnek
compact için bir NavigationStack, regular için elle yazılmış bir HStack kenar çubuğu. Seçim her birinde ayrı saklanır. Izgara «Duo ise üç sütun, değilse bir sütun» diye yazılır. Düğme grubu if width > 700 ile yatay ve dikey arasında iki büsbütün farklı görünüme ayrılır.
Öncelik sırası
Sistem gezinme kabı kullanılabiliyorsa kabı kullan. Yinelenen kartlar için uyarlanabilir ızgara kullan. Yerel olarak «yatay sığmıyorsa dikey diz» kararı için kalan alanı yoklayan bir yerleşim kullan. Size class bundan sonra gelir. Kesin geometri en sona kalır. Bölünmüş görünümün bilgi mimarisi «koleksiyon, seçim, ayrıntı»dır. Dar iken liste ayrıntıya ilerler, geniş iken yan yana durur. Seçim bu değişimin ardından yaşamalıdır. Fazladan genişlik bu katmanı göstermek içindir. Uzun metin yine sınırlı genişlikte kalır.
Liste ve ayrıntı
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
// 不要写死侧栏点数
Kart ızgarası: sütun sayısı değil, en küçük genişlik
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)
}
}
Bölünmüş ekran seni orta bir genişliğe indirdiğinde sütun sayısı kendiliğinden 2'ye iner. 3'ten birden 1'e zıplamaz. Yarı katlıyken sütun sayısının çift olmasını istiyorsan bu Bölüm 7'nin işidir; şimdi yazma.
Aynı görünüm grubunun dizilişi değişir, kimliği değişmez
ViewThatFits(in: .horizontal) {
HStack { Preview(); Inspector() }
VStack { Preview(); Inspector() }
}
let layout = isWiderThanTall
? AnyLayout(HStackLayout())
: AnyLayout(VStackLayout())
layout {
Chart()
Legend()
}
UIKit'te ViewThatFits yoktur. Karşılık gelen düşünce şudur: yerleşim sırasında «yatay hâlâ sığıyor mu» diye sor, sonra aynı UIStackView eksenini değiştir. Alt denetleyicileri yok edip yeniden oluşturma; yoksa düzenlenmekte olan yazı kaybolur.
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. Ne zaman kullanılır: koleksiyon, seçim, ayrıntı. Darsa ileri iter, genişse yan yana koyar. Ne zaman kullanılmaz: onun yerine elleHStackkenar çubuğu yazma. Bu, Bölüm 8'deki çift içerik dizilişi değildir.GridItem(.adaptive(minimum:)). UIKit: kap genişliğini en küçük öğe genişliğine bölerek sütun sayısını bul. Ne zaman kullanılır: kart, albüm, pano gibi yinelenen içerik. Ne zaman kullanılmaz:isDuo ? 3 : 1diye yazma.ViewThatFits. Verilen eksende hangi alt ağacın sığdığını dener. Ne zaman kullanılır: düğme grubu, üst veri, önizleme artı denetçi gibi yerel seçimler. Ne zaman kullanılmaz: tüm uygulamanın gezinmesini bununla değiştirme.AnyLayout. Yerleşim kabını değiştirir, alt görünümlerin kimliğini korur. Ne zaman kullanılır: grafik ile açıklama, önizleme ile özellikler; diziliş değişir ama aynı iki görünüm kalır. Ne zaman kullanılmaz: ikiifdalının her birine alt görünümü bir kez daha yazma.
Kontrol noktası
Yan yana düzenden tek sütuna katlanınca seçim nerede durmalıdır?
Bölünmüş ekrandan sonra genişlik yalnızca iki adet 220 pt karta yeter. Sütun sayısı nasıl gelmelidir?
ViewThatFits«hangi cihaz» sorusunu mu yanıtlar, yoksa «hangi diziliş sığıyor» sorusunu mu?
Bu bölümde henüz yapılmayacaklar. Sekme çubuğunun kenar gezinmesine dönüp dönmeyeceği sonraki bölümdedir. İki içerik parçasının kat çizgisinden kaçması Bölüm 8'dedir. Şimdi Arrangement getirme.
Bölüm 5 · Sistem çubukları kendiliğinden yana geçer, sahte çubuklar geçmez
Bitirdiğinde şunları yapabilirsin. Gezinme çubuğunu, araç çubuğunu ve sekme çubuğunu hem yatay hem dikey biçimde tanınır kılabilir, ikincil eylemleri sistemin taşma menüsüne bırakabilirsin.
Bozulan örnek
Sayfanın tepesinde elle çizilmiş bir HStack vardır. Ya da ayrı new ile oluşturulmuş bir UINavigationBar, UIToolbar veya UITabBar vardır. Sistem bu içerikleri dikey çubuğa toplamaz. Sık görülen başka bir sorun: paylaş düğmesinin yalnızca "Share" başlığı vardır. Dikey çubuk yalnızca simge gösterir. Simgesi olmayan öğe hiç dikey duruma geçmez. Üstüne bir de kendi «…» menünü çizersen sistemin taşma menüsüyle yer kavgasına girersin.
Çubuk ne zaman dikey olur, içeri kim girebilir
27.1 SDK ile derledikten sonra dış ekran, gezinme çubuğunu, araç çubuğunu ve sekme çubuğunu yanda tek bir şerit halinde birleştirir. İç ekranın yatay duruşu ve leading ya da trailing konumdaki bazı görünümler de böyle davranır. İç ekranın dikey duruşu yatay çubuk kullanmaya devam eder. Bölünmüş görünümde dikey çubuğa yalnızca ayrıntı sütunu katılır. Yan sütun ve içerik sütunu yatay kalır. Denetçi yatay kalır.
Çubuk donanım kenarıyla hizalanır. Sağdan sola diller onu öteki tarafa taşımaz. Bölünmüş ekranda soldaki uygulamanın çubuğu soldadır, sağdaki uygulamanın çubuğu sağdadır.
Öğenin hem başlığı hem simgesi olmalıdır. Dikey biçimde simge kullanılır. Yatay biçimde simge öne alınır, başlık da kullanılabilir. Taşma menüsünde simge ile başlık birlikte görünür. Özel görünüm, dikey yerleşime uygun olduğunu bildirmediğin sürece varsayılan olarak yatay çubukta kalır. Salt yazı düğmesi yatay çubukta kalır. Sistemin Edit düğmesi tam olarak simge ile yazı arasında böyle geçiş yapar.
Dikey çubuğun tepesi geri ya da kapat için ayrılır. Ardından Done gibi belirgin eylemler gelir. Diğer öğeler eski gruplarını korur. Taşma varsayılan olarak aşağıdan yukarıya toplanır. Gezinme türündeki sayfa sekme çubuğunu korumayı öne alır. İş yapan sayfa araç öğelerini korumayı öne alır.
Aynı eylem grubu, iki çubukta da kullanılır
SwiftUI tarafı NavigationStack içine asılır:
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 tarafı, gezinme denetleyicisinin içindeki alt denetleyiciye asılır:
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
Geri düğmesini gezinme denetleyicisi kendisi koyar. Özel kapatmayı leading tarafına, Done düğmesini pinnedTrailingGroup içine koy. Arama alanı tüm yeri kapladığında, sabitlenmiş öğeler taşmaya en son girer. additionalOverflowItems, kendi «daha fazla» öğelerini sistem menüsüne almak içindir. Üç noktayı taşmaya bırak. Diğer menüler başka bir simge kullansın.
Simge ile yazı arasında gidip gelen öğeye .horizontalOnly ver. Yatay çubuk yokken gizlenir, dikey çubuğa sıkışmaz. Özel görünümün gerçekten dikey bir çizimi varsa .verticalPreferred kullan. Esnek boşluğun dikey eksendeki boyutu 0'dır. Gruplama için ToolbarItemGroup / UIBarButtonItemGroup kullan. Elle sabit aralık sokma. Rozet için badge kullan. Sayıyı başlığın içine yazma; yoksa bu öğenin salt simgeye dönmesi zorlaşır.
Dikey çubuktan çıkmak, kararlı bir istisnadır
NavigationStack {
CalculatorView()
.toolbarVerticalBehavior(.disabled)
}
override var preferredVerticalBarBehavior: UIVerticalBarBehavior {
.disabled
}
Varsayılan .automatic değeridir. Yalnızca şu arayüzlerde çık: tam ekran video, hesap makinesi gibi kaymayan tam genişlikli yerleşim, tek bir kapat düğmesi olan sheet. Bu kararlı bir seçimdir. viewWillAppear içinde sayfa durumuna göre gidip gelme. Yalnızca bir sayfada çubuğu gizlemek istiyorsan görünürlük API'sini kullan; dikey davranışı kapatma. UIKit'te tercih gerçekten değiştiyse setNeedsUpdateOfVerticalBarConfiguration() çağır. Gezinme denetleyicisi kararı varsayılan olarak yığının tepesine bırakır. Sekme çubuğu kararı geçerli seçili sayfaya bırakır.
Çubuğun hangi yanda olduğunu ve arka planın nasıl uzadığını bil
@Environment(\.toolbarVerticalEdge) private var verticalEdge
// HorizontalEdge?;这个上下文从不用垂直栏时为 nil
Image("hero")
.backgroundExtensionEffect()
let edge = traitCollection.verticalBarEdge
// .leading / .trailing / .unspecified
// 表示系统偏好的边,不表示栏此刻是否可见
// 从不用垂直栏的上下文返回 .unspecified
let backdrop = UIBackgroundExtensionView()
backdrop.contentView = hero
UIKit'te bu kenarı dinlemek için registerForTraitChanges çağrısına UITraitCollection.systemTraitsAffectingVerticalBarEdge geçir. Trait değişse de kenar aynı kalabilir.
Birinci düzey giriş: iç ekran kenar gezinmesine dönebilir
iPhone üzerinde yalnızca .sidebarAdaptable yazmak hâlâ alt çubuktur. Desteklenen yerde kenar gezinmesine geçmek için iOS 27'nin defaultTabBarPlacement(.sidebar) değerini de ekle. iOS 18'in defaultAdaptableTabBarPlacement değerini kullanma; o iPad içindir. Kenar çubuğuyla ilgili UI'ye isTabViewSidebarAvailable üzerinden bak. Size class tahmin etmeye devam etme.
TabView {
Tab("Home", systemImage: "house") { HomeView() }
Tab("Library", systemImage: "books.vertical") { LibraryView() }
}
.tabViewStyle(.sidebarAdaptable)
.defaultTabBarPlacement(.sidebar)
tabBarController.sidebar.preferredPlacement = .sidebar
Ürün, alt çubuğu bilinçli olarak da koruyabilir ve genişleyen alanı içeriğin içindeki bölünmüş görünüme bırakabilir. İkisi de geçerlidir. Genişliğe göre kendin sahte bir kenar çubuğu çizme.
API
toolbarVerticalBehavior(iOS 27.1)..automatic/.disabled. UIKit:preferredVerticalBarBehaviorüzerine yazılır, türüUIVerticalBarBehavior. Ne zaman kullanılır: hesap makinesi, tam ekran video, tek kapat düğmeli sheet. Ne zaman kullanılmaz: gezinmeye ya da bir görünümün durumuna göre gidip gelme.axisBehavior(iOS 27.1). SwiftUI:.automatic/.horizontalOnly/.verticalPreferred. UIKit:UIBarButtonItem.axisBehavior, aynı üç değer. Ne zaman kullanılır: simge ile yazı arasında geçen öğede horizontalOnly; gerçekten dikey çizimi olan özel görünümde verticalPreferred. Ne zaman kullanılmaz: başlığı ve simgesi olan sıradan düğme automatic kalsın.visibilityPriority(iOS 27.0). SwiftUI:.automatic/.low/.high, ayrıcainit(lowerThan:)/init(higherThan:). UIKit:.standard/.low/.high. Ne zaman kullanılır: önce grubun tamamına öncelik ver, sonra grup içindeki tek öğeye ver. Sık kullanılanları ve rozetlileri taşmaya en son bırak. Ne zaman kullanılmaz: ikinci bir «…» menüsü yapma. Sistem menüsüToolbarOverflowMenuileadditionalOverflowItemsöğeleridir. UIKit'inadditionalOverflowItemsdeğeri iOS 16'dan beri kullanılabilir.Sıkıştırma davranışı (iOS 27.1). SwiftUI:
.automatic/.prefersToolbarItems/.prefersTabBar. UIKit:navigationItem.verticalBarCompressionBehaviorüzerinde.automatic/.prefersBarItems/.prefersTabBar. Ne zaman kullanılır: gezinme türünde varsayılan (önce araç öğelerini topla, sekmeyi koru). Görev türünde önce sekme çubuğunu topla. Ne zaman kullanılmaz: sıkıştırmayı taklit etmek için iki çubuğu ayrı ayrı yazma.topBarPinnedTrailing(iOS 27.0). UIKit karşılığıpinnedTrailingGroupiOS 16'dan beri kullanılabilir. Kapatma için.cancellationActionkullan; UIKit'teleadingItemGroupsiçine koy. Ne zaman kullanılır: Done gibi, geri düğmesine yakın durması gereken ana eylem. Ne zaman kullanılmaz: ikincil eylemleri buraya sabitleme. Onlar taşabilmelidir.toolbarVerticalEdge(iOS 27.1). SwiftUI:HorizontalEdge?; bağlam hiç dikey çubuk kullanmıyorsa nil. UIKit:traitCollection.verticalBarEdge; değer leading, trailing ya da unspecified olur. Yansıttığı şey tercih edilen kenardır; çubuk o anda görünmez olsa bile. Ne zaman kullanılır: özel içeriği bu çubukla hizalarken. Ne zaman kullanılmaz: unspecified değerini «çubuk az önce gizlendi» diye okuma. Bu, bağlamın dikey çubuk kullanmayacağı anlamına gelir.backgroundExtensionEffect()(iOS 26). UIKit:UIBackgroundExtensionView;contentViewgüvenli alana konur, kenarlar uzantıyla doldurulur. Ne zaman kullanılır: ana görsel ya da arka plan dikey çubuğun altına uzasın diye. Ne zaman kullanılmaz: dokunulabilir ön plan, bununla güvenli alanın dışına çıkmasın.defaultTabBarPlacement(iOS 27.0)..sidebarAdaptableüzerine bir de.sidebar. UIKit:sidebar.preferredPlacement = .sidebar. Ortam değerleriisTabViewSidebarAvailablevetabBarPlacementde iOS 27'dedir. Ne zaman kullanılır: birinci düzey bölümler, geniş yerleşimde sürekli gezinme olarak durmaya uygunsa. Ne zaman kullanılmaz: Duo içindefaultAdaptableTabBarPlacementkullanma. Bu değiştirici iOS 18'den beri vardır ve iPad üzerinde etkilidir.
Kontrol noktası
Yalnızca başlığı olan, simgesi olmayan bir düğme dikey çubuğa girer mi?
Bölünmüş görünümün üç sütunundan hangisinin çubuğu dikey olur?
Gezinme türündeki sayfada yer yetmezse varsayılan olarak önce araç öğeleri mi toplanır, yoksa sekme çubuğu mu?
Elle çizilmiş bir
UIToolbarneden yana geçmez?
Bu bölümde henüz yapılmayacaklar. Çubuğun solda mı sağda mı olduğuna karar vermek için kat çizgisini sorgulama. Çubuğun kenarını sistem trait / environment içine koyar. Sheet'in hangi tarafa konacağı sonraki bölümdedir.
Kaynak: HIG · Vertical controls; Preparing · Optimize bars. Tech Talk 111462.
Bölüm 6 · Dört kenarı ayrı hesapla, sheet hangi yana yapışacağını kendisi bildirsin
Bitirdiğinde şunları yapabilirsin. Sheet için bir konum seçebilir, kullanılabilir alanı bakışımsız güvenli alana göre hesaplayabilir ve ekranla eşmerkezli köşeler ile odak sayesinde tam boy görselin öznesini koruyabilirsin.
Bozulan örnek
Sol inset ikiyle çarpılıp toplam güvenli alan sanılır. Bölünmüş ekran ya da dikey çubuk solu ve sağı eşit olmaktan çıkarır, içerik kayar. Başka bir sorun, sheet yerine kendin tam ekran bir maske yapmandır. Sistem sheet'i, uyarı, menü ve popover kat çizgisinden kaçınır. Senin masken kaçınmaz. Ana görsel .fill ile kırpılır. Dış ekranda görünen yüz, iç ekranda kesilir.
Sheet'in çubuğu yerleşime uyar
Dış ekranda araç çubuğu olan sheet varsayılan olarak dikey çubuk kullanır. İç ekranda sheet varsayılan olarak ortalanır ve çubuğu yataydır. leading tarafında dikey çubuk yoktur, trailing tarafında vardır. Yerleşimi yalnızca sheet dikkate alır. Bunun için ikinci bir özel geçiş yazma. Tek kapat düğmesi olan sheet, önceki bölümün dikey çubuğu kapatmaya izin verdiği durumdur. sourceView ayarlandığında UIKit preferredPlacement değerini yok sayar.
.sheet(item: $selection) { item in
ItemDetail(item: item)
.presentationPlacement(.trailing)
}
let sheet = detail.sheetPresentationController
sheet?.preferredPlacement = .trailing
// .automatic / .leading / .center / .trailing
// sourceView 非 nil 时,这个属性被忽略
Güvenli alanın dört kenarı bağımsızdır
content
.safeAreaPadding(.horizontal)
background
.ignoresSafeArea()
let usable = view.bounds.inset(by: view.safeAreaInsets)
// 分别使用 left、right、top、bottom
// 折痕不是 safe area inset
Süs, güvenli alanın dışına taşabilir. Dokunulabilir içerik ve okunması gereken yazı içeride kalır. Kamera, dikey çubuk ve bölünmüş ekran solu ile sağı ya da üst ile altı eşit olmaktan çıkarır. Ekran köşesine yapışan özel şekil için eşmerkezli dikdörtgen kullan. Yarıçapı sabitleme.
RoundedRectangle(cornerRadius: 12) // 不要写死去贴屏幕角
ConcentricRectangle()
view.cornerConfiguration = .corners(radius: .containerConcentric())
Tam boy görsel öznesini korumalıdır
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
}
Özne geometrik merkezde değilse kırpmaya bir odak ver. Yalnızca fill ile fit arasında geçiş yapmak yetmez. Oyun ise ekranı doldurur ve oranı değiştirir; bkz. Bölüm 1.
API
presentationPlacement(iOS 27.0)..automatic/.center/.leading/.trailing. UIKit:UISheetPresentationController.preferredPlacement. Ne zaman kullanılır: her sheet'i kapalı, açık ve yarı katlı durumda bir kez gördükten sonra kenara mı yoksa ortaya mı yapışacağına karar ver. Ne zaman kullanılmaz: popover ve uyarıya bu değiştiriciyi geçirme.sourceViewayarlıysa UIKit bunu yok sayar.Bakışımsız inset.
bounds.inset(by: safeAreaInsets). Ne zaman kullanılır: kullanılabilir genişliği ya da yüksekliği elle hesapladığın her yerde. Ne zaman kullanılmaz: bir kenarın inset değerini karşı kenara yansıtma. Kat çizgisini de safe area içine sıkıştırma.ConcentricRectangle(iOS 26). UIKit:UICornerConfigurationüzerindeki kap eşmerkezli yarıçapı;UIView.cornerConfigurationvardır. Ne zaman kullanılır: özel panel, geçerli ekranın ya da kabın köşesine yapışsın diye. Ne zaman kullanılmaz: kartın kendi küçük köşesi hâlâ sabit yarıçap kullanabilir. Kat çizgisinden kaçmak için bunu kullanma.fill / fit. Geçerli orana ya da size class değerine göre seç, ya da bir odak ayarla. Ne zaman kullanılır: ana görsel ve video kapağı; dar dış ekrandan geniş iç ekrana kadar özne korunmalıdır. Ne zaman kullanılmaz: contentMode değerini yalnızca açılışta bir kez seçme.
Kontrol noktası
İç ekranda trailing sheet'in araç çubuğu yatay mıdır, dikey midir?
İçerik genişliğini
left * 2ile neden elde edemezsin?Sistem sheet'i kat çizgisinden kaçınır. Kendin çizdiğin tam ekran maske kaçınır mı?
Bu bölümde henüz yapılmayacaklar. Özel tuval, karekod ve sürükleme denetimi kat çizgisinden kaçacaksa ayrılmış bölgeyi sonraki bölüm sorgular. Şimdi kat çizgisinin koordinatını tahmin etme.
Bölüm 7 · Kat çizgisi ve kamera iki ayrılmış bölgedir, güvenli alan değildir
Bitirdiğinde şunları yapabilirsin. Özel yerleşimde etkin kat çizgisini ve kamerayı bulabilir, sayfanın tamamını yeniden kurmak yerine yalnızca kapanan parçayı kaydırabilirsin.
Bozulan örnek
Yarı katlanır katılmaz başka bir arayüz ağacına geçilir. Ya da «kat çizgisi genişliğin yüzde 50'sindedir» diye sabitlenir. Bölünmüş ekran ve dönüşten sonra bu oran artık görünümünün merkezi değildir. Öteki uçta ise yazıdaki her paragraf kat çizgisinden kaçırılır. Kayan içerik o yarığın üzerinden zaten kayabilir.
İki tür bölge; sistem bileşenlerinin çoğu zaten kaçınır
Ayrılmış bölge, başka bir şeyin senin koordinat sisteminde kapladığı dikdörtgendir. frame, margins değerini içerir. Her birinin türü, etkin olup olmadığı ve bir kimliği vardır. Sorgu, geçerli görünümle kesişen bölgeleri döndürür. Görünümün iç ekranın yalnızca yarısını kaplıyorsa tüm ekranın kat çizgisini görebileceğini varsayma.
division, bölme. Yarı katlıyken menteşe büyük bir alanı iki yana ayırır. Tamamen düzleşince bu bölge etkin olmaz, genişliği 0 olur.
occlusion, örtme. Dış ekranın ön kamerası hep oradadır ve Dynamic Island'a doğru genişler. İç ekranın ön kamerası normalde ekranın altında gizlidir. Yalnızca kullanım sırasında bir alanı kaplar ve arayüzün kenara çekilmesini sağlar.
Uyarı, menü, sheet ve bölünmüş görünüm kendiliğinden uyarlanır. Bu dikdörtgenleri okuman gereken yerler özel tuval, karekod, yüz, kesintisiz okunması gereken kısa cümle ve sürüklenen nesnedir. Yalnızca kapanan öğeyi kaydır ve ilgili denetimlerini onunla birlikte götür. Onu ekranın öteki ucuna yollama. Kayan yazıyı ve listeyi bir bütün olarak yerinden oynatma.
Önce etkin olanları süz, sonra özel içeriği yerleştir
SwiftUI'deki reservedRegions çağrısına layoutDirectionBehavior: de eklenebilir; varsayılan .mirrors değeridir. Yalnızca kendin yansıtma yaptıysan .fixed geçir, yoksa iki kez yansır. Kameranın fiziksel yeri dile göre dönmez. SwiftUI geometriyi varsayılan olarak önce senin için yansıtır. onGeometryChange proxy'si de aynı sorguyu yapabilir.
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)
}
Çift sütun gibi yapısal bir karar, cihaz düz dururken ve bölme bölgesi henüz etkin değilken de görülebilmelidir. İşte o zaman .includeInactive geçirilir. Denetimi hemen kaydırıp kaydırmamaya yine isActive karar verir.
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). SwiftUI'deGeometryProxyüzerindedir. UIKit:view.reservedRegions(kind:options:), dönüş türü[UIView.ReservedRegion]. Ne zaman kullanılır: sistem kabının senin için kaydırmayacağı özel içerik. Ne zaman kullanılmaz: sıradan liste, bölünmüş görünüm, sheet, uyarı. Sonucu güvenli alan inset'i sanma.ReservedRegion(iOS 27.1).id,kind,frame,margins,isActive. Türler.divisionve.occlusion. Ne zaman kullanılır: konumuframeile koy.framekenar payını zaten içerir. Ne zaman kullanılmaz: etkin olmayan division ile düğmeyi itme. Düz dururken tıklama yerini değiştirmemelidir..includeInactive(iOS 27.1).ReservedRegion.QueryOptions. Ne zaman kullanılır: «kat çizgisi var olduğu sürece ızgara çift sütun kullansın» gibi, o an yarı katlı olup olmamasından bağımsız kararlar. Ne zaman kullanılmaz: etkin olmayan bir bölge buldun diye düğmeyi önceden kaydırma.layoutDirectionBehavior..mirrorsya da.fixed. Ne zaman kullanılır: varsayılanı koru; otomatik yerleşimli alt görünümler ile bölge aynı yönü kullansın. Ne zaman kullanılmaz:.fixedyalnızca elle yansıtma yaptığında geçirilir.
Kontrol noktası
Tamamen düzleştikten sonra division hâlâ etkin midir? Genişliği nedir?
Kaydırılabilen bir yazının her paragrafı kat çizgisinden kaydırılmalı mıdır?
İç ekran kamerası normalde occlusion kaplar mı?
Bu bölümde henüz yapılmayacaklar. Oynatıcı ile oynatma listesi gibi eşli içerik sonraki bölümde Arrangement'a bırakılır. Kat çizgisinden kaçıp kaçmamaya menteşe açısıyla karar verme.
Kaynak: ReservedRegion; Preparing · Adapt to reserved regions. Tech Talk 111463.
Bölüm 8 · İki içerik tek bir deneyimse yer değiştirmeyi kaba bırak
Bitirdiğinde şunları yapabilirsin. Bir görünüm çiftinin split mi yoksa overlay mi kullanması gerektiğine karar verebilir ve gezinmeyi bu kabın dışında tutabilirsin.
Bozulan örnek
TabView ya da NavigationSplitView yerine Arrangement konur. Ya da Arrangement, List, ScrollView veya bölünmüş görünümün bir sütununun içine yerleştirilir. Sonuçta içeriğin yarısı kaydırılarak çıkarılamaz ve dokunulamaz.
Zaten iki yığına benziyorsa oraya eşle
Arrangement'ın yalnızca bir ana görünümü ve bir ikincil görünümü vardır. Alana, en-boy oranına, size class değerine ve bölme bölgesine bakar. Duo olup olmadığına bakmaz. Gezinme kabı dışarıda durur. Apple'ın kendi örneği, bir gezinme yığınının içine bir Arrangement koymaktır. Arrangement'ın içine bir daha bölünmüş görünüm koyma.
split. Eski
HStack/VStackkarşılığıdır. Ya da kimsenin kimseyi kapatmaması gereken birincil-ikincil ilişkidir. Yüksekten daha genişse solda ve sağda durur. Genişten daha yüksekse üstte ve altta yığılır. Bölme bölgesi belirdiğinde iki yanı ayarlar ve kat çizgisinden kaçınır. Tek bir eksene kısıtlandıktan sonra o eksen kullanılamıyorsa yalnızca ana görünüm kalır..split.axes(.horizontal)yatay eksene sığmadığında yalnızca ana içeriği gösterir.overlay. Eski
ZStackkarşılığıdır. Etkin bir bölme bölgesi yokken ana görünüm ikincil görünümün üstündedir (kapalı ya da tamamen açık). Yarı katlıyken ana görünüm kat çizgisinin arka ya da alt tarafına, ikincil görünüm ön ya da üst tarafına gider.
Oynatıcı ve liste: 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)
Yalnızca solda ve sağda izin ver; dikeyken yalnızca oynatıcı kalsın:
.arrangementViewStyle(.split.axes(.horizontal))
arrangement.updateArrangement(.split.axes(.horizontal))
Denetimler görüntünün üstünde: overlay
ArrangementView {
TransportControls()
} secondary: {
Stage()
}
.arrangementViewStyle(.overlay)
arrangement.setViewController(controls, for: .primary)
arrangement.setViewController(stage, for: .secondary)
arrangement.updateArrangement(.overlay)
Oran ve katman için şu ayarlar da vardır; hepsi iOS 27.1'dedir: splitArrangementLayoutRatio, splitArrangementLayoutSize, splitArrangementFixedLayoutSize, overlayArrangementEdge. Ortam değerleri splitArrangementAxis ile overlayArrangementZIndex, alt görünümün şu an hangi dizilişte olduğunu bilmesini sağlar. UIKit zIndex, splitAxis ve isHidden okumak için state(for:) kullanır. Birincil ve ikincil görünümün boyut aralığı UISplitArrangement.ViewProperties içindedir. Örtme kenarı UIOverlayArrangement.ViewProperties.edge içindedir.
API
ArrangementView(iOS 27.1).initprimary ile secondary alır. UIKit:UIArrangementViewController;setViewController(_:for:animated:)konumu.primary/.secondary. Ne zaman kullanılır: oynatıcı ile kuyruk, düzenleyici ile denetçi, tuval ile özellikler. İki parça aynı deneyimdir. Ne zaman kullanılmaz: uygulamanın birinci düzey gezinmesi. Bölünmüş görünümün, listenin ya da kaydırma görünümünün içine koyma..split(iOS 27.1).arrangementViewStyle(.split),.axes(.horizontal)ya da.vertical. UIKit:updateArrangement(.split.axes(...)). Ne zaman kullanılır: iki yanın da kendine ait alanı vardır, kimse kimseyi örtmez. Ne zaman kullanılmaz: denetim zaten içeriğin üstünde yüzmelidir. O, overlay'dir..overlay(iOS 27.1). Bölme bölgesi yokken ana görünüm üsttedir. Yarı katlıyken ana görünüm arka ya da alt tarafa, ikincil görünüm ön ya da üst tarafa gider. Ne zaman kullanılır: ön plan / arka plan; normalde birbirini örtmelerine izin vardır. Ne zaman kullanılmaz: iki sütunlu posta. O, Bölüm 4'ün bölünmüş görünümüdür ya da buradaki split'tir.updateArrangement(_:animated:)(iOS 27.1). split / overlay değiştirir ya da yalnızca ekseni değiştirir. Ne zaman kullanılır: ürün bir ekseni kilitlemek istiyorsa ya da iki üslup arasında geçiş varsa. Ne zaman kullanılmaz: her dönüşte frame'i kendin hesaplayıp bunu çağırma. Varsayılan üslup zaten kabı izler.
Kontrol noktası
Eski düzen bir
ZStackidi ve yarı katlıyken kat çizgisinin iki yanına ayrılmalı. split mi kullanılır, overlay mi?Arrangement gezinmeyi mi sarmalı, yoksa gezinme tarafından mı sarılmalıdır?
Onu
ScrollViewiçine koymak neye yol açar?
Bu bölümde henüz yapılmayacaklar. split mi overlay mi olduğuna menteşe açısıyla karar verme. Açı, Bölüm 10'un etkileşim girdisidir.
Kaynak: Preparing içindeki SwiftUI / UIKit örnekleri; HIG · Arrangement views. İmzalar iOS 27.1 SDK içindeki ArrangementView ile UIArrangementViewController üzerinden karşılaştırıldı.
Bölüm 9 · Açılıp kapanmak yerleşim değişikliğidir, uygulamayı yeniden başlatmak değildir
Bitirdiğinde şunları yapabilirsin. Gezinmeyi, kaydırmayı, seçimi, düzenlemeyi ve oynatmayı yerleşimin üstünde tutabilirsin. Duo üzerinde bölünmüş ekranın ve yeni pencerenin sınırını da bilirsin.
Bozulan örnek
Kapalıyken liste üçüncü mektubu seçmiştir. Açılınca başka bir görünüm ağacı birinci mektuptan başlar, taslak da boştur. Başka bir bozuk biçim: yalnızca dış ekranın tam ekranı ile iç ekranın tam ekranı denenmiştir. Bölünmüş ekran yarıya indiğinde çubuklar üst üste biner, yazı kırpılır. Bir de «açıldıysa mutlaka çok geniştir» varsayımı yapılır.
Kesintisiz bir genişlik, artı iki donanım sınırı
İç ekranda bütün uygulamalar Split View'a katılır. Video da bir uygulamayla üst üste gelebilir. Her uygulamanın dikey çubuğu kendi dış kenarına yapışır. Bu yüzden çubuğun solda da sağda da olabilir. İki yanı da dene.
Çok geniş yerleşim orta genişliğe, oradan da compact'a geri dönebilmelidir. Durum — gezinme yolu, kaydırma, seçim, düzenleyici, oynatma ilerlemesi, kaydedilmemiş form — tek sütun mu çift sütun mu kararını veren katmanın üstünde durur.
Duo, çoklu sahne destekleyen ilk iPhone'dur. iPad üzerinde yeni pencere açan uygulama burada da açar. Yeni pencere dış ekranda oluşturulamaz. İstek başarısız olursa geçerli pencerede kal. UIWindowScene.ActivationAction yapılandırma kapanışı bir ActivationConfiguration döndürmelidir; bir istek nesnesini değiştirmeye çalışma. Platform çoklu pencereyi desteklemiyorsa senin verdiğin alternate yoluna git. Dış ekranda bir dokunuşun mutlaka ikinci bir pencere açacağını varsayma. UIRequiresFullScreen hâlâ geçerlidir, ama cihaz açılıp kapanırken arayüz yine boyut değiştirir.
Seçim yerleşimin dışında yaşar
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)
}
Kaydırma konumu için sistem kaydırma görünümünün kendi kaydını kullan, ya da modelin içinde duran bir kimlik kullan. Kapanırken scrollTo ile tepeye dönme. Oynatma için aynı oynatıcı nesnesini kullan. Boyut değişince onu replace ile değiştirme.
Kontrol edilecek olgular
Durumu yukarı taşıma. Seçim, yol, taslak ve oynatıcı uyarlanabilir kabın dışında durur. Ne zaman kullanılır: compact ile regular arasında geçecek her sayfa. Ne zaman kullanılmaz: dış ekran ve iç ekran için ayrı bir durum nesnesi yapma.
Split View. Çubuk, o uygulamanın dış kenarındadır. Sol yarının çubuğu soldadır, sağ yarının çubuğu sağdadır. Ne zaman kullanılır: Device Hub'da kendini bir sola bir sağa koy. Ne zaman kullanılmaz: dikey çubuğun her zaman trailing'de olduğunu varsayma.
UIWindowScene.ActivationAction(iOS 15). KapanışUIWindowScene.ActivationConfiguration(userActivity:)döndürür. Platform çoklu pencereyi desteklemiyorsaalternatekullanılır. Ne zaman kullanılır: iPad üzerinde çoklu pencere zaten destekleniyorsa ve Duo iç ekranında da aynı şey isteniyorsa. Ne zaman kullanılmaz: dış ekranda yeni sahne isteğini başarısızlığı ele almadan zorlama. Yeni pencere dış ekranda oluşturulamaz.UIRequiresFullScreen. Tam ekran isteği yine uygulanır. Ne zaman kullanılır: ekranı zaten tek başına kullanman gerekiyorsa eski ayarı koru. Ne zaman kullanılmaz: bunun açılıp kapanmanın getirdiği boyut değişimini engelleyeceğini sanma. Yerleşim yine değişimi izlemelidir.
Kontrol noktası
İç ekran açıldıktan sonra taslak hangi durum kopyasından geri gelmelidir?
Uygulama bölünmüş ekranın sağındayken dikey çubuk daha çok hangi yanda olur?
Yeni pencere dış ekranda oluşturulabilir mi?
Bu bölümde henüz yapılmayacaklar. Gezinmeyi menteşe açısıyla sıfırlama. Açılıp kapanma zaten boyut ve ayrılmış bölge üzerinden ifade edilir. Açıyı yalnızca gerçek fiziksel etkileşime bırak.
Kaynak: HIG'in ekranlar arası süreklilik ve bölünmüş ekranın dış kenar çubuğu hakkındaki açıklaması; Preparing'in birden çok boyut için test isteği. Tech Talk 111464.
Bölüm 10 · Açı efektleri sürer, çubukları ve sütun sayısını değil
Bitirdiğinde şunları yapabilirsin. Menteşe varken açıyı perde, objektif ya da oyun mekaniğine çevirebilirsin. Menteşe yokken ya da etkileşim hiyerarşiden çıkınca güvenli bir değere dönebilirsin.
Bozulan örnek
if hinge.angle > someRadians { showSidebar = true }
Düz duruş, bölünmüş ekran ve sıradan iPhone, açıyı senin beklediğin gibi vermez. Kenar çubuğunun görünüp görünmeyeceğini Bölüm 3 ve Bölüm 4 zaten alanla yanıtlamıştır.
Yerleşim alana bakar, etkileşim menteşeye bakar
Açıya uyan işler şunlardır: onu fiziksel girdi, oyun düzeneği, çalgı, kamera duruşu, masa etkileşimi ya da açılıp kapanmaya açıkça bağlı bir efekt olarak kullanmak. Uymayan işler şunlardır: kenar çubuğu, sütun sayısı, ızgara adedi, gezinmenin katlanıp katlanmadığı.
SwiftUI'de DeviceHingeContext.hinge değerinin nil olması, bu cihazda menteşe olmadığı anlamına gelir. «Kapandı» anlamına gelmez. Kapalı durum .closed değeridir. angle bir Angle değeridir. status şunlardır: .closed, .partiallyOpen, .fullyOpen.
UIKit'te nil'in anlamı farklıdır. Etkileşim, menteşe güncellemesi verebilen hiyerarşiden çıktığında update.hinge nil olur. Açı radyandır. UIHinge.Status bir de .unknown içerir. Güncelleme sıklığı ve kesinliği sisteme aittir. Belirli bir kare hızına bağlanma. Yalnızca kapalı, yarı açık ve tamamen açık ile ilgileniyorsan status oku.
Yarı açıkken açıyı perdeye çevir, diğer zamanlarda sıfırla
.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)
onHingeChange(isEnabled:_:) içindeki isEnabled varsayılan olarak true'dur. UIKit interaction.isEnabled kullanır. Kapalıyken geri çağrı olmaz ve kaçırılan güncellemeler kuyruğa da girmez. Yeniden açıldığında, geçerli hiyerarşide hâlâ menteşe varsa mevcut durum hemen bir kez daha verilir. Bu kapanış kaçabilir. Döngüsel başvuru olmasın diye [weak self] kullan.
API
onHingeChange(iOS 27.1).(isEnabled:_:). Parametreler eski ve yeni ikiDeviceHingeContextdeğeridir. Ne zaman kullanılır: efekt menteşeyle kesintisiz değişmek zorundaysa. Ne zaman kullanılmaz: kat çizgisinden kaçma, bölünmüş görünüm, ızgara. Onlar ayrılmış bölge ve Arrangement kullanır.DeviceHinge(iOS 27.1).statusartıangle: Angle. Bağlamdakihingeisteğe bağlıdır. Ne zaman kullanılır: nil ise efekti sıfırla, çökme. Ne zaman kullanılmaz: nil değerini «kapandı» diye okuma. Kapalı durum.closeddeğeridir.UIHingeInteraction(iOS 27.1).init { interaction, update in }.update.hingeisteğe bağlı birUIHingedeğeridir.angleradyandır. Durum.unknowniçerir. Ne zaman kullanılır: menteşeyi izleyecek görünüme ekle. Resmi örnek,viewDidLoadiçindeaddInteractionçağırmaktır. Ne zaman kullanılmaz: buradaki nil, etkileşimin güncelleme sağlayan hiyerarşiden çıktığı anlamına gelir. Her zaman «bu telefonda menteşe yok» demek değildir.Güncelleme sıklığı. Başlık dosyası şunu yazar: hız ve ayrıntı düzeyi sistem durumuna göre değişir. Ne zaman kullanılır: açıyı kesintisiz bir girdi olarak al ve kare düşmesini tolere et. Ne zaman kullanılmaz: kalibrasyon ya da yerleşim animasyonu için «saniyede mutlaka N kez» diye bağlanma.
Kontrol noktası
Kenar çubuğunun görünüp görünmeyeceğine
hinge.anglebakarak karar verilebilir mi?SwiftUI'de
hinge == nilne anlama gelir? UIKit'teki nil ne anlama gelir?Yalnızca yarı açık olup olmadığını bilmek istiyorsan açıyı mı okumalısın, status değerini mi?
Bu bölümde henüz yapılmayacaklar. Dış ekranı, senin yerleştirdiğin ikinci bir pencere sanma. O sonraki bölümdedir ve yalnızca çekim sırasında geçerlidir.
Bölüm 11 · Dış ekrandaki ek içerik yalnızca çekim sırasında sistem tarafından gösterilir
Bitirdiğinde şunları yapabilirsin. Ana sahnenin iki ekran arasında nasıl yer değiştirdiğini ve dış ekrana ne zaman ek bir içerik konabileceğini açıklayabilirsin.
Bozulan örnek
Açılınca arayüz iki UIWindow parçasına ayrılır. Biri iç ekranı çizer, biri dış ekranı. Sistem, ikinci tuvalini kapalı yüzde bırakmaz. Simülatörde kamera da yoktur. Bu kod Device Hub içinde doğrulanamaz.
Ana deneyim tek ana sahneyi izler
Açılıp kapanırken ana sahne kendisi öteki yüze geçer. Bütün kritik düğmeler ana arayüzde kalır. Dış ekrandaki ek içerik etkileşimli olabilir, ama az olmalıdır.
Desteklenen tek yol kamera çekim aksesuarıdır. Cihaz açık, uygulama önde ve iç ekran tam ekran, ayrıca sürmekte olan bir çekim oturumu varken sistem onu dış ekrana gösterebilir. Ne zaman ve nerede göstereceğine sistem karar verir. İstediği an da geri alabilir. Kapatmak için isEnabled kullan. Açıp kapatmak için kaydı silme. Durum aynı model üzerinden gider.
Simülatörde kamera yoktur. Bu bölüm gerçek cihaz ister. Kameranın baktığı yön, açılıp kapanma ve dönüşten sonra tersine dönebilir. Kamera seçerken «hangi yüze bakıyor» diye seç. Bkz. Choosing a camera by the direction it faces.
ExternalNonInteractiveAccessory dış ekran ve AirPlay içindir. Duo'nun dış ekranı değildir.
Prompter: ana arayüz yine çekim yapabilir
CameraCaptureAccessory, SDK içinde macOS, tvOS, watchOS, visionOS ve Mac Catalyst için kullanılamaz diye işaretlidir. sceneAccessory ile registerSceneAccessory(_:) iOS 27.0'dan beri vardır. cameraCapture(sceneConfiguration:) ve oturum rolü .windowCameraCaptureAccessory iOS 27.1'dedir. Bağlamı sahne temsilcisine vermen gerekiyorsa userInfo taşıyan fabrika yöntemini kullan. Değeri UISceneConnectionOptions.sceneAccessoryUserInfo içinden geri al.
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:),sceneAccessoryiçine asılır.onAvailabilityChange, sistemin onu göstermeye hâlâ razı olup olmadığını bildirir. Ne zaman kullanılır: prompter, geri sayım, çekilen kişinin görüntüyü görmesi. Kaydı, çekim arayüzüyle aynı görünüme yap. Ne zaman kullanılmaz: ana yoldaki deklanşör, odak, ayarlar. Dış ekran her an kaybolabilir.registerSceneAccessory(iOS 27.0). UIKit:UISceneAccessory.cameraCapture(sceneConfiguration:), rol.windowCameraCaptureAccessory. Dönen registration,unregisterSceneAccessoryiçindir. Ne zaman kullanılır: çekim oturumu sürdüğü sürece ek sahne. Ne zaman kullanılmaz: geçici gizlemek için unregister etme. Etkin durumunu kullan.ExternalNonInteractiveAccessory. Bağlanan ekran / AirPlay. Ne zaman kullanılır: zaten dış büyük ekran destekliyorsan ve içerik etkileşim istemiyorsa. Ne zaman kullanılmaz: Duo'nun kapanan dış ekranı.Simülatör. Device Hub açabilir, kapatabilir, döndürebilir, katlayabilir. Kamera yoktur. Çoğu uygulama uzantısı da Duo simülatöründe çalışmaz. Ne zaman kullanılır: yerleşim, çubuk, sheet, bölünmüş ekran. Ne zaman kullanılmaz: simülatörde çekim aksesuarının doğrulandığını ilan etme.
Kontrol noktası
Çekim yokken bir panoyu dış ekrana sabitleyebilir misin?
Prompter kullanılamazken deklanşör nerede olmalıdır?
Bu bölüm neden yalnızca simülatörde kapatılamaz?
Buraya kadar. Üründe kamera yoksa bu bölümde kod yazmayabilirsin. Önceki Bölüm 0–9, tasarımdan uyarlanabilir yerleşime giden ana yolu zaten kapsar.
Kaynak: Preparing · Improve your app’s camera handling. Tech Talk 111465.
Ek · Karşılaştırma, kırmızı işaretler ve yanıtlar
Önceki on iki bölüm yargıyı sırayla kurar. Burada üç API katmanı, görünmemesi gereken yazımlar ve kontrol noktası yanıtları bir aradadır. Bir bölümü bitirdikten sonra dönüp karşılaştırabilirsin.
Üç katman, atlama
| Katman | Ne zaman yazılır | SwiftUI | UIKit |
|---|---|---|---|
| 1 · Her boyut | Bölüm 3–6. Bütün Apple pencereleri yararlanır | NavigationSplitView, TabView, GridItem(.adaptive), ViewThatFits, AnyLayout, size class, onGeometryChange, sistem .toolbar |
UISplitViewController, UINavigationController, UITabBarController, kap genişliği, trait, layoutSubviews, viewWillTransition |
| 2 · Kat çizgisi ve dikey çubuk bilindiğinde | Bölüm 5–8 içinde, sistem kabı yetmediğinde | reservedRegions, ArrangementView, toolbarVerticalBehavior, axisBehavior, visibilityPriority, presentationPlacement, toolbarVerticalEdge |
reservedRegions(kind:options:), UIArrangementViewController, preferredVerticalBarBehavior, axisBehavior, verticalBarEdge, preferredPlacement |
| 3 · Yalnızca donanımın kendisi | Bölüm 10–11, ürün gerçekten istediğinde | onHingeChange, CameraCaptureAccessory, sceneAccessory |
UIHingeInteraction, UISceneAccessory.cameraCapture, rol .windowCameraCaptureAccessory |
Dikey çubuğun öğe kuralları Bölüm 5'tedir. Çünkü 27.1 SDK sistem çubuklarını ayağa kaldırır. Kat çizgisini sorguladığın için değildir. Kat çizgisi sorgusu Bölüm 7'de başlar.
Kullanılabilirlik, karıştırmamak için
| Sistem | Bu basamakta ilk kez gelenler |
|---|---|
| iOS 26 | ConcentricRectangle, backgroundExtensionEffect(), UIBackgroundExtensionView, UICornerConfiguration |
| iOS 27.0 | visibilityPriority, ToolbarOverflowMenu, topBarPinnedTrailing, presentationPlacement, defaultTabBarPlacement, isTabViewSidebarAvailable, sceneAccessory / registerSceneAccessory |
| iOS 27.1 | Dikey çubuk davranışı ve kenarı, axisBehavior, sıkıştırma davranışı, ayrılmış bölge, Arrangement, menteşe, CameraCaptureAccessory |
UIKit'in leadingItemGroups, pinnedTrailingGroup ve additionalOverflowItems değerleri iOS 16'dan beri kullanılabilir. Duo üzerinde dikey çubuktaki sırayı ve taşmayı onlar üstlenir. Ama özelliklerin kendisi 27.1 ile ortaya çıkmış değildir.
SwiftUI görünürlük önceliği .automatic / .low / .high değerleridir. UIKit karşılığı .standard / .low / .high değerleridir. Sıkıştırma davranışında da bir ad farkı vardır: SwiftUI .prefersToolbarItems, UIKit .prefersBarItems.
Dosyayı açınca önce bunları ara
Sıradan yerleşim için kullanılıyorlarsa değiştirilmelidir:
UIScreen.main.bounds,UIScreen.main.scale, saklanmışUIScreenUIWindow(frame: UIScreen.main.bounds)userInterfaceIdiom == .padya da.phoneUIDevice.current.orientation,statusBarOrientation,interfaceOrientation; bunaeffectiveGeometry.interfaceOrientationdahildirhorizontalSizeClass == .regularolunca «yalnızca iPad» sayfasına girmekbounds.height == 844ya da bilinen cihaz yükseklikleri listesisafeAreaInsets.left * 2if isDuo,if isFolded, kenar çubuğuna ya da sütun sayısına menteşe açısıyla karar vermekSabitlenmiş büyük frame, sabitlenmiş yan sütun genişliği, sabitlenmiş ızgara sütun sayısı
Elle çizilmiş üst çubuk ya da ayrı oluşturulan
UIToolbar/UINavigationBar/UITabBarYalnızca başlığı olan, simgesi olmayan araç çubuğu düğmesi; elle çizilmiş «…» menüsü
Kritik içeriğin kat çizgisinin üstünde ortalanması; compact ile açık durum için ayrı bir durum kopyası
Odağı olmayan
.fillana görsel ve orana göre fill / fit arasında seçim yapmamakBelgede yazılan istisnaların dışında
toolbarVerticalBehavior(.disabled)kullanmak ya da sayfa durumuna göre gidip gelmek
Device Hub'da en az bir kez görülen duruşlar
| Duruş | Beklenti |
|---|---|
| Kapalı dikey | Dış ekran, compact genişlik + regular yükseklik, dikey çubuk |
| Kapalı yatay | İki yön de compact, dikey çubuk |
| Açık dikey | İç ekran regular × regular, yatay çubuk. Üstte ve altta çubuk koruyan tek yaygın duruş budur |
| Açık yatay | regular × regular, dikey çubuk |
| Yarı katlı | Aynı hiyerarşi. Kesin içerik kat çizgisinin üstünde oturmaz. Kayan içerik sayfa boyunca yol değiştirmez |
| İç ekranda bölünmüş ekran, kendin solda ve sağda | Çubuk kendi dış kenarına yapışır. Durum yerinde kalır |
Her sheet ve her popover bu duruşlarda bir kez açılmalıdır. Mağaza ekran görüntüleri App Store Connect içindeki Screenshot specifications belgesine uyar. Sık kullanılan düzenleme dış ekran için 1398×2034, iç ekran için 2007×2853'tür. Yatayda iki kenar yer değiştirir. Bu sayıları yerleşime yazma. Malzemede «Nisan 2027'den itibaren gönderim Duo ekran görüntüsü ister» yazıyorsa kesin tarih, Apple'ın o tarihteki duyurusuna uyar.
Kontrol noktası yanıtları
Bölüm 0. İç ekran dikeyken sistem çubukları üstte ve alttadır. Altı duruş yine tek bir arayüzdür. Açıldıktan sonra hâlâ bir iPhone uygulamasıdır. Yalnızca iç ekran regular × regular olur.
Bölüm 1. 900 pt genişliğindeki ayarlar formu cümleyi ekrana yayar. Etiket ile anahtar boşlukla birbirinden ayrılır. Genişlik sınırlanmalı, fazla alan sonraki hiyerarşi katmanına bırakılmalıdır. Gradyan ortada kalabilir, ödeme düğmesi kalamaz. Listenin tümünü seç komutu listeyle gider, en sağdaki sistem çubuğuyla gitmez.
Bölüm 2. Dağıtım hedefi iOS 18 olarak kalabilir. 27.1 SDK ile derlendikten sonra dikey çubuk görünür. Dört yan boşsa derleme SDK'sı değiştirilir. Cihaz modeli tespit edilip frame çekilmez. Yeni simgeler eski sistemde yoktur, bu yüzden #available içine alınmalıdır.
Bölüm 3. regular genişlik Duo iç ekranında da oluşur, bu yüzden artık iPad demek değildir. İç ekran yön bildirimini dikkate almaz. Geniş mi yüksek mi olduğu, kabın genişliği ile yüksekliği karşılaştırılarak bilinir. Sol ve sağ inset eşit olmayabilir. left * 2 öteki kenarı yanlış hesaplar.
Bölüm 4. Seçim, bölünmüş görünümün dışındaki aynı durum kopyasında durur. Sütun sayısı, kap genişliğinin en küçük öğe genişliğine bölünmesiyle gelir. Burada 2'ye iner. ViewThatFits, hangi dizilişin sığdığını yanıtlar.
Bölüm 5. Yalnızca başlığı olan düğme dikey çubuğa girmez. Bölünmüş görünümde yalnızca ayrıntı sütununun çubuğu dikey olur. Gezinme türündeki sayfa varsayılan olarak önce araç öğelerini taşmaya alır ve sekme çubuğunu korur. Ayrı oluşturulan UIToolbar, gezinme kabının sağladığı çubuk değildir. Sistem onu ayağa kaldırmaz.
Bölüm 6. İç ekranda trailing sheet'in araç çubuğu dikeydir. Sol ve sağ güvenli alan bakışımsızdır, yansıtılamaz. Elle çizilen tam ekran maske, sistem sheet'i gibi kat çizgisinden kaçınmaz.
Bölüm 7. Tamamen düzleşince division etkin olmaz, genişliği 0'dır. Kaydırılabilen yazı paragraf paragraf kaydırılmaz. İç ekran kamerası normalde occlusion kaplamaz. Yalnızca kullanım sırasında kaplar.
Bölüm 8. ZStack, overlay ile eşlenir. Arrangement gezinme tarafından sarılır, tersi yapılmaz. ScrollView içine koymak parçalardan birini ulaşılamaz kılar.
Bölüm 9. Taslak aynı durumdan geri gelir, başka bir görünüm ağacından gelmez. Bölünmüş ekranın sağındaki dikey çubuk sağdadır, yani o uygulamanın dış kenarındadır. Yeni pencere dış ekranda oluşturulamaz.
Bölüm 10. Kenar çubuğu menteşe açısına bakmaz. SwiftUI'deki nil, bu cihazda menteşe olmadığı anlamına gelir. UIKit'teki nil, etkileşimin güncelleme sağlayan hiyerarşiden çıktığı anlamına gelir. Yalnızca yarı açık olup olmadığını bilmek için status okunur.
Bölüm 11. Çekim yokken pano dış ekrana sabitlenemez. Deklanşör ana arayüzde kalır. Simülatörde kamera yoktur. Çekim aksesuarı gerçek cihaz ister.
Resmi girişler
ReservedRegion ve UIKit tarafındaki
UIView.ReservedRegionArrangementView ve
UIArrangementViewControllerTech Talks: 111466 tasarım, 111461 hazırlık, 111462 dikey çubuklar, 111463 duruş ve Arrangement, 111464 çoklu ekran ve sahneler, 111465 kamera
Preparing sayfası ayrıca WWDC 2026 · 278 ve WWDC 2025 · 282 bağlantılarını listeler
En iyi uygulama · Alttaki giriş yerinde kalır, genişlik içeriğe bırakılır
Bitirdiğinde şunları yapabilirsin. Bu uygulamayı önceki kurallarla karşılaştırabilirsin. Hangilerinin Apple'ın biçimiyle yerleştiğini, hangilerinin ürün seçimi olduğunu ve hangilerinin henüz doğrulanmış sayılamayacağını ayırabilirsin.
Bu uygulamanın bir Duo sürümü yoktur. Dağıtım hedefi hâlâ iOS 18'dir. Ekranı doldurmak ve sistem çubuğunun dikey olabilmesi için 27.1 SDK ile derlenir. Açılıp kapanma rotayı değiştirmez, sayfa modelini değiştirmez. Fazladan regular genişlik, «Bugün» ve «Nefes» içindeki sol-sağ sütunlarda kullanılır. Üç birinci düzey giriş kenar çubuğuna çevrilmez. Menteşe açısı, kameranın dış ekranı ve çoklu pencere için ürün gerekçesi yoktur, bu yüzden yazılmaz.
Karşılaştırma
| Öğreticideki yargı | Bu uygulamanın yaptığı |
|---|---|
| Bölüm 0–2: tek arayüz, anahtar SDK'dır | Cihaz adına göre dallanmaz. Yeni API #available ile sarılır. Eski sistem, aynı ilişkinin HStack / VStack biçimini bırakır |
| Bölüm 3–4: sonraki katmanı kap genişliğine göre göster | compact kabuk ne kadar genişlerse genişlesin tek sütundur. regular, 800 pt'te hâlâ tek sütundur. 801 pt'ten itibaren solda ve sağda yan yana durur. En büyük ana sütun 480 pt artı okunabilir ayrıntı 720 pt (toplam 1201 pt) sığdığında daha seyrek bir kademeye girer. 1024 pt hâlâ yan yanadır, cümleyi ekrana yaymak değildir |
| Bölüm 5: sistem çubuğu, ama kenar çubuğu zorunlu değildir | .sidebarAdaptable üzerine defaultTabBarPlacement(.tabBar) eklenir. Üç Tab altta kalır |
| Bölüm 6: sheet konumunu bildirir; elle çizilen maske kat çizgisinden kaçmaz | Sistem sheet'i regular üzerinde niyete göre trailing ya da center gider. compact ödeme katmanı hâlâ kökteki maskedir ve kat çizgisinden kendiliğinden kaçmaz |
| Bölüm 7–8: yalnızca eşli iki içerik parçasını kaydır | Nefes, ödeme sayfasının yatay şeridi ve eşit genişlikte birincil-ikincil sütun ArrangementView split kullanır. Kayan ana sayfa ve ayrıntı, kat çizgisi yüzünden yol değiştirmez |
| Bölüm 9–11: durum süreklidir; donanım API'si isteğe bağlıdır | Seçim ve rota yerleşimin dışındadır. onHingeChange, çekim aksesuarı ve çoklu pencere yapılmaz |
Kök gezinme üç girişi korur
İç ekran kenar çubuğuna dönebilir. Bu ürün o seçimi yapmaz. «Bugün», eğilimler ve nefes hâlâ alttaki Tab'lerdir. Ayarlar «Bugün» içinden ileri itilir. iPad yolu defaultAdaptableTabBarPlacement(.tabBar) kullanır. iOS 27 bir de defaultTabBarPlacement(.tabBar) diye açıklar. İç ekran açıldıktan sonra fazla alan sayfanın içinde belirir: Bugün'de solda kart, sağda ayrıntı; nefeste solda kip, sağda egzersiz.
Yan yana durmak iki koşulu aynı anda sağlamalıdır: bu sayfanın kabuğu regular olmalıdır ve geçerli yatay size class de regular olmalıdır. Böylece dar olan ağaç, paylaşılan seçimi kapmaz. Kabuk compact ise kap çok geniş olsa bile kademe tek sütun kalır.
TabView(selection: adaptiveSectionSelection) {
// 三个 Tab
}
.tabViewStyle(.sidebarAdaptable)
.defaultAdaptableTabBarPlacement(.tabBar)
// iOS 27:content.defaultTabBarPlacement(.tabBar)
adaptiveShellRole == .regular && horizontalSizeClass == .regular
Arrangement yalnızca var olan bir ilişkiyi anlatır
Gezinme dışarıda kalır. Eski sürümün yerleşimi ile 27.1 Arrangement'ı aynı ilişkidir, başka bir sayfa değildir.
| Sahne | Eski ilişki | 27.1 |
|---|---|---|
| Nefes egzersizi | Ana görsel üstte, denetimler altta | .split. Yüksekse üst-alt, genişse sol-sağ olur ve kat çizgisinden kaçınır. Eksen kilitlenmez, menteşe açısına da bakılmaz |
| Ödeme sayfası yatay | Ana görsel ile satın alma yan yana | .split. Eski sistem hâlâ HStack kullanır |
| Eşit genişlikte birincil-ikincil sütun | İki sütunun da kendine ait alanı vardır | .split.axes(.horizontal), oran 0.5. Arka plan kat çizgisinin üzerinden geçebilir. Dokunulabilir içerik Arrangement'ın içindedir |
Sistemin yapacağı işler ve henüz tamam sayılmayanlar
Güvenli alan dört kenardan okunur. Kayan liste ve ayrıntıdaki görsel, yarı katlı diye yer değiştirmez. Sistem sheet'i: compact .automatic gider, regular niyete göre trailing ya da center seçer.
Açıkça yapılmayanlar: nefes ya da kenar çubuğu menteşe açısıyla yerleştirilmez. Kamera yoktur, çekim aksesuarı kaydedilmez. Dış ekranda yeni pencere oluşturulmaz. Altı duruş için sayfa modeli kopyalanmaz. Ürün açılıp kapanmayı gerçekten girdi yapmak isterse ya da bir çekim arayüzü belirirse ancak o zaman yeniden düşünülür.
Henüz tamam sayılamaz: altı duruşun Device Hub içinde elle görülmesi gerekir. Açık simülatör komutları açıp kapatamaz ve yarı katlayamaz. compact ödeme maskesi sistem sheet'i değildir. Tek kapat düğmeli açıklama sheet'inin dikey çubuğu kapatıp kapatmayacağı henüz kararlaşmamıştır. Paylaşılan öğe geçişinin kullandığı sabit üst çubuk hâlâ ölçek katmanının dışında durur. Derlemenin geçmesi, kat çizgisinin ve dikey çubuğun görüldüğü anlamına gelmez.
Yeni sayfa nasıl bağlanır: önce mevcut Tab ya da gezinme yığınına koy, durum dışarıda kalsın. Sonra kap kademesine göre tek sütun ya da yan yana karar ver. Uzun metin yaklaşık 720 pt'te durur. Yalnızca iki içerik parçası zaten yer değiştirmesi gerekiyorsa Arrangement kullan ve #available(iOS 27.1, *) ile eski yerleşimi bırak. Önce sistem toolbar, size class ve okunabilir genişlik yaz. Sonra ayrılmış bölge ve Arrangement yaz. Menteşe ile yan ekran, ürün isteyene kadar yazılmaya devam etmez.
Bu uygulamayla kendi kendini dene
İç ekran genişledikten sonra üç Tab kenar çubuğuna mı yükselmeli, yoksa altta mı kalmalı?
regular kap yalnızca 800 pt iken «Bugün» tek sütun mu olmalı, yan yana mı?
Nefes egzersizindeki küre ile denetimler neden
onHingeChangeyerine.splitkullanır?compact ödeme maskesi yarı katlıyken kat çizgisinin üstünde duruyorsa «sistem sheet'i kaçınır» denebilir mi?
Yanıtlar. Üç Tab altta kalır. 800 pt hâlâ tek sütundur, 801 pt'te yan yana durulur. Küre ile denetimler aynı egzersizin üst-alt ilişkisidir. Bu ilişki split'e bırakılır. Açı yerleşimi belirlemez. Ödeme maskesi sistem sheet'i değildir ve kat çizgisinden kendiliğinden kaçınmaz.
Okumaya devam
Bu fikirleri Stress HRV’te deneyin
App Store'dan indirBu notlar kişisel wellness için ürün düşüncesi paylaşır. Tıbbi tavsiye değildir.