Renklerini sıkıştırmadan bir SwiftUI gradyan ilerleme çubuğunu göstermek için gradyanı tüm iz kadar geniş tutun ve önde hizalanmış bir maskeyi animasyonla hareket ettirin. Bu macOS örneği, bu davranışı özel bir ProgressViewStyle içinde uygular.

Görev, sentetik bir etkinlik için yeniden boyutlandırılabilir bir masaüstü ilerleme göstergesi oluşturmaktır. Tamamlanma oranı %25 olduğunda sabit renk dizisinin ilk çeyreğini göstermelidir. İlerleme arttıkça sonraki renkler görünmeli, mevcut renkler ise yerlerinde kalmalıdır.

İlerleme çubuğu kısaldığında renkler neden sıkışır?

Apple, LinearGradient öğesinin başlangıç ve bitiş noktalarını doldurduğu şeklin sınırlayıcı dikdörtgenine eşlediğini belgeliyor. Dolayısıyla gradyana ilerlemeyle orantılı bir genişlik vermek, renklerinin dağıtıldığı alanı da değiştirir. Dörtte bir genişliğindeki bir gradyan yine de tüm renk dizisini içerir. LinearGradient — Apple Geliştirici Belgeleri

Bir gösterme efekti için iki boyutu birbirinden ayırın: gradyanın genişliği izin tam genişliğidir; görünür genişlik ise izin genişliğiyle tamamlanan kesrin çarpımıdır. Bu, ayrı bir ilerleme çubuğu API’si değil, belgelenmiş gradyan davranışından türetilmiş bir tasarımdır.

Örnek olarak 0, 0.5 ve 1 normalize edilmiş konumlarında mavi, mor ve turuncu durakları bulunan 320 punto genişliğinde bir iz düşünün. Gradyan gösterilirken mor orta nokta her zaman 160 puntoda kalır:

İlerleme Gradyanın tam genişliği Maske genişliği Görünür sonuç
%0 320 punto 0 punto Yalnızca iz
%25 320 punto 80 punto Gradyanın ilk çeyreği
%50 320 punto 160 punto Mor orta noktasına kadar gradyan
%100 320 punto 320 punto Tüm renk dizisi

Bu, kullanışlı bir tanılama yöntemi sağlar: %25’te sondaki turuncu durak hâlâ gizli olmalıdır. İlerleyen kenarda zaten görünüyorsa ilerlemeye bağlı genişliğin gradyana uygulanıp uygulanmadığını inceleyin.

Eksiksiz bir macOS gradyan ilerleme demosu oluşturma

Dağıtım hedefi macOS 12 veya üzeri olan bir macOS SwiftUI uygulama hedefi kullanın. Aşağıda kullanılan hizalama duyarlı mask(alignment:_:) aşırı yüklemesi macOS 12’den itibaren kullanılabilir. Apple, bu aşırı yüklemenin başka bir görünümün alfa kanalını maskelenen görünüme uyguladığını açıklıyor. mask(alignment:_:) — Apple Geliştirici Belgeleri

Oluşturulan uygulama giriş noktası dosyasını bu eksiksiz programla değiştirin. Oluşturulan içerik görünümü dosyası kullanılmadan kalabilir. Apple’ın uygulama giriş noktası sözleşmesinin gerektirdiği gibi hedefte tam olarak bir @main bildirimi bulundurun. App — Apple Geliştirici Belgeleri

import SwiftUI

@main
struct GradientRevealDemoApp: App {
    var body: some Scene {
        WindowGroup {
            GradientRevealDemo()
        }
    }
}

struct GradientRevealDemo: View {
    @State private var progress = 0.25
    @State private var isIndeterminate = false

    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            ProgressView(
                value: isIndeterminate ? nil : progress,
                total: 1.0
            ) {
                Text("Sample activity")
            } currentValueLabel: {
                if isIndeterminate {
                    Text("In progress")
                } else {
                    Text("\(Int((progress * 100).rounded()))%")
                }
            }
            .progressViewStyle(GradientRevealStyle())

            Slider(value: $progress, in: 0...1, step: 0.01) {
                Text("Sample completion")
            }
            .disabled(isIndeterminate)

            HStack {
                Button("Reset") { progress = 0 }
                Button("25%") { progress = 0.25 }
                Button("50%") { progress = 0.5 }
                Button("Complete") { progress = 1 }
            }
            .disabled(isIndeterminate)

            Toggle("Indeterminate", isOn: $isIndeterminate)
        }
        .padding(24)
        .frame(minWidth: 360, idealWidth: 520, minHeight: 240)
    }
}

@MainActor
struct GradientRevealStyle: ProgressViewStyle {
    func makeBody(configuration: Configuration) -> some View {
        let fraction = configuration.fractionCompleted.flatMap {
            $0.isFinite ? min(max($0, 0), 1) : nil
        }

        VStack(alignment: .leading, spacing: 8) {
            configuration.label

            if let fraction = fraction {
                GeometryReader { geometry in
                    ZStack(alignment: .leading) {
                        Rectangle()
                            .fill(Color.gray.opacity(0.2))

                        LinearGradient(
                            stops: [
                                .init(color: .blue, location: 0),
                                .init(color: .purple, location: 0.5),
                                .init(color: .orange, location: 1)
                            ],
                            startPoint: .leading,
                            endPoint: .trailing
                        )
                        .frame(width: geometry.size.width, height: 14)
                        .mask(alignment: .leading) {
                            Rectangle()
                                .frame(
                                    width: geometry.size.width
                                        * CGFloat(fraction),
                                    height: 14
                                )
                                .animation(
                                    .easeInOut(duration: 0.25),
                                    value: fraction
                                )
                        }
                    }
                    .frame(width: geometry.size.width, height: 14)
                    .clipShape(Capsule())
                }
                .frame(height: 14)
            } else {
                ProgressView()
                    .progressViewStyle(.circular)
            }

            configuration.currentValueLabel
        }
    }
}

Özel stil, ilerleme görünümünün görünümü için belgelenmiş genişletme noktası olan makeBody(configuration:) protokolünü uygular. Sağlanan her iki etiketi de oluşturur ve tamamlanma kesrini yapılandırmadan okur. Stili bu protokolde tutmak, çağıranın ProgressView arayüzünü korur. ProgressViewStyle — Apple Geliştirici Belgeleri

Örnek %25’te başlar. Tekrarlanabilir kontrol noktaları için düğmeleri kullanın, ara değerleri incelemek üzere kaydırıcıyı sürükleyin ve yerleşimi incelemek için pencereyi yeniden boyutlandırın. Bu kontroller sentetik yerel durumu değiştirir; ölçülmüş bir işi temsil etmez. Kaynak incelendi ve genişlik hesaplamaları Python ile kontrol edildi, ancak araştırma ortamında SwiftUI programı derlenmedi veya çalıştırılmadı.

Yerleşim gradyanı nasıl sabit tutar?

GeometryReader, göstergeye ayrılan alanı ölçer. Apple bunu, içeriği kendi boyutuna ve koordinat alanına bağlı olan, tercih edilen boyutu esnek bir kapsayıcı olarak tanımlıyor. .frame(height: 14) değiştiricisini bunun dışına uygulamak, bu esnek kapsayıcıya dikey yığın içinde açık bir yükseklik verir. GeometryReader — Apple Geliştirici Belgeleri

Bu kapsayıcının içinde gradyana ölçülen alanın tam genişliği verilir. Yalnızca maskenin içindeki dikdörtgen çarpılmış genişliği alır. İlerleme değiştiğinde, pencerenin belirli bir boyutu için gradyanın çerçevesi sabit kalır.

Maskenin .leading hizalaması çok önemlidir: küçük maskeyi gradyanın önde gelen kenarına yerleştirir. Bir çerçevenin kendi hizalama parametresi içeriği o çerçevenin içine yerleştirir; maske ile maskelenen görünüm arasındaki hizalamanın yerini almaz. Apple bunları ayrı hizalama sorumlulukları olarak belgeliyor. mask(alignment:_:) — Apple Geliştirici Belgeleri, frame(width:height:alignment:) — Apple Geliştirici Belgeleri

Opak dikdörtgen, bulunduğu her yerde gradyanı gösterir. Maskelemede alfa kullanıldığı için tonu, gösterilen renkleri belirlemez. Dikdörtgeni saydam bir dikdörtgenle değiştirmek gradyanı gizler. Gri izi maskenin dışında tutun; böylece sıfır ilerlemede görünür kalır.

Son kapsül, birleştirilmiş izi ve gösterilen gradyanı aynı dış siluete göre kırpar. Apple, kırpmayı kırpma şeklinin kapladığı kısımları koruma işlemi olarak belgeliyor. Bu örnekte, ilerleyen kenar kapsülün yuvarlak ucuna ulaşana kadar bilerek düz tutulur; kapsül biçimli bir gösterme maskesi ise ilerleyen kenarı yuvarlatır. clipShape(_:style:) — Apple Geliştirici Belgeleri

Pencerenin yeniden boyutlandırılması izin tam genişliğini değiştirir; böylece gradyan yeni genişliğe göre yeniden hesaplanır. Burada “sabit renkler”, pencere yeniden boyutlandırılırken sabit ekran koordinatlarında kalmaları değil, ilerleme güncellemeleri sırasında iz üzerindeki konumlarının sabit olması anlamına gelir.

İlerleme değerlerini, türlerini ve yaşam döngüsü davranışını koruma

Stil, fractionCompleted değerini isteğe bağlı bir Double olarak alır. Apple, sayısal aralığını 0 ile 1 arasında tanımlar ve belirsiz ilerleme veya bir tarih aralığına göre ilerleme için nil kullanır. Genişlik hesabında bu kesri CGFloat türüne dönüştürmek, sayısal sınırı açıkça belirtir. fractionCompleted — Apple Geliştirici Belgeleri

Örnek sonlu kesirleri savunmacı biçimde sınırlayıp eksik veya sonlu olmayan kesirleri standart bir belirsiz ilerleme göstergesine yönlendirir. Bu sonlu olmayan değerleri ele alma davranışı örneğe özgü açık bir politikadır. Kullanılamayan bir kesri sıfır tamamlanma olarak yorumlamaktan kaçınır. Yerleşik bir stil kullanması için iç gösterge açıkça .circular seçer.

Çağrı noktasında sonlu bir tamamlanmış miktar ve sonlu, pozitif bir toplam sağlayın. Değer tabanlı başlatıcı, belirsiz ilerleme için nil kabul eder ve aralık dışındaki değerleri sınırlarına sabitlediğini belirtir. Örnekte toplam 1’dir ve kaydırıcı da aynı aralıkla kısıtlanmıştır. init(value:total:label:currentValueLabel:) — Apple Geliştirici Belgeleri

Üst görünüm ilerleme durumunun sahibidir; stil ise çizimini yapılandırmadan türetir. Durum değiştiğinde SwiftUI, bağımlı görünümleri günceller. Bu nedenle örneğin görsel durumunu korumak için zamanlayıcıya, görünüm yaşam döngüsü geri çağrısına veya arka plan görevine ihtiyacı yoktur. State — Apple Geliştirici Belgeleri

Animasyon, gösterme dikdörtgenine uygulanır. Değer kapsamlı değiştirici, izlenen kesir değiştiğinde animasyon uygular; seçilen çeyrek saniyelik süre, sunuma yönelik örnek bir tercihtir. Görev ilerlemesini hesaplamaz. Arayüzünüz için anında güncellemeler daha uygunsa değiştiriciyi kaldırın. animation(_:value:) — Apple Geliştirici Belgeleri

Zamanlayıcıyla yönlendirilen ilerleme için belgelenmiş bir sınır vardır: Apple, tarihe göre ilerleyen görünümlerin özel stilleri desteklemediğini belirtir. Bu gradyan görünümü için sayısal ilerleme başlatıcısını kullanın ve etkinliğin sahibinin tamamlanan kesri sağlamasını sağlayın. Apple’ın tarihe göre otomatik güncellemelerine ihtiyacınız varsa bu başlatıcıyı desteklenen sunumuyla birlikte kullanın. init(timerInterval:countsDown:label:currentValueLabel:) — Apple Geliştirici Belgeleri

Gösterimi kısa bir tanılama dizisiyle kontrol etme

macOS hedefini oluşturduktan sonra bu kontrolleri uygulayın. Renk eşleme sorunlarını hizalama ve boyutlandırma sorunlarından ayırt ederler:

  1. Reset’i seçin. Gri iz kalmalı ve hiçbir gradyan görünmemelidir.
  2. 25%’i seçin. Yalnızca gradyanın baş tarafındaki renkler görünmelidir. Görünür bir turuncu uç, gradyanın kendisinin kısaltıldığına işaret eder.
  3. 50%’yi seçin. Sağlanan duraklarla gösterim sınırı mor orta noktaya ulaşmalıdır.
  4. Complete’i seçin. Gradyan, turuncu da dâhil olmak üzere izi doldurmalıdır.
  5. %50’de pencereyi yeniden boyutlandırın. Görünür bölüm, yeni boyutlandırılmış izin yarısı olarak kalmalıdır.
  6. Indeterminate seçeneğini etkinleştirin. Sayısal dolgunun yerini yerleşik gösterge almalı, görev ve mevcut değer etiketleri ise görünür kalmalıdır.

Gösterim merkezden dışa doğru büyüyorsa .mask(alignment: .leading) ayarını kontrol edin. Gösterge dikey alanda aşırı yer kaplıyorsa GeometryReader dışındaki çerçeveyi kontrol edin. İlerlemeyi değiştirmek her renk durağını hareket ettiriyorsa tamamlanma çarpanını yalnızca maskenin kullandığını doğrulayın.

Yeniden kullanılabilir kural basittir: gradyanı tüm izin boyutuna göre ayarlayın, ardından ilerlemeyi göstermek için ayrı boyutlandırılmış bir maske kullanın. Bu sorumlulukları birbirinden ayırmak, ilerleme görünümünün belgelenmiş yapılandırmasını korurken kısmi tamamlanmayı, yeniden boyutlandırmayı ve animasyonu anlamayı kolaylaştırır.

Kaynaklar