SwiftUI eğitiminde erişilebilirliği görünür hale getirin: Dynamic Type, VoiceOver semantiği, Xcode Accessibility Inspector ve UI testleriyle iPhone uygulama geliştirme süreçlerinde ölçülebilir denetimler kurun.
SwiftUI Eğitiminde Dynamic Type ve VoiceOver Denetim Rehberi
SwiftUI eğitimi için Dynamic Type sözleşmesini kodla kurmak
Dynamic Type desteği yalnızca metne .font(.body) vermek değildir. Tasarım sistemindeki özel fontlar sabit puntoyla oluşturulursa kullanıcının içerik boyutu tercihi atlanır. swiftui eğitimi kapsamında font tanımını merkezi bir katmanda tutun ve özel fontu mutlaka semantic text style'a bağlayın. Bu bağlantı, SwiftUI'nin kullanıcının içerik boyutu kategorisine göre ölçekleme eğrisini seçmesini sağlar.
enum AppTypography {
static func body() -> Font {
.custom("SourceSans3-Regular", size: 17, relativeTo: .body)
}
static func title() -> Font {
.custom("SourceSans3-Semibold", size: 22, relativeTo: .title2)
}
}
struct AccountRow: View {
let name: String
let email: String
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(name)
.font(AppTypography.title())
Text(email)
.font(AppTypography.body())
.foregroundStyle(.secondary)
}
.fixedSize(horizontal: false, vertical: true)
}
}.fixedSize(horizontal: false, vertical: true) burada metnin dikeyde kesilmesini önler; fakat bunu her kapsayıcıya körlemesine eklemeyin. Örneğin yatay bir ScrollView içindeki satırda sınırsız dikey ölçüm, beklenmedik yükseklik hesaplarına neden olabilir. Önce Preview'da Accessibility XXXL benzeri kategoride uzun yerelleştirilmiş içerik deneyin; sonra yalnızca çok satıra izin vermesi gereken metin düğümlerine uygulayın. Bu inceleme, bir ios eğitimi veya swift kursu projesinde erken yapılmadığında genellikle ancak destek talebiyle görünür olur.
Sabit yükseklik ve satır limiti en sık taşma nedenidir. frame(height: 44) ile sarılmış iki satırlı bir etiketi büyütmeye çalışmak yerine, dokunma hedefini ayrı kurun: satırın içeriği doğal yüksekliğini belirlesin, minimum hedef alanı kapsayıcı sağlasın. contentShape, görünür metin kısa kaldığında dahi hit-test alanını tanımlar.
Button {
viewModel.openInvoice(invoiceID)
} label: {
HStack(alignment: .firstTextBaseline, spacing: 12) {
Image(systemName: "doc.text")
.imageScale(.large)
Text(invoiceTitle)
.font(AppTypography.body())
.multilineTextAlignment(.leading)
Spacer(minLength: 8)
}
.padding(.vertical, 10)
.frame(maxWidth: .infinity, minHeight: 44, alignment: .leading)
.contentShape(Rectangle())
}VoiceOver semantiği: iOS MVVM mimarisiyle erişilebilir ekran durumu
VoiceOver kullanıcısı bir ekranı piksel düzeniyle değil, erişilebilirlik ağacındaki odak sırasıyla tüketir. Kartın başlığı, tutarı ve işlem düğmesi tek bir kavramsal öğeyse çocukları tek tek okutmak yerine .accessibilityElement(children: .combine) kullanın. Ancak kartta bağımsız iki eylem varsa combine etmek eylemleri görünmez kılar. Bu ayrım, ios mvvm mimarisi içinde ViewModel'in ekran durumunu erişilebilir açıklamaya dönüştürdüğü net bir sınır gerektirir.
@MainActor
final class TransferRowViewModel: ObservableObject {
let recipientName: String
let amount: Decimal
let status: TransferStatus
var accessibilitySummary: String {
let amountText = amount.formatted(.currency(code: "TRY"))
return "\(recipientName), \(amountText), \(status.accessibilityText)"
}
}
struct TransferRow: View {
@ObservedObject var viewModel: TransferRowViewModel
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(viewModel.recipientName)
Text(viewModel.amount, format: .currency(code: "TRY"))
}
Spacer()
StatusBadge(status: viewModel.status)
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(viewModel.accessibilitySummary)
.accessibilityIdentifier("transfer-row-\(viewModel.recipientName)")
}
}children: .ignore örneğinde tüm görsel çocuklar tek label ile değiştirilir. Bu nedenle label'a tutar, para birimi ve durum dahil edilmelidir; aksi halde ekranda görünen kritik bilgi VoiceOver ağacından düşer. Para formatını elle "\(amount) TL" üretmek yerine FormatStyle.Currency kullanmak, ondalık ayıracı ve farklı locale davranışını sistem formatlayıcısına bırakır. Swift eğitimi materyallerinde sık görülen başka hata, dekoratif durum ikonuna label vermektir. Bilgiyi zaten özet cümlesi taşıyorsa ikonu .accessibilityHidden(true) yapın.
Asenkron yükleme sonlandığında odak kaybolmasın diye tüm ekranı koşullu olarak başka bir view ile değiştirmeyin. Listeyi koruyup yükleme durumunu ekleyin. Yeni içerik gerçekten kullanıcının dikkatini gerektiriyorsa, ana aktörde UIAccessibility.post(notification: .announcement, argument: "3 transfer yüklendi") çağrısı yapılabilir. Bunu her state güncellemesinde çağırmak yanlış bir pratiktir: konuşma kuyruğunu doldurur ve kullanıcının mevcut odağını keser.
Xcode eğitimi pratiği: Accessibility Inspector ile denetlenebilir akış
Xcode'dan uygulamayı Simulator veya fiziksel cihaza bağlayın, sonra Open Developer Tool içinden Accessibility Inspector'u açın. Inspector'daki hedef simgesini kullanarak her öğe için label, value, traits, frame ve Actions alanlarını kontrol edin. Denetimde şu üç sonucu kayıt altına alın: odaklanılabilir öğe sayısı, anlamlı label'ı olmayan öğe sayısı ve rotor ile erişilebilen heading sayısı. Bu sayılar sürümler arasında regresyon yakalamak için ekran görüntüsünden daha kullanışlıdır.
Örneğin ayarlanabilir bir miktar seçici iki ayrı '+' ve '-' düğmesi olarak kalırsa VoiceOver kullanıcısı değer bağlamını her adımda yeniden toplar. Tek bir adjustable element, swipe up/down hareketini doğrudan alan değeriyle eşler. Bu davranışı Accessibility Inspector Actions bölümünde doğrulayın.
struct QuantityPicker: View {
@Binding var quantity: Int
var body: some View {
Text("\(quantity) adet")
.font(.body.monospacedDigit())
.padding(12)
.accessibilityElement()
.accessibilityLabel("Adet")
.accessibilityValue("\(quantity)")
.accessibilityAdjustableAction { direction in
switch direction {
case .increment:
quantity = min(quantity + 1, 99)
case .decrement:
quantity = max(quantity - 1, 1)
@unknown default:
break
}
}
}
}Inspector ile yalnızca VoiceOver label'larını değil, Dynamic Type sonrası çerçeveyi de denetleyin. Bir elementin accessibility frame'i başka bir butonun frame'iyle örtüşüyorsa odak sırası belirsizleşebilir. Özellikle overlay ile tüm kartı kaplayan görünmez Button ve kart içindeki gerçek Button kombinasyonu bunu üretir. Çözüm, iki etkileşimi aynı ZStack'te bindirmek değil, kart navigasyonunu NavigationLink olarak kurup ikincil eylemi ayrı erişilebilir öğe yapmaktır.
iPhone uygulama geliştirme sürecinde erişilebilirlik regresyon testi
Erişilebilirlik denetimini manuel tıklama listesi olmaktan çıkarın. UI testinde içerik boyutunu launch argument ile büyütün, kritik öğeyi identifier üzerinden bulun ve label ile hittable durumunu doğrulayın. Bu yaklaşım, tasarım değişikliği sonrası sabit yükseklik kaynaklı kesilme veya yanlış birleştirilmiş accessibility element sorunlarını CI'da yakalar.
final class InvoiceAccessibilityTests: XCTestCase {
func testInvoiceRowIsReachableAtAccessibilitySize() {
let app = XCUIApplication()
app.launchArguments += [
"-UIPreferredContentSizeCategory",
"UICTContentSizeCategoryAccessibilityXXXL"
]
app.launch()
let row = app.otherElements["invoice-row-INV-42"]
XCTAssertTrue(row.waitForExistence(timeout: 3))
XCTAssertTrue(row.isHittable)
XCTAssertTrue(row.label.contains("vadesi geçmiş"))
}
}Identifier'ı kullanıcıya görünen, yerelleştirilmiş metinden üretmeyin. Yukarıdaki INV-42 gibi kararlı bir domain kimliği kullanın; aksi halde Türkçe, İngilizce veya A/B metin değişikliği test seçicisini bozar. Ayrıca otherElements sorgusu uygulamanın gerçek accessibility tree'sine bağlıdır. Testi yalnızca staticTexts ile yazmak, yanlışlıkla combine edilmiş bir kartın alt metnini bulamadığınızda size nedenini söylemez.
Bir swiftui eğitimi laboratuvarında minimum test matrisi olarak normal boyut, en büyük erişilebilirlik boyutu ve sağdan sola bir locale seçin. Sonuncusu önemlidir: HStack içinde görsel olarak doğru duran fakat sabit .padding(.leading) kullanan düzen, RTL yönünde anlamsal başlangıç kenarını bozabilir. Bunun yerine .padding(.horizontal) veya gerektiğinde .padding(.leading) yerine Edge.Set ile layout direction test edilmiş bir tasarım token'ı kullanın.
Ölçüm: Dynamic Type altında yeniden çizim maliyetini karşılaştırmak
Erişilebilir metin boyutu büyüdüğünde liste satırlarının yüksekliği ve görünür metin miktarı artar; bu, kaydırma sırasında daha fazla layout ve pahalı body hesaplamasını görünür kılar. Sorunu tahmin etmeyin. Instruments'ta Time Profiler şablonuyla fiziksel cihazda aynı 500 satırlı listeyi önce normal, sonra accessibility boyut kategorisinde 20 saniye otomatik kaydırın. Call Tree'de 'Invert Call Tree' ve 'Hide System Libraries' seçenekleriyle uygulama tarafındaki en yüksek self time veren formatter, image decode veya ViewModel hesaplamasını bulun.
Sık görülen örnek, her body değerlendirmesinde tarih formatlayıcısı oluşturmaktır. Önce Time Profiler'da DateFormatter.init çağrı sayısını kaydedin. Sonra formatlamayı değer tipli Date.FormatStyle ile ViewModel'e taşıyın ve aynı senaryoyu yeniden ölçün. Başarı ölçütü 'hissedilir derecede akıcı' değildir; aynı kaydırma süresinde formatter init örnek sayısı ve uygulama kodunun toplam CPU zamanı önce-sonra karşılaştırılır.
struct InvoiceViewData: Identifiable {
let id: UUID
let title: String
let dueDateText: String
init(invoice: Invoice, locale: Locale = .current) {
id = invoice.id
title = invoice.title
dueDateText = invoice.dueDate.formatted(
.dateTime.day().month(.abbreviated).year().locale(locale)
)
}
}
struct InvoiceRow: View {
let item: InvoiceViewData
var body: some View {
VStack(alignment: .leading) {
Text(item.title)
Text(item.dueDateText).foregroundStyle(.secondary)
}
}
}Bu değişiklikte locale değişimine dikkat edin. Uygulama çalışırken dil veya bölge değişimini destekliyorsanız önceden üretilmiş dueDateText bayat kalır. Bu durumda ViewModel verisini Locale.current.identifier değişimini gözleyen bir bağımlılıkla yeniden üretin veya formatlamayı body içinde değil, locale değiştiğinde çalışan ayrı bir dönüşümde yapın. ios kursu ve xcode eğitimi projelerinde bu edge case genellikle sadece ekran yeniden açılınca fark edilir.
App Store yayınlama öncesi erişilebilirlik çıkış kriterleri
app store yayınlama öncesinde her kritik akış için teslim edilebilir bir kontrol listesi oluşturun: giriş, ödeme, hata durumu ve boş durum ekranları Accessibility Inspector ile taransın; en büyük içerik boyutunda UI testi çalışsın; dekoratif görsellerin accessibilityHidden durumu gözden geçirilsin; özel kontrol varsa adjustable action veya custom action denenmiş olsun. Bu listeyi PR şablonuna eklemek, denetimi sürüm sonu manuel işine bırakmaktan daha güvenilirdir.
- PR'da cihaz modeli, içerik boyutu kategorisi ve Inspector bulgusu yazılsın.
- CI'da
xcodebuild test -scheme App -destination 'platform=iOS Simulator,name=iPhone 16'benzeri sabit bir hedefte erişilebilirlik UI testleri çalışsın. - Yeni
accessibilityIdentifierdeğerleri, analitik veya kullanıcı metni yerine kararlı domain kimliğiyle tanımlansın. - Ödeme gibi kritik akışlarda VoiceOver odağının hata mesajına ulaşabildiği fiziksel cihazda doğrulansın.
Bu disiplin iphone uygulama geliştirme ekibinde tasarım, iOS ve QA arasında somut bir ortak dil kurar: 'erişilebilir' gibi yoruma açık bir sıfat yerine, hangi elementin hangi label, trait, action ve test ile doğrulandığı konuşulur. Swift eğitimi içeriğinde de aynı yaklaşım, API bilgisini üretim kalitesinde kabul kriterine dönüştürür.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
SwiftUI eğitiminde Dynamic Type testi Xcode ile nasıl otomatikleştirilir?
XCUIApplication için '-UIPreferredContentSizeCategory' ve 'UICTContentSizeCategoryAccessibilityXXXL' launch argument'lerini ekleyin. Ardından kararlı accessibilityIdentifier ile elementi bulun, waitForExistence, isHittable ve label assertion'larını çalıştırın. Testi normal ve erişilebilirlik boyutunda ayrı senaryolar olarak CI'a koyun.
iOS MVVM mimarisi içinde VoiceOver label hangi katmanda üretilmeli?
Birden fazla alanın tek bir erişilebilir öğede birleştiği kartlarda label'ı ViewModel'in sunum verisi olarak üretin. Para ve tarih için FormatStyle kullanın, View ise accessibilityLabel değerini bağlasın. Ancak label tamamen statik ve sadece görsel dekorasyonu açıklıyorsa View'de kalabilir.
iPhone uygulama geliştirme sırasında Accessibility Inspector ile hangi hatalar bulunur?
Inspector elementin label, value, trait, frame ve action bilgilerini gösterir. En değerli bulgular boş label, birbiriyle örtüşen accessibility frame, yanlış button trait'i, gereksiz dekoratif ikon odağı ve adjustable kontrolün increment/decrement action eksikliğidir.
Swift kursu projelerinde .accessibilityElement(children: .combine) ne zaman kullanılmamalı?
Bir kapsayıcı içindeki çocuklar bağımsız eylemlere sahipse combine kullanmayın. Örneğin kartta hem detay navigasyonu hem de silme düğmesi varsa, combine silme eylemini VoiceOver odağından çıkarabilir. Kartı NavigationLink, silmeyi ayrı Button olarak modelleyin.
AI / LLM Discovery
Bu makale Opendart Akademi iOS / Swift eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


