SwiftUI için yeni WebKit
İzlemekte geç kaldığım bir video olan WWDC25: Meet WebKit for SwiftUI videosundan sonra tekrardan bir medium yazısı yazarak aklımda…
SwiftUI için yeni WebKit

İzlemekte geç kaldığım bir video olan ***WWDC25: Meet WebKit for SwiftUI videosundan sonra tekrardan bir medium yazısı yazarak aklımda kalanları, önemli noktaları bir kez daha yazıya dökerek burada bir döküman oluşturmaya karar verdim. Daha önce WebPage adına bir [yazıyı](https://hasanalidev.medium.com/ios26-ile-webview-ve-webpage-swiftui-765a4950888f)*** sizlerle paylaşmıştım fakat bu sefer daha fazla ayrıntıya yer vererek daha detaylı bir dökümanı sizlerle buluşturmaya çalışacağım.
WebKit, Apple ekosistemindeki önemli bileşenlerden biri olup, Safari, Mail ve diğer pek çok iOS/macOS uygulamasının temelini oluşturur. Safari’nin hızını, güvenliğini ve esnekliğini sağlayan WebKit, yıllardır biz geliştiricilere web içeriğini uygulamamıza kolayca entegre etme imkanı sunuyor. Bunun yanı sıra SwiftUI ile bir uygulama yapmak ne kadar kolay olsa bir web sayfasını uygulamanın içerisine gömmek istediğimizde tamamen native olmayan bir yol izlememiz gerekiyordu. Bu sorunu aşmak için Apple, WebKit’i SwiftUI ile uyumlu hale getiren yeni bir API sundu.
SwiftUI için özelleştirilen WebKit ile, web içeriğini doğrudan SwiftUI uygulamamıza entegre etmekte daha fazla esneklik bulacağız. Artık bir WebView kullanarak, bir URL sağlamak kadar basit bir işlemle, dinamik web içeriğini uygulamanıza dahil edebileceğiz. Ayrıca, **WebPage** sınıfı ile web içeriğini daha kolay bir şekilde yönetebilicek ve sayfanın içerisindeki içerik ile iletişim kurabileceğiz.
Web İçeriğini Yüklemek ve Kullanıcıya Göstermek
Web içeriğini uygulamamıza entegre etmek, özellikle dinamik ve etkileşimli web sayfalarını görüntülemek isteyen geliştiriciler için önemli bir ihtiyaçtır. SwiftUI ile WebKit entegrasyonunun sunduğu yeniliklerden biri de, web içeriğini uygulamaya dahil etmenin artık çok daha kolay olmasıdır. Bu işlem, sadece bir URL sağlamak kadar basit bir hale geldi.
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://webkit.org/"))
}
}
Ama her zaman WebView’ı sadece tek bir URL görüntülemek için kullanmak istemeyebilirsiniz. Burada basit bir buton yardımıyla WebView içerisinde iki farklı URL göstermeyi sağlayabiliriz.
struct ContentView: View {
@State private var toggle = false
var body: some View {
WebView(url: toggle
? URL(string: "https://www.webkit.org")
: URL(string: "https://www.swift.org")
)
.toolbar {
Button("URL Değiştir", systemImage: "pip.swap") {
toggle.toggle()
}
}
}
}
Tabii bu basit bir değişim; web içeriğiyle daha dinamik bir etkileşim kurmak isteyebiliriz. İşte bu noktada **WebPage** sınıfı devreye giriyor. Detaya girmeden önce vermek gerekirse WebPage ile bir web sayfasının başlığını native bir şekilde bir navigationTitle olarak sayfanıza verebilirsiniz.
struct ContentView: View {
@State private var page = WebPage()
var body: some View {
NavigationStack {
WebView(page)
.navigationTitle(page.title)
}
}
}

**WebPage sınıfı, içerik üzerinde daha fazla kontrol sağlayan ve uygulamanız ile web içeriği arasındaki etkileşimi yöneten bir başka önemli bileşendir. WebView ile birlikte kullanıldığında, sayfanın başlığı gibi özellikler üzerinde değişiklik yapabilir ve hatta web sayfasındaki içeriğe programatik olarak müdahale edebiliriz. WebPage sınıfı, web içeriğini yükleyip kontrol etmek için kullanılan yeni bir Observable** sınıfıdır.
@Observable
final class ArticleViewModel {
var page: WebPage
var lake: LakeArticle
init(lake: LakeArticle) {
self.lake = lake
self.page = WebPage()
}
func loadArticle() {
page.load(URLRequest(url: lake.url))
}
}
İlk adımımızda, WebPage ile iletişim kurmak için hem de view tarafında bu işi yapmamak için bir ViewModel sınıfı oluşturuyoruz. Bu sınıfı, web sayfasını yönetmek ve kullanıcıya gerekli verileri sağlamak için kullanıcaz.
Daha sonrasında sayfamızda WebView’ımıza bir url vermek yerine ViewModel’imiz içerisindeki WebPage tipindeki page değişkenimizi veriyoruz. Tabii ki sayfamız açıldığında loadArticle fonksiyonuyla page değişkenimize bir değer ataması yapıyoruz.
struct ArticleView: View {
@Environment(ArticleViewModel.self) private var model
var body: some View {
WebView(model.page)
.onAppear {
model.loadArticle()
}
.ignoresSafeArea(.all, edges: .bottom)
}
}
Tabii WebPage için farklı içerik yükleme şekilleri de sunuyor SwiftUI bize:
1- Web içeriğini bir URL üzerinden yüklemek için URLRequest kullanılır. Bu yöntem, belirli bir URL’yi yükleyerek, sayfayı doğrudan bu adrese yönlendirmek için uygundur.
let page = WebPage()
var request = URLRequest(url: item.url)
request.attribution = .user
page.load(request)
URLRequest: Bu, URL üzerinden web sayfasını yüklemek için kullanılan isteği temsil eder. Burada, sayfanın kullanıcıya ait bir içerik olduğunu belirlemek içinrequest.attribution = .usereklenmiş.page.load(request): Yükleme işlemi başlatılır. Bu,WebPagesınıfının, URL'den gelen veriyi nasıl alıp görüntüleyeceğini belirtir.
2- HTML içeriğini doğrudan bir string olarak yüklemek istiyorsanız, HTML verisini doğrudan load(html:baseURL:) metodu ile yükleyebilirsiniz. Bu yöntem, belirli bir sayfanın HTML içeriğini, herhangi bir URL yerine doğrudan string olarak yükler.
let page = WebPage()
page.load(html: item.html, baseURL: URL(string: "about:blank")!)
item.html: Burada, HTML içeriği bir string olarak sağlanır.baseURL: Sayfa içeriğinde kullanılan göreli URL'lerin doğru çözülmesi için temel URL belirtilir.about:blankgibi bir temel URL burada kullanılır.
3- Web içeriğini bir arşiv dosyasından (örneğin, webArchiveData) yüklemek isterseniz, bu yöntemle MIME türü ve karakter kodlaması da belirtilir.
let page = WebPage()
let base = URL(string: "about:blank")!
let mimeType = "application/x-webarchive"
page.load(item.webArchiveData, mimeType: mimeType, characterEncoding: .utf8, baseURL: base)
item.webArchiveData: Web arşivinden gelen veriyi yükler. Bu, sayfanın tüm içeriğinin yerel olarak saklandığı bir format olabilir.mimeType: Verinin MIME türünü belirtir, buradaapplication/x-webarchiveolarak belirtilmiştir, çünkü yüklenen içerik bir web arşividir.characterEncoding: Yükleme sırasında hangi karakter kodlamasının kullanılacağını belirtir, burada UTF-8 kullanılmıştır.baseURL: Burada dabaseURL, göreli URL'lerin çözülmesi için temel URL olarakabout:blankbelirtilmiştir.
WebKit, normalde uzak URL’leri ve verileri doğrudan yüklerken, bize yerel içerikleri yükleme imkanı da sunar. URLSchemeHandler protokolü sayesinde, özel şemalarla (örneğin lakes://) yerel dosyalar ya da uygulamanıza paketlenmiş içerikleri (Bundled Resources) yükleyebilirsiniz.
URLSchemeHandler
*URL Scheme *yani bir URL şeması**, URL’nin iki nokta (:) karakterinden önceki kısmıdır. Örneğin:
https://bir https şemasıdır.
file://bir file şemasıdır.
Özel şemalar da olabilir, örneğin
**lakes://** gibi.
WebKit, yaygın şemaları (https, file, about) zaten yönetiyor, fakat özel şemalar için **URLSchemeHandler** kullanarak kendi kaynak yükleyicimizi yazabiliriz.
URLSchemeHandler ile Özel Şemaları Yönetmek
Bir özel şemaya (örneğin lakes://) karşılık gelen tüm kaynaklar için bir handler (işleyici) tanımlayabilirsiniz. Bu işleyici, o şemaya sahip bir URL ile karşılaştığında ne yapması gerektiğini belirler. Örneğin, lakes:// şemasına sahip bir URL geldiğinde, WebKit bu URL'yi URLSchemeHandler protokolü ile işleyiciye yönlendirir.
Scheme Handler’ınızı Oluşturun
URLSchemeHandler protokolünü uygulayarak, kendi işleyicimizi tanımlayabiliriz. Bu işleyici, özel şemalarla gelen tüm istekleri alacak ve uygun verileri sağlayacaktır. Örnek olarak Apple kaynaklarına bakacak olursak:
struct LakeResourceSchemeHandler: URLSchemeHandler {
func reply(for request: URLRequest) -> some AsyncSequence<URLSchemeTaskResult, any Error> {
AsyncThrowingStream { continuation in
let resourceData = dataForLake(request.url, bundle: .main)
let response = responseForLake(request.url, contentLength: resourceData.count)
// Yanıtı gönder
continuation.yield(.response(response))
// Veriyi gönder
continuation.yield(.data(resourceData))
// Akışı tamamla
continuation.finish()
}
}
}
Bu koda parça parça baktığımızda
- AsyncThrowingStream:
Bu, asenkron veri akışını yöneten bir yapılandırmadır. Burada, yerel verileri parça parça yüklemek ve kullanıcının istediği içeriği dinamik olarak sağlamak için kullanılıyor.
continuation: Asenkron işlemi sürdüren bir nesnedir.continuation.yield()ile akışa veri gönderilir. - continuation.yield(.response(response)): WebKit, gelen isteğe karşılık olarak bir response döndürür. Bu, HTTP yanıtı gibi olabilir. Bu, verinin başlık bilgilerini (content-type, content-length vb.) içerir.
- continuation.yield(.data(resourceData)):
Burada, belirli bir yerel kaynağın (örneğin, bir HTML sayfası veya medya dosyası) içeriği veri olarak gönderilir.
resourceData, yerel olarak uygulamanın içinde bulunan ve yüklenmesi gereken veriyi temsil eder. - continuation.finish(): Bu, veri akışının sona erdiğini belirtir. Yani, veri tamamlandığında, işlem sonlanır ve WebKit bu isteği tamamladığını bilir.
Buradaki akış ise şöyle çalışır
- Request ve Response:
LakeResourceSchemeHandler, gelen URL isteğini (URLRequest) alır ve bu isteğe karşılık gelen bir yanıt (response) oluşturur. Ardından, dataForLake fonksiyonu ile ilgili yerel veriyi alır (bu veri HTML, medya dosyası veya başka bir türde olabilir).
- Asenkron Veri Akışı:
Veriler, AsyncThrowingStream içinde continuation.yield(.data()) ile parça parça (stream) gönderilir. Bu, büyük veri dosyalarının kesintiye uğramadan yüklenmesini sağlar.
- Veri Akışını Tamamlama:
continuation.finish() ile akış sona erer ve WebKit işlemi bitirir.
Daha sonra URLSchemeHandler protokolüyle yarattığımız handler’ımızı WebPage ile aşağıdaki gibi kullanabiliriz.
init(lake: LakeArticle) {
self.lake = lake
let scheme = URLScheme("lakes")!
let handler = LakeResourceSchemeHandler()
var configuration = WebPage.Configuration()
configuration.urlSchemeHandler[scheme] = handler
self.page = WebPage(configuration: configuration)
}
Burada init methodu içerisinde oluşturduğumuz LakesURLSchemeHandler adlı handler objecti oluşturuyoruz. Bu sınıfta, özel bir URL şeması olan “lakes://” ile gelen istekleri işlemek için URLSchemeHandler protokolünü implement etmiştik. Bu işleyici, WebKit’in lakes:// şemasıyla karşılaştığında devreye girerek, gelen URL’yi işler ve veriyi (örneğin yerel bir HTML dosyasını) yükler.
Web sayfası, page.load() fonksiyonu ile lakes://someLocalResource şeklinde yükleniyor. Burada, lakes:// şeması WebKit’in, LakesURLSchemeHandler işleyicisini kullanarak yerel kaynağı (HTML dosyası veya diğer içerikler) yüklemesini sağlıyor. Bu işlem, uygulamanın içinde bulunan yerel verilerin dinamik bir şekilde yüklenmesini mümkün kılıyor.
WebView ile JavaScript İletişimi
Evaluating JavaScript özellikleri sayesinde, sayfa içeriği ile etkileşime geçebilir ve sayfa dinamiklerini değiştirebiliriz. Bu, kullanıcı etkileşimi, içerik güncelleme, form yönetimi ve daha birçok senaryo için faydalı olacaktır. Örneğin, bir formu otomatik olarak doldurabilir, sayfanın başlığını güncelleyebilir veya sayfa içeriğiyle ilgili hesaplamalar yapabilirsiniz.
Yeni callJavaScript API’si, WebKit for SwiftUI içinde JavaScript ile iletişim kurmayı çok daha kolay hale getiriyor.
func parseSections() async -> [LakeArticle.Section] {
let result = try? await page.callJavaScript(
"""
const headers = document.querySelectorAll("h2");
return [...headers].map((header) => ({
"id": header.id,
"title": header.textContent
}));
"""
)
guard let result = result as? [[String : Any]] else {
fatalError()
}
return result.map(LakeArticle.Section.init(_:))
}
Bu örnek kod ile bir web sayfasındaki HTML başlıklarını (özellikle <h2> etiketlerini) JavaScript kullanarak analiz ediyoruz ve bu başlıkları bir model sınıfına dönüştürüyoruz. Burada amaç, web sayfasında başlıkların ID'lerini ve başlık metinlerini almak ve bu bilgileri LakeArticle.Section modeline dönüştürmek aslında. Daha sonra bu datayı bir içindekiler kısmı için kullanabiliriz. Bu uygulamamızın tasarımına göre değişebilir.
WebKit for SwiftUI ile web içeriğini yalnızca görüntülemekle kalmaz, aynı zamanda bu içerik ile etkileşimde bulunmak da oldukça kolaydır. currentNavigationEvent ve event.navigationID gibi özelliklerle sayfa navigasyonunu takip edebilir ve kullanıcının web sayfasında yaptığı işlemlere göre tepki verebiliyoruz. Bu özellikler sayesinde, bir kullanıcı web sayfasında gezindiğinde, sayfanın hangi aşamada olduğunu anında bilebilir ve buna göre dinamik içerik yükleme veya hata yönetimi gibi işlemleri tetikleyebiliriz.
Web sayfasındaki currentNavigationEvent özelliğini izleyebiliriz. Kullanıcı bir bağlantıya tıkladığında veya sayfa yönlendirildiğinde, navigationID’yi güncelliyoruz ve bunu kullanıcıya göstermek için bir metin olarak ekranda görüntüleyip, navigationID ile her bir navigasyonu benzersiz bir şekilde tanımlamamıza olanak tanır, böylece her sayfa geçişinin doğru bir şekilde izlenmesini sağlar.
Şuan bu konu için kod örneklerini paylaşmıyorum çünkü Apple resmi dökümantasyonunda WWDC 25'de tanıttığı bu yeni özellikleri göremedim. Herhangi bir güncelleme olduğunda yazıyı güncelliyor olacağım.
Bunun yanı sıra Apple bize WebPage ile yeni property’ler sunuyor.
page.title (Sayfa Başlığı)
page.title, web sayfasının başlığını döndüren bir özelliktir. Sayfa başlığı genellikle HTML sayfasının <title> etiketinden alınır. Bu özellik, sayfanın başlığını dinamik olarak takip etmenizi sağlar. Web sayfası başlığı değiştikçe, SwiftUI arayüzünde bu değişiklik yansıtılabilir.
page.url (Mevcut URL)
page.url, web sayfasının geçerli URL’sini gösterir. Bu özellik, kullanıcının hangi sayfada olduğunu izlemek ve sayfa gezintisini takip etmek için faydalıdır. Sayfa üzerinde gezinti yapıldıkça bu değer değişir.
page.estimatedProgress (Tahmini İlerleme)
page.estimatedProgress, sayfanın yüklenme sürecindeki ilerlemeyi gösterir. Sayfa ne kadarının yüklendiği hakkında bilgi verir ve genellikle bir ilerleme çubuğunda gösterilir. Sayfa yüklenmeye devam ettikçe, bu değer güncellenir.
page.themeColor (Tema Rengi)
page.themeColor, sayfanın tema rengini alır. Bu renk, sayfa başlığında veya menüde görsel bir rehber olarak kullanılabilir. Sayfanın görünümüyle uyumlu olarak tema rengi otomatik olarak WebKit tarafından alınır.
page.mediaType (Medya Türü)
page.mediaType, web sayfasındaki medya türünü belirler. Örneğin, sayfada video, ses veya resim gibi medya öğeleri bulunuyorsa, bu özellik ile sayfadaki medya türüne dair bilgi alabilirsiniz. Bu özellik, sayfa içeriğinin türünü tanımak ve işlemi buna göre yönetmek için kullanılır.
page.cameraCaptureState (Kamera Durumu)
page.cameraCaptureState, sayfa içeriği ile ilgili olarak kameranın aktif olup olmadığını belirler. Sayfada kamera kullanımı gerektiren işlemler varsa, bu özellik kameranın açık olup olmadığını kontrol etmenizi sağlar. Bu, özellikle video konferans veya görüntü işleme uygulamalarında kullanışlıdır.
page.loadingStatus (Yüklenme Durumu)
page.loadingStatus, web sayfasının yüklenip yüklenmediğini belirten bir özelliktir. Sayfa yüklenmeye başladığında bu özellik, yükleme sürecini takip etmenize olanak tanır. Sayfa tamamen yüklendiğinde, bu özellik tamamlandığını belirtecektir.
Özetle WebPage sınıfı ile web sayfası üzerinde başlık, URL, ilerleme durumu, tema rengi, medya türü gibi bilgileri dinamik olarak yönetmenizi sağlar. Bu özellikler sayesinde, kullanıcı etkileşimlerini daha etkili bir şekilde takip edebilir ve sayfa içeriği ile daha güçlü bir etkileşim oluşturabilirsiniz.
Scroll ve Kullanıcı Etkileşimi için Sunulan Yenilikle
WebKit for SwiftUI, kullanıcı etkileşimini özelleştirmek için güçlü view modifier özellikleri sunuyor. Web sayfalarındaki içeriklerin kaydırılmasını ve etkileşimini yönetmek oldukça kolay hale geliyor. İşte bu özelliklerin neler olduğuna bakalım
Scroll Konumu Kontrolü
Kaydırma pozisyonunu kontrol etmek, sayfanın belirli bölümlerine hızlıca kaydırma yapmak için faydalı olabilir. WebViewScrollPosition modifier’ı ile sayfa içeriğinin kaydırma konumunu yönetebiliriz. Bu özellik, kullanıcının sayfada belirli bir öğeye tıklaması sonucu sayfayı o öğeye kaydırmak için kullanılabilir.
Örneğin viewModel kısmında ilgili bölümün konumunu tespit edip ardından webview üzerinde scroll yapılabilir.
func positionForSection(_ section: LakeArticle.Section?) async -> CGFloat? {
guard let section else { return nil }
let result = try? await page.callJavaScript(
"return computeSectionOffset(sectionID)",
arguments: [
"sectionID": section.id
]
)
return result as? CGFloat
}
struct ArticleView: View {
@Environment(ArticleViewModel.self) private var model
@State private var scrollPosition = ScrollPosition()
@Binding var section: LakeArticle.Section?
var body: some View {
WebView(model.page)
.webViewScrollPosition($scrollPosition)
.onChange(of: section, initial: true) { _, newSection in
Task {
guard let position = await model.positionForSection(newSection) else {
return
}
scrollPosition.scrollTo(y: position)
}
}
}
}
- webViewScrollPosition: Web sayfasının kaydırma pozisyonunu kontrol eder.
- scrollPosition.scrollTo: Belirli bir öğeye kaydırma işlemini yapar.
Sayfada Ara Desteği
Find-in-Page özelliği, sayfada metin araması yapabilmeyi sağlıyor. Kullanıcılar, sayfada bir anahtar kelime veya metin arayabilir ve arama sonuçları üzerinde gezinmek için arama kutusunu kullanabilirler.
struct ArticleView: View {
@Environment(ArticleViewModel.self) private var model
@State private var findNavigatorIsPresented = false
var body: some View {
WebView(model.page)
.findNavigator(isPresented: $findNavigatorIsPresented)
.toolbar {
ToolbarItemGroup {
Button("Find", systemImage: "magnifyingglass") {
findNavigatorIsPresented.toggle()
}
}
}
}
}
- findNavigator: Sayfada arama yapmayı etkinleştirir.
isPresentedparametresi ile arama kutusunun görünür olup olmadığı kontrol edilir.
Bu özellik, kullanıcılara sayfa üzerinde hızlıca arama yapma ve arama sonuçları üzerinde gezinti yapabilme imkanı tanır.
Platform Bazlı Özellikler
Henüz deneyimlememiş olsamda Apple’ın anlattıklarına dayanarak visionOS gibi özel platformlarda, look-to-scroll (bakarak kaydırma) etkileşim özelliklerini etkinleştirebilirsiniz. Bu özellik, kullanıcının göz hareketlerine göre sayfa kaydırmasını sağlar. look-to-scroll, özellikle göz izleme teknolojisine sahip cihazlarda oldukça kullanışlıdır.
struct ArticleView: View {
@Environment(ArticleViewModel.self) private var model
var body: some View {
WebView(model.page)
#if os(visionOS)
.webViewScrollInputBehavior(.enabled, for: .look)
#endif
}
}
- webViewScrollInputBehavior: Bu özellik, kaydırma davranışını özelleştirmenizi sağlar.
enabledve.lookparametreleri, bakarak kaydırma davranışını etkinleştirir. - look-to-scroll, kullanıcının bakış yönüne göre kaydırma yapmayı sağlar ve bu özellik özellikle göz izleme teknolojisiyle entegre cihazlarda kullanılabilir.
Sonuç olarak, yeni WebKit API ile hem güçlü hem de kullanıcı dostu uygulamalar geliştirmek mümkün. Web içeriğiyle etkileşimi, modern özelliklerle daha verimli hale getirerek webview kullandığımız sayfalarda bile native hissiyatı verebiliriz.
İyi kodlamalar!
메타데이터
- post_id
- dc17abc32bb1
- slug
- swiftui-için-yeni-webkit-dc17abc32bb1
- url
- https://medium.com/@hasanalidev/swiftui-i%C3%A7in-yeni-webkit-dc17abc32bb1
- canonical_url
- https://medium.com/@hasanalidev/swiftui-i%C3%A7in-yeni-webkit-dc17abc32bb1
- author_url
- https://medium.com/@hasanalidev
- status
- ok
- fetched_at
- 2026-07-16 07:48:42