· Workflow · 9 dakika okuma

Fastlane olmadan SwiftUI için App Store ekran görüntülerini otomatikleştirmek (2026)

Fastlane olmadan SwiftUI için App Store ekran görüntülerini otomatikleştirmek (2026)
TL;DR. Fastlane, iOS mağaza ekran görüntülerini otomatikleştirmenin tek yolu değil — sadece en ünlüsü. Apple ihtiyacın olan her şeyi yerel olarak sunuyor: canlı uygulamayı yakalamak için XCUITest + XCUIScreenshot, aynı işi daha az standart kodla yapan yeni Swift Testing eklenti API'si ve simülatör çalıştırmadan bir SwiftUI görünümünü doğrudan PNG'ye dönüştüren ImageRenderer. swift-snapshot-testing ve AppScreenshotKit gibi açık kaynak araçlar bunları sarmalıyor. Hiçbiri pazarlama karuselini oluşturmuyor — çerçeveler, metinler, arka planlar, 50 dilde metin. Bu ayrı bir adım ve Mokbi tam olarak burada devreye giriyor: yakaladığın ekran görüntülerini kompoze ediyor ve yerelleştiriyor. Onları yakalamıyor.

Fastlane'in snapshot'ı "App Store ekran görüntülerimi otomatikleştir" sorusuna varsayılan yanıttır ve haklı bir nedenle — olgun ve CI dostudur. Ama aynı zamanda bir Ruby araç zinciri, bir Snapfile, test hedefine kopyaladığın bir SnapshotHelper.swift ve altında zaten sahip olduğun aynı XCUITest API'lerinin ince bir sarmalayıcısı olan bir şey için önemli miktarda kurulum demektir. SwiftUI'de geliştirme yapıyorsan ve iOS projene bir Ruby bağımlılığı eklemek istemiyorsan, ekran görüntülerini yalnızca Xcode ve Swift kullanarak otomatikleştirebilirsin. Bu yazı, yapıştırabileceğin kodlarla birlikte 2026'daki yerel seçeneklerin dürüst haritasıdır ve hangi aracın hangi işi yaptığına dair net bir çizgi çizer. Karşılaştırmalı artı-eksiler için bkz. Mokbi ve Fastlane karşılaştırması.

Önce bir çerçeveleme yapalım, çünkü çok fazla karışıklığı önlüyor: bir kareyi yakalamak (uygulamayı bir duruma götürüp ekranda görünenin PNG'sini kaydetmek) ile bir mağaza ekran görüntüsü kompoze etmek (o kareyi bir cihaz mockup'ı içinde, bir başlıkla, bir arka planla ve çevrilmiş metinlerle, gereken her boyutta dışa aktarmak) iki farklı sorundur. Bu yazıdaki her şey birinciyi çözüyor. Hiçbiri ikinciyi çözmüyor.

Fastlane'i neden es geçmeli?

Fastlane'de yanlış bir şey yok. Geliştiricilerin yerel yola yönelmesinin nedenleri ideolojik değil, pratik:

  • Döngüde Ruby yok. Fastlane, kendi sürüm karmaşasına sahip bir Ruby gem'idir (Bundler, gem çakışmaları, Ruby güncellemesi sonrası ara sıra CI kırılmaları). Projen zaten saf Swift'se, bu tek bir iş için bakımını yaptığın koca bir çalışma zamanı demektir.
  • Test hedefine zaten sahipsin. snapshot arka planda kullanıcı arayüzü testleri üretir. Zaten kullanıcı arayüzü testleri yazıyorsan, bir ekran görüntüsü yakalamak iki ek satırdır — yeni bir araca gerek yok.
  • SwiftUI matematiği değiştirdi. ImageRenderer ile Fastlane'in yapamadığı bir şeyi yapabilirsin: uygulamayı hiç başlatmadan bir ekranı PNG'ye render etmek — Fastlane her zaman simülatörü yönlendirir.
  • CI'da daha az hareketli parça. Ekran görüntüleri üreten sade bir xcodebuild test, xcodebuild'a shell çağrısı yapıp ardından bir sonuç paketini işleyen bir Fastlane lane'inden akıl yürütmesi daha kolaydır.

Madalyonun öbür yüzü: Fastlane can sıkıcı kısımları (.xcresult paketinden ekleri toplama, yerel × cihaz başına düzenleme, bir HTML önizleme oluşturma ve deliver ile yükleme) tek bir komutta paketler. Yerel yola gidersen bu toplama işinin bir kısmı senin sorunun olur. Bu takas kararın tamamıdır ve bilinçli olarak verilmeye değer.

Seçenek 1: XCUITest + XCUIScreenshot (canlı uygulamayı yakala)

Bu, Fastlane'in kendisinin de üzerine inşa edildiği temeldir. Bir kullanıcı arayüzü test hedefi gerçek uygulamanı simülatörde başlatır, herhangi bir kullanıcı arayüzü testiyle aynı bulucu/dokunma API'siyle yönlendirir ve XCUIScreen.main tüm cihaz ekranını yerel çözünürlükte yakalar. O ekran görüntüsünü bir XCTAttachment içine sarmalar ve test geçtiğinde bile Xcode'un onu tutması için lifetime = .keepAlways ayarlarsın (varsayılan, başarı durumunda ekleri siler).

// ScreenshotUITests.swift  — runs in a UI test target
import XCTest

final class ScreenshotUITests: XCTestCase {
    func testCaptureCarouselScreens() {
        let app = XCUIApplication()
        // A launch argument your app reads to seed demo data + hide
        // anything that would make a noisy screenshot (debug banners, etc).
        app.launchArguments += ["-FASTLANE_SNAPSHOT", "NO", "-SCREENSHOT_MODE", "YES"]
        app.launch()

        // Drive the app to the state you want, then capture the WHOLE screen.
        snapshot("01_Home")

        app.buttons["openLibrary"].tap()
        snapshot("02_Library")
    }

    private func snapshot(_ name: String) {
        // XCUIScreen.main captures the full device screen at native
        // resolution — exactly what App Store Connect wants.
        let shot = XCUIScreen.main.screenshot()
        let attachment = XCTAttachment(screenshot: shot)
        attachment.name = name
        attachment.lifetime = .keepAlways // or Xcode discards it when the test passes
        add(attachment)
    }
}

Ekran görüntüleri .xcresult paketinin içine düşer. Çalıştırmadan sonra bunları çıkarırsın — ya sonuç paketini Xcode'da açıp sürükleyerek çıkararak ya da CI'da paketi gezip klasöre adlandırılmış PNG'ler yazan xcparse / xc-screenshot gibi bir araçla. Bu çıkarma adımı, Fastlane'in senin için otomatikleştirdiği tam olarak o kısımdır ve yerel yola giderek üstlendiğin ana şeydir.

Kod değişikliği olmadan yerelleştirme. Buradaki güzel numara Xcode test planı: bir test planı oluştur, dil/bölge başına bir yapılandırma ekle (her biri uygulama dilini + bölgesini ayarlar) ve aynı test yapılandırma başına bir kez çalışır. Test koduna dokunmadan yerelleştirilmiş ekran görüntüleri — her yerelde render edilmiş uygulama arayüzü — elde edersin. App Store Connect'in gerektirdiği çözünürlüklere ulaşmak için 6,9 inç iPhone sınıfı bir simülatör ve 13 inç iPad sınıfı bir simülatör üzerinde çalıştır.

Artılar ve eksiler:

  • Gerçek uygulama pikselleri. Simülatörün tam çözünürlüğünde gerçek çalışan görünümler — yazı tipleri, tema, dinamik veri, hepsi yerel.
  • Fastlane yok, Ruby yok. Saf Xcode + Swift. Herhangi bir CI'da xcodebuild test altında çalışır.
  • Yerel, kod değil test planı yapılandırmasıdır. Dil başına bir yapılandırma ekleyip yeniden çalıştır; Swift'te yerel başına dallanma yok.
  • Çıkarmayı sen üstlenirsin. PNG'leri .xcresult paketinden çekmek sana kalmıştır (tek seferlik bir betik, ama gerçek).
  • Bakım maliyeti. Bulucular (buttons["openLibrary"], dokunmalar) arayüz değiştiğinde bozulur, tıpkı herhangi bir kullanıcı arayüzü testi gibi.

Seçenek 2: Swift Testing eklentileri (modern, daha hafif sürüm)

Swift Testing (Apple'ın Xcode 26 ile gelen makro tabanlı çerçevesi), XCTAttachment'ın yaptığını daha az tören ve daha güzel bir çağrı noktasıyla yapan bir Attachment API'si ekledi. Uygulamayı yine XCUIApplication ile yönlendirir ve XCUIScreen.main.screenshot() ile yakalarsın — bunlar XCUITest'te yaşıyor — ama görüntüyü bir @Test fonksiyonundan Attachment.record(...) ile kaydedersin. Eklenti API'si görüntünün nasıl üretildiğini umursamaz; uyumlu herhangi bir görüntü tipini kabul eder ve testin sonuç paketine yazar.

// ScreenshotTests.swift  — Swift Testing (Xcode 26+)
import Testing
import XCTest // still needed for XCUIApplication / XCUIScreen

@MainActor
struct ScreenshotTests {
    @Test func homeAndLibrary() async throws {
        let app = XCUIApplication()
        app.launchArguments += ["-SCREENSHOT_MODE", "YES"]
        app.launch()

        // Attachment.record attaches to THIS test's result bundle.
        let home = XCUIScreen.main.screenshot()
        Attachment.record(home.image, named: "01_Home.png")

        app.buttons["openLibrary"].tap()
        let library = XCUIScreen.main.screenshot()
        Attachment.record(library.image, named: "02_Library.png")
    }
}

İşlevsel olarak bu, Seçenek 1 ile aynı canlı uygulama piksellerini yakalar — aynı temel ekran yakalamadır. Kazandığın şey, projen zaten Swift Testing'e geçtiyse daha temiz Swift Testing ergonomisidir (parametreli testler, varsayılan olarak async, XCTestCase alt sınıflaması yok). Kaçamadığın şey ise aynı çıkarma adımıdır: PNG hâlâ sonuç paketinde yaşar ve yükleme için hâlâ çıkarılması gerekir. Bunu zaten Swift Testing kullanıyorsan tercih et; aksi halde Seçenek 1 sonuç olarak aynıdır.

Seçenek 3: ImageRenderer (simülatör çalıştırma yok)

Bu, Fastlane'in yapısal olarak sunamayacağı seçenektir. ImageRenderer (SwiftUI, iOS 16+ / macOS 13+) bir SwiftUI görünümü alır ve hiyerarşisini doğrudan bir UIImage / CGImage'a render eder — uygulama başlatma yok, simülatör kullanıcı arayüzü otomasyonu yok, bozulacak bulucu yok. Ona App Store piksel boyutlarına tam oturan bir görünüm verir ve PNG bayt'larını geri okursun.

// Render a SwiftUI view straight to a PNG — no simulator UI run.
import SwiftUI

@MainActor
func renderScreen() -> Data? {
    let view = HomeScreen()                 // your real SwiftUI screen
        .frame(width: 1320, height: 2868)   // 6.9-inch iPhone, exact pixels at 1x

    let renderer = ImageRenderer(content: view)
    renderer.scale = 1                       // size is already in target pixels
    renderer.isOpaque = true                 // App Store rejects alpha channels

#if canImport(UIKit)
    return renderer.uiImage?.pngData()
#else
    guard let cg = renderer.cgImage else { return nil }
    let rep = NSBitmapImageRep(cgImage: cg)
    return rep.representation(using: .png, properties: [:])
#endif
}

Mağaza çıktısı için iki şey önemli. isOpaque = true ayarla — App Store Connect alfa kanalı içeren ekran görüntülerini reddeder ve opak bir render bunu önler. Bir de scale'e dikkat et: görünümü hedef piksel yerine puan cinsinden boyutlandırıyorsan, çıktının bulanık olmaması için renderer.scale'i cihaz ölçeğine ayarla; çerçeveyi tam hedef piksellerde boyutlandırıyorsan (yukarıdaki gibi), ölçeği 1'de tut.

Büyük çekince ImageRenderer'ın render edemediği şeydir. SwiftUI'yi render eder, UIKit/AppKit destekli görünümleri değil: web görünümleri, harita görünümleri, medya oynatıcılar, kamera önizlemeleri ve bazı sistem kontrolleri yer tutucu olarak çıkar. Temiz, deterministik bir SwiftUI ekranı için harikadır — anında, arayüzsüz, mükemmel boyutlu, cihaz üzerinde dahil her yerde çalışır. Platform görünümleri içeren bir ekran için gerçek derleyici çıktısını yakalamak amacıyla yine Seçenek 1-2'ye dönersin.

  • Açık ara en hızlısı. Görüntü başına milisaniyeler, simülatör açılışı yok, kullanıcı arayüzü yönlendirmesi yok. CI'da harika.
  • Kolayca tam boyutlar. Çerçeveyi 1320 × 2868'e (veya gereken herhangi bir boyuta) boyutlandır ve bu senin çıktın — "App Store'a göre yeniden boyutlandır" tahmini yok.
  • Durumu sen oluşturursun. Çalışan uygulama olmadığı için sahte veriyi doğrudan görünüme aktarırsın — deterministik, demo verisi seed'leyen başlatma argümanları yok.
  • Yalnızca SwiftUI. Platform destekli görünümler yer tutucu olarak render edilir. Ekranını güvenmeden önce doğrula.

Seçenek 4: Yukarıdakileri sarmalayan açık kaynak araçlar

Sürekli karşına çıkan ve komşu sorunları çözen iki açık kaynak proje var.

pointfree/swift-snapshot-testing en popüler SwiftUI anlık görüntü kütüphanesidir ve amacı konusunda net olmakta fayda var: mağaza varlıkları üretmek için değil, regresyon testi için yapılmıştır. İlk çalıştırma bir referans PNG kaydeder ve bilerek başarısız olur; sonraki çalıştırmalar, bir dolgu veya yazı tipi değişikliğinin arayüzünü kaydırdığı günü yakalamak için o referansla farkı karşılaştırır.

// pointfree/swift-snapshot-testing — built for regression, not store assets.
import SnapshotTesting
import SwiftUI
import Testing

@MainActor
struct HomeSnapshotTests {
    @Test func home_iPhone16ProMax() {
        let view = HomeScreen()
        // First run records the reference PNG and FAILS on purpose;
        // later runs diff against it. To re-record: record: .all
        assertSnapshot(of: view, as: .image(layout: .device(config: .iPhone13ProMax)))
    }
}

Kaydedilen referans görüntüleri ham ekran görüntüleri olarak yeniden kullanabilirsin ve .image(layout: .device(...)) stratejileri gerçek cihaz boyutlarında render eder — ama bu, aracın sıkılığıyla çatışır (piksel mükemmelliğinde eşleşme ister; makineler arası anti-aliasing farkları kararsız başarısızlıklara yol açar). Arayüzünü regresyonlara karşı korumak için başvur, birincil ekran görüntüsü hattın olarak değil.

AppScreenshotKit (shitamori1272 tarafından) tam olarak bu iş için yapılmıştır. Cihaz sınıfları ve yerelleri belirten bir @AppScreenshot makrosuyla bir SwiftUI görünümü tanımlarsın, gerçek ekranını bir Apple cihaz çerçevesi içinde render etmesi için DeviceView içine sararsın ve bir Swift Testing fonksiyonundan dışa aktarırsın. Çıktıyı temiz bir Screenshots/<locale>/<device>/ ağacına düzenler.

// AppScreenshotKit — declarative SwiftUI screenshots, runs under Swift Testing.
import AppScreenshotKit
import SwiftUI

@AppScreenshot(.iPhone69Inch(), options: .locale([Locale(identifier: "en_US")]))
struct HomeShot: View {
    var body: some View {
        DeviceView { HomeScreen() } // your real screen, wrapped in a device frame
    }
}

// In a test target:
import AppScreenshotKitTestTools
import Testing

@Test @MainActor
func exportScreens() throws {
    let out = URL(fileURLWithPath: "Screenshots")
    let exporter = AppScreenshotExporter(option: .file(outputURL: out))
    try exporter.export(HomeShot.self) // → Screenshots/en_US/iPhone_6_9_inch/...
}

Bu alanda bilinmeye değer ilgili araçlar da var: storescreens gibi CLI'lar tüm App Store Connect hattını (simülatörler genelinde paralel XCUITest yakalama, metinli çerçeveli render'lar, Apple'ın API'si üzerinden meta veri yükleme) tek bir yapılandırma dosyasından yönetir. Tek, idempotent, CI dostu bir zincir istiyorsan gerçekten faydalıdırlar. Ayrıca metin düzeni hakkında kendi görüşlerini de getirirler — ki bu, pazarlama yüzeyi için özel bir görsel editörün genellikle kazandığı sızıntı noktasıdır.

Yukarıdakilerin hiçbirinin yapmadığı kısım: kompozisyon + 50 dilde yerelleştirme

İşte sınırlı, dürüst paragraf. Yukarıdaki her seçenek çıplak kareler üretir — bir cihaz çözünürlüğünde uygulama arayüzün (ya da AppScreenshotKit ve benzerleriyle, sade bir cihaz mockup'ı içindeki bir kare). Dönüşüm sağlayan App Store karuselleri çıplak kareler değildir: hedeflediğin her yerelde tekrarlanan, genellikle çok panelli bir dizi olarak, kısa bir başlığın altında, bir arka plan üzerinde, stilize bir cihaz mockup'ı içindeki karedir. Bu kompozisyon adımı Mokbi'nin amacıdır. Yakaladığın kareleri (XCUITest'ten, ImageRenderer'dan, simülatörden, nereden olursa) getirir, bir tarayıcı editörüne bırakır, cihaz çerçeveleri ve metinler ile arka planlar eklersin, metinleri yaklaşık tek tıkla 50 App Store diline kadar çevirirsin ve gereken her boyutta toplu dışa aktarırsın. Filigranla önizleme ile tasarım ücretsizdir; sınırsız filigransız dışa aktarma ve mağazaya yayınlama bir abonelikle gelir — Solo €29.99/mo (1 uygulama) veya Studio €49.99/mo (5 uygulamaya kadar), tek seferlik satın alma yok. Sınırı açıkça belirtmek gerekirse: Mokbi kaynak ekran görüntülerini yakalamaz — uygulamanı çalıştırmaz veya görünümlerini render etmez. Yakalama işi yukarıdaki yerel Swift araçlarında kalır; Mokbi, o araçların ürettiğini kompoze eder ve yerelleştirir.

Gerçekçi birleşik SwiftUI iş akışı

  1. Kaynak kareleri yerel olarak yakala. Deterministik SwiftUI ekranları için tam piksel boyutlarında ImageRenderer ile render et (Seçenek 3) — en hızlısıdır ve simülatör gerektirmez. Platform görünümleri içeren ekranlar için ya da gerçek canlı durumu istiyorsan bir XCUITest (Seçenek 1) veya Swift Testing (Seçenek 2) yakalaması yaz ve 6,9 inç iPhone sınıfı ile 13 inç iPad sınıfı bir simülatörde çalıştır.
  2. Gerekiyorsa yakalamayı yerelleştir. Uygulama arayüzünün her yerelde render edilmesi için dil başına bir test planı yapılandırması ekle ya da ImageRenderer'a verdiğin görünüme bir yerel aktar. Bu, uygulama piksellerini yerelleştirir — pazarlama başlığını değil.
  3. Pazarlama karuselini kompoze et. Kareleri Mokbi'ye bırak, stilize çerçeveler/metinler/arka planlar ekle, çok panelli diziyi oluştur. Bu, yerel araçların yapamadığı adımdır.
  4. Metinleri çevir ve dışa aktar. Başlık metnini hedef App Store yerellerin genelinde tek tıkla çevir, ardından gereken her boyutta toplu dışa aktar.
  5. Bir sonraki arayüz değişikliğinde, yeniden yakala ve yeniden aç. Render aracını veya kullanıcı arayüzü testini yeniden çalıştır, kaydedilen Mokbi projesini yeniden aç, kareleri değiştir, yeniden dışa aktar. Kompozisyon ve çeviriler korunur.

Otomasyonu tamamen atlayabileceğin durumlar

Yılda bir ila üç sürüm gönderiyorsan, herhangi bir ekran görüntüsü altyapısı kurmak — yerel veya Fastlane fark etmez — sana kazandırdığından daha fazla saat maliyetine mal olur. Uygulamayı doğru cihaz sınıfında iOS Simulator'da çalıştır, her ekrana git, simülatörün ekran görüntüsü komutunu çalıştır (tam cihaz çözünürlüğünde yakalar) ve on dakikada App Store'a hazır PNG'lerin elinde olur. Sonra bir kez kompoze et ve yerelleştir, devam et. Yukarıdaki otomasyon, tam olarak "elle yeniden çek" on dakikalık bir iş olmaktan çıktığında karşılığını verir — sık sürümler, çok sayıda yerel ya da bir portföydeki birkaç uygulama. Makineyi ne kadar sağlam göründüğüne değil, sürüm sıklığına göre eşleştir. Aynı mantık diğer platformlar için de geçerli; bkz. bu sorunun React Native sürümü ve daha geniş Fastlane ve kodsuz karşılaştırması.

Neyle yakalarsan yakala, dışa aktarmadan önce hedefleri sağlama al: tam piksel boyutlarını ve format kurallarını (PNG/JPEG, RGB, alfa kanalı yok) App Store ekran görüntüsü boyutları rehberinden al. Apple, tolerans göstermeden bir piksel bile eksik boyutları reddeder, bu yüzden bu otuz saniyeye değer.

Sıradaki okuma

Editörü aç →