• 23.08.2026 09:17:31
  • Admin Admin

SwiftUI eğitimi kapsamında VoiceOver semantiği, Dynamic Type taşmaları, erişilebilir odak ve XCTest denetimlerini ele alın. iPhone uygulama geliştirme sürecinde erişilebilirliği ölçülebilir bir teslim kriterine dönüştürün.

SwiftUI Eğitiminde Erişilebilirlik: VoiceOver, Odak ve Dynamic Type

iOS Eğitimi İçin Başlangıç: Erişilebilirlik Borcunu Ölçmek

Bir ios eğitimi veya ios kursu içinde erişilebilirliği yalnızca VoiceOver ile ekranı dinleyerek değerlendirmek yetersizdir. Xcode içindeki Accessibility Inspector ile çalışan uygulamaya bağlanın; her ekranda odak sırasını, Role, Label, Value, Hint ve Trait alanlarını kaydedin. Örneğin ödeme ekranında görsel bir kartın VoiceOver tarafından üç ayrı anlamsız öğe olarak okunması, kullanıcıya tek bir "Visa ile biten 4242" bilgisi verilmesi gereken yerde semantik ağacın yanlış kurulduğunu gösterir. İlk hedefi ölçülebilir yapın: kritik akışlarda Inspector'ın Audit sekmesinde kalan issue sayısını ve otomatik XCTest audit hatalarını pull request başına sıfırlayın.

Swift eğitimi ve swift kursu materyallerinde UI testi, yalnızca butona basıldı mı kontrolüne indirgenmemelidir. XCTest'in yerleşik erişilebilirlik denetimini kritik akışların başlangıç testine ekleyin. `accessibilityIdentifier` testin makineye yönelik sabit sözleşmesidir; kullanıcıya gösterilen, yerelleştirilen `accessibilityLabel` yerine identifier ile eleman bulun. Böylece Türkçe metin değiştiğinde test seçicisi kırılmaz, fakat label için ayrı bir beklenti yazabilirsiniz.

import XCTest

final class CheckoutAccessibilityTests: XCTestCase {
    func testCheckoutPassesAccessibilityAudit() throws {
        let app = XCUIApplication()
        app.launch()

        // Önce uygulamanın gerçek kullanıcı akışıyla ödeme ekranına ilerleyin.
        app.buttons["cart.checkout"].tap()

        try app.performAccessibilityAudit()

        let submit = app.buttons["checkout.submit"]
        XCTAssertTrue(submit.exists)
        XCTAssertEqual(submit.label, "Siparişi onayla")
    }
}

Bu audit'i ilk kez eklediğinizde bütün uygulamayı tek testte taramak yerine ekran bazında ilerleyin. `performAccessibilityAudit()`; küçük dokunma hedefleri, erişilemeyen öğeler ve kontrast gibi sorunları raporlar, ancak iş alanına özgü yanlış metni anlayamaz. Bu nedenle Audit sonucuna ek olarak Accessibility Inspector'da "adet" bilgisinin gerçekten "Sepette 3 ürün" şeklinde okunup okunmadığını manuel kabul kriteri yapın. Bu ayrım, bir xcode eğitimi laboratuvarında sık görülen "audit yeşil, deneyim anlaşılmaz" yanılgısını engeller.

SwiftUI Eğitimi: Görsel Hiyerarşiyi VoiceOver Semantiğine Çevirmek

SwiftUI'de `HStack` ve `VStack` görsel yerleşim araçlarıdır; otomatik olarak kullanıcı niyetini ifade etmezler. Ürün satırında dekoratif görseli gizleyin, metin parçalarını tek bir bilgi öğesinde birleştirin ve eylemi ayrı bir `Button` olarak bırakın. Özellikle satırın tamamına `onTapGesture` ekleyip içinde ayrıca bir Button bırakmak, VoiceOver'da iç içe veya belirsiz eylemler doğurur; satırın tamamı seçilebilir olacaksa doğrudan dış kabuğu `Button` yapın.

struct ProductRow: View {
    let product: Product
    let isFavorite: Bool
    let toggleFavorite: () -> Void

    var body: some View {
        HStack(spacing: 12) {
            AsyncImage(url: product.thumbnailURL) { image in
                image.resizable().scaledToFill()
            } placeholder: {
                Color.gray.opacity(0.2)
            }
            .frame(width: 56, height: 56)
            .clipShape(RoundedRectangle(cornerRadius: 8))
            .accessibilityHidden(true)

            VStack(alignment: .leading) {
                Text(product.name)
                Text(product.price.formatted(.currency(code: "TRY")))
                    .foregroundStyle(.secondary)
            }
            .accessibilityElement(children: .combine)

            Spacer()

            Button(action: toggleFavorite) {
                Image(systemName: isFavorite ? "heart.fill" : "heart")
            }
            .accessibilityIdentifier("product.favorite.\(product.id)")
            .accessibilityLabel(isFavorite ? "Favorilerden çıkar" : "Favorilere ekle")
            .accessibilityValue(product.name)
        }
    }
}

Buradaki önemli incelik, `.accessibilityElement(children: .combine)` uygulamasını yalnızca metinsel, pasif alt ağaçta kullanmaktır. Bunu tüm `HStack` üzerine uygularsanız favori butonu bağımsız VoiceOver odağını kaybedebilir. Ayrıca fiyatı `Text("₺199")` gibi ham bir string ile üretmek yerine `formatted(.currency(code: "TRY"))` kullanmak; bölgesel biçimlendirme yanında ekran okuyucunun para tutarını daha anlamlı seslendirmesine yardımcı olur. Accessibility Inspector ile satıra odaklandığınızda beklenen sıra "ürün adı, fiyat" ve ardından "Favorilere ekle, düğme, ürün adı" olmalıdır.

İkonların anlamını yalnızca renk veya SF Symbol şekliyle taşımayın. Başarılı durum için yeşil onay işareti kullanıyorsanız aynı anda `accessibilityLabel("Ödeme doğrulandı")` sağlayın ve görsel durumda `accessibilityDifferentiateWithoutColor` ortam değerini test edin. Bu, renk ayrımına ihtiyaç duyan kullanıcı ayarında yeşil-kırmızı farkı kaybolduğunda durum bilgisinin korunmasını sağlar.

iPhone Uygulama Geliştirme Sürecinde Dynamic Type Taşmalarını Önlemek

iPhone uygulama geliştirme ekranlarında sabit `frame(height:)` ve sabit font boyutu, erişilebilir metin boyutlarında en sık kesilme nedenidir. `@ScaledMetric` ile görselle ilişkili ölçüleri kullanıcının metin ölçeğine bağlayın; yatay alana sığmayan iki eylem için `ViewThatFits` ile dikey alternatifi açıkça tanımlayın. Böylece sistem metni büyüttüğünde sadece label büyümez, avatar ve boşluk da kontrollü biçimde uyum sağlar.

struct AccountActions: View {
    @ScaledMetric(relativeTo: .body) private var iconSize = 28

    var body: some View {
        ViewThatFits(in: .horizontal) {
            actionContent(axis: .horizontal)
            actionContent(axis: .vertical)
        }
        .padding()
    }

    @ViewBuilder
    private func actionContent(axis: Axis) -> some View {
        let layout = axis == .horizontal
            ? AnyLayout(HStackLayout(spacing: 12))
            : AnyLayout(VStackLayout(spacing: 12))

        layout {
            Label("Adresleri yönet", systemImage: "house")
                .imageScale(.medium)
                .frame(minHeight: iconSize + 20)
            Button("Yeni adres ekle") { }
                .buttonStyle(.borderedProminent)
        }
    }
}

Xcode Preview'da yalnızca varsayılan fontla bakmak yerine aynı view'ı erişilebilir boyutta render edin; bunun amacı taşmayı daha kod incelemesinde yakalamaktır. `lineLimit(1)` kullanılan her başlık için bunun ürün gereksinimi mi yoksa tesadüfi bir kısıtlama mı olduğunu sorgulayın: sipariş numarası gibi kaybolmaması gereken içerikte `fixedSize(horizontal: false, vertical: true)` ve çok satır, çoğu zaman ellipsis'ten doğrudur.

#Preview("Accessibility XXL") {
    AccountActions()
        .environment(\.dynamicTypeSize, .accessibility3)
        .frame(width: 320)
}

#Preview("Differentiate without color") {
    PaymentStatusView(status: .failed)
        .environment(\.accessibilityDifferentiateWithoutColor, true)
}

Önce-sonra karşılaştırmasını görünür hale getirin: değişiklikten önce 320 pt genişlikte `.accessibility3` preview ekran görüntüsünde kesilen label sayısını kaydedin; `ViewThatFits` ve esnek satır yüksekliği sonrasında aynı preview ile tekrar sayın. Bu bir benchmark değildir, fakat regression için somut bir görsel sözleşmedir. SwiftUI eğitiminde sık atlanan edge case, kullanıcının hem büyük Dynamic Type hem de Bold Text tercih etmesidir; iki ayar birlikte satır genişliğini artırdığı için sadece normal ağırlıkta yapılan kontrol yeterli değildir.

iOS MVVM Mimarisi ile Doğrulama Hatasında Erişilebilir Odağı Taşımak

Bir form gönderildiğinde hata banner'ını ekranda göstermek, VoiceOver odağını otomatik olarak oraya götürmez. iOS MVVM mimarisi içinde ViewModel doğrulama sonucunu yayınlasın; SwiftUI View ise `@AccessibilityFocusState` ile hangi kontrolün odağa gideceğine karar versin. Bu ayrım önemlidir: ViewModel'in UIKit'e bağımlı `UIAccessibility.post` çağrısı yapması test edilebilirliği azaltır ve ekran yaşam döngüsü tamamlanmadan yapılan anons bazen kaybolur.

import SwiftUI
import UIKit

@MainActor
final class LoginViewModel: ObservableObject {
    @Published var email = ""
    @Published var emailError: String?

    func submit() {
        emailError = email.contains("@") ? nil : "Geçerli bir e-posta adresi girin."
    }
}

struct LoginView: View {
    enum Field: Hashable { case email }

    @StateObject private var model = LoginViewModel()
    @AccessibilityFocusState private var focusedField: Field?

    var body: some View {
        VStack(alignment: .leading) {
            TextField("E-posta", text: $model.email)
                .textInputAutocapitalization(.never)
                .keyboardType(.emailAddress)
                .accessibilityFocused($focusedField, equals: .email)

            if let error = model.emailError {
                Text(error)
                    .foregroundStyle(.red)
                    .accessibilityAddTraits(.isStaticText)
            }

            Button("Giriş yap") { model.submit() }
        }
        .onChange(of: model.emailError) { error in
            guard let error else { return }
            focusedField = .email
            Task { @MainActor in
                await Task.yield()
                UIAccessibility.post(notification: .announcement, argument: error)
            }
        }
    }
}

`Task.yield()` burada rastgele bir gecikme değildir: SwiftUI'ın hata metnini yeni render ağacına yerleştirmesi için ana aktörde bir çalışma turu bırakır; sonra yapılan anons, artık ekranda var olan durumla uyumludur. Yine de hata metnini ve TextField label'ını aynı cümleyle iki kez anons ettirmemeye dikkat edin. Accessibility Inspector'da gönder düğmesine bastıktan sonra odağın E-posta alanına döndüğünü, ardından tek hata anonsunun geldiğini kontrol edin. Bu pratik, form odak yönetimini işleyen bir swift kursu projesinde özellikle tekrar kullanılabilir bir kalıptır.

App Store Yayınlama Öncesi Xcode Eğitimi Odaklı CI Kontrol Listesi

App Store yayınlama öncesinde erişilebilirliği kişisel cihaz kontrolüne bırakmayın. CI'da simulator hedefiyle erişilebilirlik test planını çalıştırın ve sonucu `.xcresult` olarak saklayın; başarısız audit, sürüm adayını bloklayan bir test hatası olmalıdır. Aşağıdaki komutta simulator adını CI imajınızda gerçekten bulunan bir cihazla değiştirin; sabit cihaz adına bağımlı pipeline kurmak, Xcode imajı yenilendiğinde gereksiz kırılma üretir.

xcodebuild test   -scheme StoreApp   -destination 'platform=iOS Simulator,name=iPhone 16'   -only-testing:StoreAppUITests/CheckoutAccessibilityTests   -resultBundlePath build/Accessibility.xcresult

PR şablonuna üç doğrulanabilir madde ekleyin: Accessibility Inspector audit çıktısı incelendi mi, `.accessibility3` preview'da kesilen kritik metin var mı, yeni etkileşimli view'lara benzersiz `accessibilityIdentifier` eklendi mi? Bu kontrol, xcode eğitimi sırasında öğrenilen araç bilgisini teslim sürecine taşır. Kimliklerde görünen ürün adını veya çevrilmiş metni kullanmayın; `checkout.submit` ve `product.favorite.` gibi kararlı adlar, hem UI testlerini hem de hata raporlarını yerelleştirmeden bağımsız tutar.

Sık Sorulan Sorular

SwiftUI eğitimi sırasında VoiceOver label ile accessibilityIdentifier farkı nedir?

`accessibilityLabel` kullanıcıya seslendirilen metindir ve yerelleştirilmelidir; örneğin "Favorilere ekle". `accessibilityIdentifier` ise XCTest seçicisidir ve `product.favorite.42` gibi sabit kalmalıdır. UI testinde identifier ile öğeyi bulun, label'ı ise ayrı bir `XCTAssertEqual` ile kullanıcı metni olarak doğrulayın.

iOS kursu projesinde Dynamic Type taşmasını Xcode ile nasıl test ederim?

View'a Preview içinde `.environment(\.dynamicTypeSize, .accessibility3)` ve dar bir `.frame(width: 320)` uygulayın. Yatay düzen sığmıyorsa `ViewThatFits` ile dikey varyant tanımlayın; sabit `frame(height:)` yerine içerik yüksekliğinin büyümesine izin verin. Kritik ekranlar için bu preview ekran görüntüsünü PR incelemesinde karşılaştırın.

iOS MVVM mimarisi içinde form hatasında VoiceOver odağı nereye taşınmalı?

ViewModel doğrulama hatasını yayınlamalı, View `@AccessibilityFocusState` ile hatalı `TextField`a odak vermelidir. Render tamamlanmadan yapılan anonsları önlemek için odağı atadıktan sonra `Task.yield()` ile bir ana aktör turu bekleyip `UIAccessibility.post(notification: .announcement, argument: error)` çağrısı yapın.

App Store yayınlama öncesi iPhone uygulama geliştirme için hangi erişilebilirlik testi CI'a eklenmeli?

Kritik akışların UI testine `try app.performAccessibilityAudit()` ekleyin ve `xcodebuild test -resultBundlePath ...` ile oluşan `.xcresult` paketini CI artefact'ı olarak saklayın. Audit otomatik sorunları yakalar; ayrıca Accessibility Inspector ile odak sırası ve iş alanına özgü anons metinleri manuel kabul kriteri olarak incelenmelidir.

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.

Opendart Akademi llms.txt