Ana içeriğe geç
  1. 100 Günde SwiftUI Notları/

20.Gün - SwiftUI Proje-2 Bölüm-1

Bu proje ile beraber SwiftUI’de ilerlemeye devam ediyoruz. VStack, Image, LinearGradient gibi yeni konuları öğreneceğiz. Bu bölümün uygulaması GuessTheFlag isimli bayrak tahmin uygulaması olacak. Fakat uygulamaya geçmeden önce bazı temel bilgileri öğrenmemiz gerekiyor.

SwiftUI Stack Kullanımı #

Ekrandaki birden fazla view öğesi ile ilgileniyorsak üç tane kullanışlı yolumuz var; HStack, VStack ve ZStack bunlar yatay, dikey ve derinlik ile ilgilenirler.

İlk SwiftUI projesi oluşturduğumuzda şu şekilde görünür;

var body: some View {
    VStack {
        Image(systemName: "globe")
            .imageScale(.large)
            .foregroundStyle(.tint)
        Text("Hello, world!")
    }
    .padding()
}

Yukarıdaki kod, tek bir tür view döndürür o da text view’dır. Eğer üst üste iki text view döndürmek isteseydik şöyle yazabilirdik;

var body: some View {
    Text("Hello, world!")
    Text("This is another text view")
}

Veya VStack kullanarak şu şekilde de yazabilirdik;

var body: some View {
    VStack {
        Text("Hello, world!")
        Text("This is inside a stack")
    }
}

Her iki text view bir VStack içine yerleştirilmiştir. Sonuç aynı gibi gözükebilir fakat üç önemli fark vardır;

  1. view’lar arasından ne kadar boşluk bırakabileceğimizi belirtmemize olanak tanır.
  2. view’ları birbirinin soluna, sağına veya ortasına yerleştirip yerleştirelemeyeceği gibi alignment (hizalama) belirtmemize olanak tanır.
  3. VStack kullanmasaydık, SwfitUI view’ları düzenlemekte serbest olacaktı. Örneğin, daha büyük ekranda iki text view yan yana görünebilirdi.

Vstack varsayılan olarak iki view arasına otomatik bir miktar boşluk(spacing) bırakır, ancak stack’deki boşluk miktarını kontrol edebiliriz;

VStack(spacing: 20) {
    Text("Hello, world!")
    Text("This is inside a stack")
}

Varsayılan olarak, VStack view’ları centered (ortalanmış) olarak hizalar, ancak bunu alignment property ile kontrol edebiliriz. Örneğin bu text view’ları .leading kullanarak sola hizalı şekilde kullanabiliriz.

VStack(alignment: .leading) {
    Text("Hello, world!")
    Text("This is inside a stack")
}

VStack ile view’ları dikey olarak hizalamanın yanı sıra, yatay olarak hizalamalar için HStack kullanabiliriz. HStack boşluk ve alignment da dahil olmak üzere VStack ile aynı söz dizilimine sahiptir.

HStack(spacing: 20) {
    Text("Hello, world!")
    Text("This is inside a stack")
}

Dikey ve yatay stack’ler içeriklerini otomatik olarak sığdırır ve kendilerini mevcut alanın ortasına hizalamayı tercih eder. Bu durumu değiştirmek ister ve tüm stack içeriğini bir tarafa itmek istersek bir veya daha fazla Spacer kullanabiliriz. Örneğin VStack’in sonuna bir Spacer eklersek tüm view’ları ekranın en üstüne iter.

VStack {
    Text("First")
    Text("Second")
    Text("Third")
    Spacer()
}

Birden fazla Spacer eklersek, mevcut alan aralarında bölünür. Örneğin, aşağıdaki kod ile boşluğun 1/3’ünü üstte ve 2/3’ünü altta kullanabiliriz.

VStack {
    Spacer()
    Text("First")
    Text("Second")
    Text("Third")
    Spacer()
    Spacer()
}

Ayrıca view’ları derinliğe göre düzenlemek için Ztack kullanabiliriz. ZStack üst üste binen view’lar oluşturur. İki text view söz konusu olduğunda, bu durum okumayı oldukça zolaştıracaktır;

ZStack {
    Text("Hello, world!")
    Text("This is inside a stack")
}

ZStack ’te view’lar üst üste bindiğinden, Spacer yoktur, ancak alignment özelliğine sahiptir. Dolayısıyla ZStack içinde bir büyük ve bir küçük iki görünüm varsa, şu şekilde üste hizalanmasını sağlayabiliriz; ZStack(alignment: .top) {

ZStack , içeriğini yukarıdan aşağıya ve arkadan öne doğru çizer. Bu durumda bir resmimiz ve ardından bazı metinlerimiz varsa ZStack ’in bunları bu sırayla çizeceği ve metni resmin üstüne yerleştireceği anlamına gelir.

VStack ve HStack 3x3’lük bir grid oluşturalım;

SwiftUI 3x3 Grid with Stack

SwiftUI Color ve Frame #

SwiftUI renkler ile işlem yapmak için bize basit ve güçlü özellikler sunuyor.

ZSTack içerisinde bulunan bir text ile başlayalım;

ZStack {
    Text("Your content")
}

text’in arkasına kırmızı bir şey koymak istiyorsak ne yapacağız?

Seçeneklerimizden biri background() modifier’ını kullanmak. Bu modifier aşağıdaki gibi bir renk çizebilir;

ZStack {
    Text("Your content")
}
.background(.red)

ZStack background color

Bu beklediğimiz şeyi yapmış gibi gözüküyor fakat bir fark var. Biz tüm ZStack’in arka plan rengini değiştirmek istesek de yalnızca text’in arka planı rengi değişti.

Aslında yukarıdaki kod ile aşağıdakinin arasında bir fark yok;

ZStack {
    Text("Your content")
        .background(.red)
}

Metnin arkasındaki tüm alanı kırımızı renk ile doldurmak istiyorsak, rengi ZStack içerisine yerleştirmeliyiz. Renk tek başına bir view olarak ele alınır.

ZStack {
    Color.red
    Text("Your content")
}

ZStack color as View

Color.red kendi başına bir view’dır. Bu sebeple şekiller ve text gibi kullanılabilir.

background() modifier’ı kullanırken, SwiftUI .red ’in aslında Color.red anlamına geldiğini anlayabiliyordu. Rengi bağımsız bir view olarak kullandığımızda, Swift’in .red ’in ne anlama geldiğini anlamasını sağlayacak bir context (bağlam) yoktur, bu sebeple Color.red şeklinde kullanmalıyız.

Renkler otomatik olarak mevcut tüm alanı kaplar, ancak belirli boyutlar istemek için frame() modifier’ını kullanabiliriz. Örneğin, aşağıdaki gibi 200x200 kırmızı bir kare isteyebiliriz.

Color.red
    .frame(width: 200, height: 200)

color frame

frame() in değerlerini istediğimiz gibi değiştirebiliriz. Örneğin, minimum 200pt genişliğinde fakat mümkünse en fazla genişliği kullansın ve en fazla 200pt yüksekliğinde bir frame’i şu şekilde oluşturabiliriz.

Color.red
    .frame(minWidth: 200, maxWidth: .infinity, maxHeight: 200)

SwiftUI Frame width height

SwiftUI bize Color.blue , Color.green , Color.indigo vb. gibi bir dizi yerleşik renk sunar. Ayrıca amaca yönelik kullanabileceğimiz renklerimizde bulunmaktadır.

Örneğin; Color.primary SwiftUI’deki metnin varsayılan rengidir ve kullanıcının cihazının açık modda mı yoksa koyu modda mı çalıştığına bağlı olarak siyah veya beyaz olacaktır. Ayrıca, cihaza bağlı olarak siyah veya beyaz olan Color.secondary de vardır, ancak bu hafif bir şeffaflığa sahiptir, böylece arkasındaki rengin bir kısmı parlar.

İstersek kendi özel rengimizi de şu şekilde oluşturabiliriz;

Color(red: 1, green: 0.8, blue: 0)

Color.red kullanımında, ekranın altında ve üstünde beyaz alanların kaldığını göreceğiz. Bu alan kasıtlı olarak boş bırakılmıştır. Kalan kısım (yani kırmızı alan) safe area olarak isimlendirilmektedir. iPhone’daki çentik (notch) veya dynamic island tarafından kırpıla endişesi olmadan bu alanda çizim yapabiliyoruz.

İçeriğimizin safe area ‘yı da kullanmasını istiyorsak .ignoresSafeArea() modifier’ını kullanabiliriz.

ZStack {
    Color.red
    Text("Your content")
}
.ignoresSafeArea()

swiftui ignores safe area

Önemli hiçbir içeriğin safe area dışına yerleştirilmemesi çok önemlidir, çünkü kullanıcının içeriği görmesini oldukça zorlaştırır.

İçeriğimiz sadece dekoratif amaçlı ise, buradaki arka plan rengimiz gibi, onu güvenli alanın dışına uzatmamızda bir sakınca yoktur.

background() modifier ile .red .green gibi sabit renkleri kullanmanın yanı sıra material de kullanabiliriz. material sayesinde buzlu cam efekti uygulayarak derinlik efektleri oluşturabiliriz.

Bunu çalışırken görelim;

ZStack {
    VStack(spacing: 0) {
        Color.red
        Color.blue
    }   

    Text("Your content")
        .foregroundStyle(.secondary)
        .padding(50)
        .background(.ultraThinMaterial)
}
.ignoresSafeArea()

swiftui background material

SwiftUI Gradient #

SwiftUI bize dört çeşit gradient sunmaktadır ve renkler gibi bunlar da kullanıcı arayüzüne çizilebilen view’lardır.

Gradient’i oluşturan bileşenler;

  • gösterilecek renklerden oluşan array
  • boyut ve yön bilgileri
  • Kullanılacak gradient türü

Örneğin, linear gradient tek bir yöne gider, bu sebeple ona aşağıdaki gibi bir başlangıç noktası belirleriz.

LinearGradient(colors: [.white, .black], startPoint: .top, endPoint: .bottom)

swiftui linear gradient

İstersek gradientimize ara noktalar da belirleyebiliriz. Örneğin, gradient’in başlangıçtan kullanılabilir alanın %45’ine kadar beyaz, ardından kullanılabilir alanın %55’inden itibaren siyah olmasını sağlayabiliriz.

LinearGradient(stops: [
    Gradient.Stop(color: .white, location: 0.45),
    Gradient.Stop(color: .black, location: 0.55),
], startPoint: .top, endPoint: .bottom)

swiftui gradient stop

Bu daha keskin bir gradient oluşturacaktır, merkezdeki küçük bir alana sıkışacaktır.

Swfit burada gradient stop oluşturduğumuzu biliyor, bu sebeple kısayol olarak Gradient.Stop yerine sadece .init yazabiliriz.

LinearGradient(stops: [
    .init(color: .white, location: 0.45),
    .init(color: .black, location: 0.55),
], startPoint: .top, endPoint: .bottom)

Alternatif olarak radial gradinet ile daire şeklinde dışa doğru gradientler oluşturabiliriz. Bu sebeple yön belirtmek yerine başlangıç ve bitiş yarı çapı belirtiriz. Örneğin;

RadialGradient(colors: [.blue, .black], center: .center, startRadius: 20, endRadius: 200)

swiftui radial gradient

Son olarak açısal gradient olarak adlandırılan bir gradient çeşidi daha vardır. Bazı yerlerde konik gradient olarak da adlandırılabilir.

Örnek olarak şunu kullanabiliriz;

AngularGradient(colors: [.red, .yellow, .green, .blue, .purple, .red], center: .center)

swiftui angular gradient

Tüm bu gradient türleri basit renkler yerine stop’lara sahip olabilir. Ayrıca, layout’larımızda bağımsız view’lar olarak çalışabilir veya bir modifier’ın parçası olarak kullanılabilirler.

SwiftUI ayrıca yukarı türlerden başka daha basit olan dördüncü gradient türünü de barındırır. Fakat üzerinde herhangi bir kontrolümüz yoktur ve ayrıca bunları tek tek view’lar yerine yalnızca background ve foreground stilleri olarak kullanabiliriz.

Bu tür, herhangi bir renkten sonra .gradient eklenerek oluşturulur.

Text("Your content")
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .foregroundStyle(.white)
    .background(.black.gradient)

swiftui easy gradient

Bu tür bir gradient çok incedir ancak neredeyse hiç uğraşmadan tasarımımıza farklı bir hava katmamımızı sağlar.

SwiftUI Butonlar ve Resimler #

Bir buton oluşturmanın en basit yolu, butonun title’ını ve butona dokunulduğunda çalıştırılması için gereken bir clousure’u sağlamaktır.

Button("Delete selection") {
    print("Now deleting…")
}

Elbette action için bir closure yerine bir fonksiyon da kullanabiliriz.

struct ContentView: View {
    var body: some View {
        Button("Delete selection", action: executeDelete)
    }

    func executeDelete() {
        print("Now deleting…")
    }
}

Butonların görünümünü özelleştirmenin birkaç farklı yolu vardır. İlk olarak butona iOS’un hem görsel hem de ekran okuyucular için görünümünü ayarlamak üzere kullanabileceğimiz bir rol ekleyebiliriz. Örneğin silme butonumuzun destructive bir rolü olduğunu söyleyebiliriz.

Button("Delete selection", role: .destructive, action: executeDelete)

İkinci olarak butonlar için yerleşik stillerden birini kullanabiliriz: .bordered ve .borderedProminent

VStack {
    Button("Button 1") { }
        .buttonStyle(.bordered)
    Button("Button 2", role: .destructive) { }
        .buttonStyle(.bordered)
    Button("Button 3") { }
        .buttonStyle(.borderedProminent)
    Button("Button 4", role: .destructive) { }
        .buttonStyle(.borderedProminent)
}

swiftui button styles

bordered butonun renklerini özelleştirmek için tint() modifier’ını kullanabiliriz.

Button("Button 3") { }
    .buttonStyle(.borderedProminent)
    .tint(.mint)

Apple, çok fazla prominent (belirgin) buton kullanılmamasını açıkça tavsiye etmektedir, çünkü her şey belirgin olduğunda hiçbir şey belirgin değildir.

Tamemen özel bir buton yapmak istiyorsak, ikinci bir closure kullanarak özel bir title iletebiliriz

Button {
    print("Button was tapped")
} label: {
    Text("Tap me!")
        .padding()
        .foregroundStyle(.white)
        .background(.red)
}

swiftui custom button

SwiftUI uygulamalarımızdaki resimleri işlemek için özel bir Image türüne sahiptir. Image oluşturmanın üç yolu vardır;

  • Image(”pencil”) , projemize eklediğimiz “Pencil” isimli bir görüntüyü getirecektir.
  • Image(decorative: “pencil”) aynı görüntüyü yükler, ancak ekran okuyucuyu etkinleştiren kullanıcılar bunu okumaz. Bu, önemli bilgiler iletmeyen resimler için kullanışlıdır.
  • Image(systemName: "pencil") iOS’ta yerleşik olarak bulunan pencil (kalem) simgesini getirecektir. Bu, Apple’ın SF Symbols simge koleksiyonunu kullanır.

Varsayılan olarak, ekran okuyucu (screen reader) etkinleştirilmişse resmimizin adını okuyacaktır, bu sebeple kullanıcının kafasını karıştırmak istemiyorsak resimlerimize net isimler vermeliyiz. Yada Image(decorative:) initializer’ını kullanmalıyız.

Butonlar ile birlikte Image ’i kullanabiliriz;

Button {
    print("Edit button was tapped")
} label: { 
    Image(systemName: "pencil")
}

Buton içinde aynı anda hem metin hem de görüntü istiyorsak iki seçeneğimiz vardır. Birincisi her ikisini de doğrudan Button ’a sağlamaktır.

Button("Edit", systemImage: "pencil") {
    print("Edit button was tapped")
}.buttonStyle(.bordered)

swiftui button with image

Fakat daha özel bir şey istiyorsak, SwiftUI’nin Label adındaki özel türünü kullanabiliriz.

Button {
    print("Edit button was tapped")
} label: {
    Label("Edit", systemImage: "pencil")
        .padding()
        .foregroundStyle(.white)
        .background(.red)
}

swiftui button with custom label

SwiftUI Alert Message #

Önemli bir şey olduğunda, kulanıcıyı bilgilendirmenin yaygın bir yolu alert kullanmaktır. alert bir başlık (title), mesaj ve duruma bağlı olarak bir veya iki butondan oluşur.

alert’i bir değişkene atayıp myAlert.show() diyemeyiz, çünkü bu durum SwiftUI’nin mantığına biraz terstir.

Bunun yerine, aşağıdaki gibi alert’in gösterilip gösterilmediğini izleyen bir state oluştururuz.

@State private var showingAlert = false

Daha sonra alert’i kullanıcı arayüzüne ekleriz ve alert’in gösterilip gösterilmeyeceğini belirlemek için bu state’i kullanmasını söyleriz. SwiftUI showingAlert ’i izleyecek ve true olur olmaz alert’i gösterecektir.

Tüm bunları bir araya getirerek kodumuzu oluşturalım;

struct ContentView: View {
    @State private var showingAlert = false

    var body: some View {
        Button("Show Alert") {
            showingAlert = true
        }
        .alert("Important message", isPresented: $showingAlert) {
            Button("OK") { }
        }
    }
}

swiftui show alert

Yukarıdaki kod alert’i buton’a ekler, fakat burada .alert() modifier’ının nerede kullanıldığının bir önemi yoktur. Tek yaptığımız, bir alert’in var olduğunu ve showingAlert ’in true olduğunda gösterildiğini söylemektir.

.alert() modifier’a yakından bakalım;

alert("Important message", isPresented: $showingAlert)

İlk kısım alert’in başlığıdır. SwiftUI, alert kapatıldığında showingAlert’i otomatik olarak false yapacağından two-way binding vardır.

Şimdi de buton’a bakalım;

Button("OK") { }

Burada boş bir closure var. Yani butona basıldığında çalışacak herhangi bir işlevsellik eklemiyoruz. Yine de bu önemli değil, çünkü bir alert içindeki herhangi bir buton alert’i otomatik olarak kapatacaktır. Bu closure alert’i kapatmanın ötesinde herhangi bir ekstra işlevsellik eklememize izin vermek için oradadır.

Alert’e daha fazla buton ekleyebiliriz. Ayrıca burası her butonun ne işe yaradığının açıkça belirtmemiz için özellikle iyi bir yerdir.

.alert("Important message", isPresented: $showingAlert) {
    Button("Delete", role: .destructive) { }
    Button("Cancel", role: .cancel) { }
}

swiftui two button

Son olarak, başlığımızla beraber aşağıdaki gibi bir closure kullanarak mesaj metni ekleyebiliriz

Button("Show Alert") {
    showingAlert = true
}
.alert("Important message", isPresented: $showingAlert) {
    Button("OK", role: .cancel) { }
} message: {
    Text("Please read this.")
}

swiftui alert with message


Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.

Bu yazı, SwiftUI Day 20 adresinde bulunan yazılardan kendim için aldığım notları içermektedir. Orjinal dersi takip etmek için lütfen bağlantıya tıklayın.