SwiftUI-Components, iOS 17 ve macOS 14 için hazır SwiftUI view'larından oluşan bir Swift
paketi: buton, kart, grafik, doğrulamalı form alanları, gesture'lar, listeler, overlay'ler,
yükleme göstergeleri ve toast'lar. 2.1.0 sürümü 14 Ekim 2026'da yayınlandı. Bu yazı paketin
çözdüğü sorunu, arkasındaki tasarım kararlarını, commit geçmişinden bir hatayı, kullanımını ve
yapmadığı şeyleri anlatıyor.
İçindekiler
- Sorun
- Tasarım kararları ve alternatifler
- Gerçek bir hata: motoru sınırlamak yerine deseni taramak
- Kullanım
- Sınırlar
Sorun
Çoğu uygulama aynı birkaç view'a ihtiyaç duyar: yükleme durumu gösteren bir buton, bir kart,
küçük bir grafik, e-posta adresini kontrol eden bir alan, bir toast. Bunları yazmak zor değil,
ama ayrıntılar zaman alıyor ve hatalar yerleşimde değil mantıkta çıkıyor: beklenmedik bir
karakterde çöken kart numarası alanı, bir önceki toast'ın zamanlayıcısı yüzünden erken kapanan
toast, adımı sıfır olduğunda çöken slider.
Paket, bir bileşeni sıfırdan yazmak yerine bugün çalışan bir tane isteyen geliştiriciler için.
Her bileşen, initializer ile yapılandırılan düz bir SwiftUI View. Benimsenecek bir tema sistemi
ya da izlenecek bir mimari yok; bir iki view alıp gerisini görmezden gelebilirsiniz. Paketin
hiçbir bağımlılığı yok.
Paketin geçmişi kısa ve sürüm numaralarında görünüyor. Repo Şubat 2026'da açıldı ve 1.0.0 ilk
commit'e etiketlendi. Haziran 2026'da bileşenler kaldırılıp yerine placeholder kod kondu (commit
c7b3799). 14 Ekim 2026'da 2.0.0 olarak geri getirildiler: iOS ve macOS için Swift 6 dil
modunda derlenir hâle geldiler ve yeni testlerin bulduğu hatalar düzeltildi. Birkaç saat sonra
2.1.0; form doğrulama, JSON'dan form, gesture'lar ve kontrast kontrolü ekledi. Bunlar yazarın üç
eski paketinden (SwiftUI-Forms-Kit, SwiftUI-Gesture-Library ve iOSUIComponents) yeniden yazıldı,
o üç repo da arşivlendi. README ve CHANGELOG bu geçmişi aynı şekilde anlatıyor.
Tasarım kararları ve alternatifler
Sade view'lar, mantık value type'larda. Bileşenler durumlarını @State ve @Binding ile
tutuyor. Public referans tipler yalnızca SnackBarManager, BannerManager, PaginatedListViewModel
ve AsyncValidation; hepsi main actor'da. Ekran gerektirmeyen kararlar value type'larda ya da
statik fonksiyonlarda duruyor: Validator, FormSchema, StepFlow, SwipeDirection,
SwipeSequenceMatcher, TransformState ve UnistrokeRecognizer; ayrıca
CreditCardInput.isLuhnValid(_:) ve FlowLayout.arrange(sizes:maxWidth:spacing:) gibi
yardımcılar. Alternatif, view'ları bir view inceleme kütüphanesiyle doğrudan test etmekti. Bu,
test target'ına bağımlılık eklerdi; statik bir fonksiyonu test etmek ise hem daha hızlı hem daha
basit. Bedeli README'de yazıyor: view'ların çoğunun kendi testi yok.
Platform farkları büyük ölçüde tek dosyada. Extensions/Platform.swift, UIKit'e özel
API'lerin neredeyse tamamını (sistem renkleri, haptics, klavye tipi, büyük harf ayarı, wheel
picker'lar) AppKit karşılığına ya da no-op'a eşliyor. Her bileşene #if os(...) serpmek de
çalışırdı, ama her dosyayı okumayı zorlaştırırdı. Bu ara katman sayesinde
TextInputField(keyboardType:), UIKeyboardType yerine paketin kendi KeyboardKind enum'unu
alıyor. İki istisna var. TestimonialCarousel'ın kendi #if os(iOS) dalı var: macOS'ta
sayfalı TabView olmadığı için orada yorum kartları tek tek gösteriliyor. cornerRadius(_:corners:)
ise UIKit tipi (UIRectCorner) alan tek public API; bu yüzden yalnızca iOS'ta var.
Yalnızca derlenen platformu iddia etmek. 2.0.0 manifest'i tvOS, watchOS ve visionOS'u
çıkardı, çünkü paket bu platformlar için hiç derlenmemişti. swift-tools-version da CI'ın
gerçekten kullandığı araç zinciri olan 6.2'ye çıktı. Minimumu iOS 17 ve macOS 14'e yükseltmek,
geri getirilen kodun onChange(of:initial:_:) API'sini #available dalları olmadan
kullanmasını sağladı. Bu kırıcı bir değişiklikti; o sürümün 2.0.0 olmasının nedeni bu.
Snapshot dosyaları yerine render kontrolleri. RenderTests ve GaugeNeedleTests view'ları
ImageRenderer ile render ediyor. Galeri sayfaları açık ve koyu temada çiziliyor; bu, çöken ya
da sıfır boyuta düşen view'ları yakalıyor. Gösterge testi ise ibrenin nereyi gösterdiğini
pikselleri okuyarak kontrol ediyor. Piksel piksel karşılaştıran bir snapshot kütüphanesi
bağımlılık eklerdi ve OS sürümleri arasındaki anti-aliasing farklarında kırılırdı. Ödünleşim şu:
bu kontroller renk ya da yerleşim kaymasını yakalamıyor.
Derlenen örnekler. README'deki kod örnekleri
Examples/ComponentGallery/ComponentGallery/ReadmeSamples.swift dosyasından kopyalanıyor. Bu
dosyayı örnek uygulama derliyor; test target'ı da bir symlink üzerinden derliyor ve bir render
testi onu çiziyor. Bir örnek API'den saparsa build kırılıyor.
Gerçek bir hata: motoru sınırlamak yerine deseni taramak
FormSchema JSON'dan form kuruyor ve bu JSON genelde bir sunucudan geliyor. Bir alan pattern
taşıyabiliyor; yani desen metni güvenilmeyen girdi. Geri izleme (backtracking) yapan bir regex
motoru, (a|a)+ gibi desenlerde üstel süre harcayabiliyor. JSON formları pakete getiren commit
(697c48f), iptal edilemeyen Swift Regex için ölçümü kaydediyor: (a|a)+, 24 "a" ve ardından
gelen "!" üzerinde 5 saniye sürdü.
İlk çözüm, aynı commit'teki PatternSafety adlı bir ön tarama katmanıydı. Deseni çalıştırmadan önce
okuyor, iç içe tekrarları ve belirsiz alternasyonu reddediyor, 256 karakterlik bir değer için en
kötü durumu tahmin ediyordu. Commit mesajı, bu katmanın deseni motorun ayrıştırıcısıyla değil
kendi okumasıyla değerlendirdiğini zaten yazıyordu.
Hata tam da bu boşluktaydı. Sonraki commit e471e0b'nin kaydettiğine göre FormSchema.decode,
yalnızca public API kullanılarak \e{(a|a)+} desenini kabul ediyordu: ön tarama süslü
parantezleri kaçış dizisinin parçası saydı, motor saymadı ve errors(in:) kısa bir girdide
29,6 saniye koştu; her fazladan "a" ile süre ikiye katlanıyordu. 256 sınırı da Character
sayıyordu, oysa motorun işi code unit ile büyüyor; \w*\w*\w*z ön taramadan geçti ve 7,4 saniye
sürdü.
Düzeltme ön taramayı yamamadı. Desen metnini taramak her yavaş deseni hızlı olandan ayıramaz;
bu yüzden e471e0b ön taramayı kaldırdı ve motorun kendisini sınırladı. Artık regex çalıştıran
tek kod PatternMatcher; Validator.matches ve FormSchema ikisi de onu çağırıyor:
- Desenleri
NSRegularExpressionderleyip çalıştırıyor; yani kabul edilen desen, çalışan desenin
ta kendisi.
- Eşleştirme
.reportProgresskullanıyor; bir eşleşme 20 ms sonra ya da çağıranın belirlediği süre sınırında
durduruluyor ve başarısız sayılıyor. Kendi sınırına takılan desen hatırlanıyor ve sonraki
değerlerde hiç çalıştırılmadan başarısız oluyor.
- 1.000 UTF-16 code unit'ten uzun metin eşleştirilmeden reddediliyor; tek bir
errors(in:)
çağrısındaki bütün eşleşmeler 100 ms'lik bir bütçeyi paylaşıyor.
- Şemanın kendisinin de kaynak sınırları var: 1 MB'tan büyük JSON, 200'den fazla alan, 50'den
fazla desenli alan ya da 500'den fazla seçenek reddediliyor.
Daha sonraki 803ee6b commit'i daha küçük bir boşluğu kapattı: bütçe bittikten sonra bile kalan
her alan, başarısız olmadan önce desenini derliyordu. Süre sınırı kontrolü artık derlemeden önce
yapılıyor. Ön tarama katmanı hiçbir sürüme girmedi; PR #6 içinde yazıldı ve kaldırıldı. Çıkarılacak ders
genel: bir filtre girdiyi, onu çalıştıracak motordan farklı bir ayrıştırıcıyla okuyorsa ikisi
farklı sonuca varabilir. Sınır, motorun kendisinde olmalı.
Kullanım
Paketi Xcode'da File › Add Package Dependencies… ile
https://github.com/muhittincamdali/SwiftUI-Components.git adresini girerek ya da
Package.swift içinde from: "2.1.0" ile ekleyin. README'deki hızlı başlangıç, yeni bir
uygulamadaki ContentView.swift dosyasının yerine geçiyor:
swift
1import SwiftUI2import SwiftUIComponents3 4struct ContentView: View {5 @State private var rating = 3.06 7 var body: some View {8 VStack(spacing: 20) {9 Text("Rate this app")10 .font(.headline)11 RatingInput(rating: $rating)12 PrimaryButton("Send rating") {13 print("Rating: \(rating)")14 }15 }16 .padding()17 }18}JSON ile tanımlanan bir form çözülürken yinelenen id'ler, imkânsız sınırlar, geçersiz regex'ler
ve yukarıdaki boyut sınırları kontrol ediliyor:
swift
1func loadContactSchema() throws -> FormSchema {2 try FormSchema.decode(from: Data(contactFormJSON.utf8)) // throws for malformed JSON3}Doğrulamalı bir alan ValidatedValue kullanıyor; hata yalnızca kullanıcı alanı düzenledikten ya
da formu gönderdikten sonra görünüyor:
swift
1@State private var email = ValidatedValue("", .required().and(.email()))Butonlar, grafikler, ödeme formu, snackbar'lar, FlowLayout, JSON formu, gesture'lar ve
ColorContrast için tam örnekler README'nin Usage bölümünde.
Sınırlar
- View'ların çoğunun kendi testi yok. 163 Swift Testing testi mantığı, durum yöneticilerini, uç
girdileri ve galeri sayfalarını kapsıyor; render testleri görünümü değil boyutu ve çökmeyi
kontrol ediyor.
- macOS'ta haptics yok, klavye tipi ve büyük harf ayarı yok sayılıyor, wheel picker'lar stepper
alanına ya da açılır menüye dönüşüyor. cornerRadius(_:corners:) yalnızca iOS'ta var.
Platform Differences makalesi her durumu listeliyor.
CreditCardInputbiçimi, Luhn hanesini, son kullanma tarihini ve CVV uzunluğunu kontrol ediyor.
Bu girdi doğrulaması; ödeme doğrulaması değil.
- Desen süre sınırı duvar saati (wall-clock) süresi; çok yüklü bir cihazda meşru ama pahalı bir
desen zaman aşımına düşebilir. Desenler Swift Regex değil, ICU söz dizimi kullanıyor.
.snackBarHost()ve.bannerHost()paylaşılan yöneticileri kullanıyor; kendi örneğinizi
bunlara vermenin henüz bir yolu yok.
- DocC sitesi, her view'ın
View'dan miras aldığı üyeleri göstermiyor. Bunlar dahil edildiğinde
arşiv 379.410 dosyaya (2,5 GB) çıkıyordu; GitHub Pages için fazla büyüktü (c896b96).
README'deki yol haritası sonraki adımları sıralıyor: Swift Package Index (.spi.yml repoda hazır),
form alanlarından başlayarak tek tek view'lara test ve FormSchema'da koşullu alanlar.
Kaynak kod: github.com/muhittincamdali/SwiftUI-Components

