Tüm Yazılar
KategoriiOS
Okuma Süresi
18 dk
Yayın Tarihi
...
Kelime Sayısı
2.846kelime

Kahveni hazırla - bu içerikli bir makale!

App links, universal links, custom URL schemes ve navigation handling. Complete deep linking guide.

Deep Linking ve Universal Links

# Deep Linking ve Universal Links: Kapsamlı Rehber 🔗

Merhaba! Bugün iOS uygulamalarının en kritik konularından biri olan deep linking'i A'dan Z'ye inceleyeceğiz. Bu rehber sonunda kullanıcılarını doğrudan uygulamanın istediğin yerine yönlendirebileceksin.


İçindekiler


🎯 Bu Yazıda Öğreneceklerin

  • Custom URL Schemes vs Universal Links farkları
  • Apple App Site Association (AASA) dosyası kurulumu
  • SwiftUI ve UIKit entegrasyonu
  • Deferred deep linking stratejileri
  • Branch.io, Firebase Dynamic Links alternatifleri
  • Debug ve troubleshooting teknikleri

📚 Deep Linking Nedir?

Deep linking, kullanıcıları bir web URL'i veya custom scheme aracılığıyla uygulamanın belirli bir ekranına yönlendirme tekniğidir.

Tür
Örnek
Avantaj
Dezavantaj
Custom URL Scheme
`myapp://product/123`
Kolay kurulum
Güvenlik riski
Universal Links
`https://myapp.com/product/123`
Güvenli, fallback
Sunucu gerekli
App Links (Android)
`https://myapp.com/...`
Cross-platform
Android-only
💡 Altın İpucu: 2024'te Universal Links kullan. Custom URL scheme'ler artık güvenlik açığı kabul ediliyor!

Dış Kaynaklar:


🔧 Custom URL Schemes (Legacy)

1. Info.plist Konfigürasyonu

xml
1
2CFBundleURLTypes
3
4
5 CFBundleURLSchemes
6
7 myapp
8 myapp-staging
9
10 CFBundleURLName
11 com.mycompany.myapp
12 CFBundleTypeRole
13 Editor
14
15
16 
17
18LSApplicationQueriesSchemes
19
20 twitter
21 instagram
22 fb
23

2. URL Handling (UIKit)

swift
1// AppDelegate.swift
2import UIKit
3 
4class AppDelegate: UIResponder, UIApplicationDelegate {
5
6 // iOS 13 öncesi - deprecated ama hala çalışır
7 func application(
8 _ app: UIApplication,
9 open url: URL,
10 options: [UIApplication.OpenURLOptionsKey: Any] = [:]
11 ) -> Bool {
12
13 // Kaynak app bilgisi
14 let sourceApp = options[.sourceApplication] as? String
15 print("📲 URL opened from: \(sourceApp ?? "unknown")")
16
17 // Deep link handler'a gönder
18 return DeepLinkHandler.shared.handle(url: url, source: .customScheme)
19 }
20}
21 
22// SceneDelegate.swift (iOS 13+)
23class SceneDelegate: UIResponder, UIWindowSceneDelegate {
24
25 func scene(
26 _ scene: UIScene,
27 openURLContexts URLContexts: Set<UIOpenURLContext>
28 ) {
29 guard let context = URLContexts.first else { return }
30
31 let url = context.url
32 let options = context.options
33
34 print("📲 Scene opened URL: \(url)")
35 print(" Source: \(options.sourceApplication ?? "unknown")")
36
37 DeepLinkHandler.shared.handle(url: url, source: .customScheme)
38 }
39}

3. Associated Domains Entitlement

swift
1// Xcode > Signing & Capabilities > + Capability > Associated Domains
2// Format: applinks:yourdomain.com
3 
4/*
5Associated Domains:
6- applinks:myapp.com
7- applinks:www.myapp.com
8- applinks:staging.myapp.com
9- webcredentials:myapp.com (AutoFill için)
10*/

4. Apple App Site Association (AASA) Dosyası

json
1// https://myapp.com/.well-known/apple-app-site-association
2// VEYA https://myapp.com/apple-app-site-association
3// NOT: .json uzantısı KULLANMA, Content-Type: application/json olmalı
4 
5{
6 "applinks": {
7 "apps": [],
8 "details": [
9 {
10 "appIDs": [
11 "TEAMID.com.mycompany.myapp",
12 "TEAMID.com.mycompany.myapp.staging"
13 ],
14 "components": [
15 {
16 "/": "/product/*",
17 "comment": "Tüm ürün sayfaları"
18 },
19 {
20 "/": "/user/*",
21 "comment": "Kullanıcı profilleri"
22 },
23 {
24 "/": "/article/*",
25 "?": { "ref": "?*" },
26 "comment": "Makaleler (query params dahil)"
27 },
28 {
29 "/": "/promo/*",
30 "comment": "Promosyon linkleri"
31 },
32 {
33 "/": "/share/*",
34 "comment": "Paylaşım linkleri"
35 },
36 {
37 "/": "/",
38 "exclude": true,
39 "comment": "Ana sayfa hariç"
40 },
41 {
42 "/": "/blog/*",
43 "exclude": true,
44 "comment": "Blog web'de kalsın"
45 }
46 ]
47 }
48 ]
49 },
50 "webcredentials": {
51 "apps": ["TEAMID.com.mycompany.myapp"]
52 },
53 "appclips": {
54 "apps": ["TEAMID.com.mycompany.myapp.Clip"]
55 }
56}

5. AASA Doğrulama Checklist

bash
1# AASA dosyasını kontrol et
2curl -I https://myapp.com/.well-known/apple-app-site-association
3 
4# Beklenen response:
5# HTTP/2 200
6# Content-Type: application/json
7# (Redirect olmamalı!)
8 
9# Apple'ın CDN'inden kontrol et
10curl "https://app-site-association.cdn-apple.com/a/v1/myapp.com"
11 
12# 🐣 Easter Egg: Apple'ın debug tool'u
13# https://search.developer.apple.com/appsearch-validation-tool/
14# (Apple Developer hesabı gerekli)

swift
1import Foundation
2 
3// MARK: - Deep Link Types
4enum DeepLink: Equatable {
5 case product(id: String)
6 case user(username: String)
7 case article(slug: String, referrer: String?)
8 case promo(code: String)
9 case share(type: ShareType, id: String)
10 case settings(section: SettingsSection?)
11 case search(query: String?)
12 case unknown(url: URL)
13
14 enum ShareType: String {
15 case product, article, playlist
16 }
17
18 enum SettingsSection: String {
19 case account, notifications, privacy, appearance
20 }
21}
22 
23// MARK: - Deep Link Source
24enum DeepLinkSource {
25 case customScheme
26 case universalLink
27 case pushNotification
28 case spotlight
29 case siri
30 case widget
31 case appClip
32}
33 
34// MARK: - Deep Link Handler
35final class DeepLinkHandler {
36 static let shared = DeepLinkHandler()
37
38 // Pending deep link (uygulama tam açılmadan önce)
39 private(set) var pendingDeepLink: DeepLink?
40
41 // Deep link callback
42 var onDeepLinkReceived: ((DeepLink, DeepLinkSource) -> Void)?
43
44 private init() {}
45
46 // MARK: - Handle URL
47 @discardableResult
48 func handle(url: URL, source: DeepLinkSource) -> Bool {
49 let deepLink = parse(url: url)
50
51 // Unknown ise false dön
52 if case .unknown = deepLink {
53 print("⚠️ Unknown deep link: \(url)")
54 return false
55 }
56
57 // Analytics event
58 trackDeepLink(deepLink, source: source)
59
60 // Callback veya pending
61 if let callback = onDeepLinkReceived {
62 callback(deepLink, source)
63 } else {
64 pendingDeepLink = deepLink
65 }
66
67 return true
68 }
69
70 // MARK: - Parse URL
71 func parse(url: URL) -> DeepLink {
72 // Custom scheme: myapp://product/123
73 // Universal link: https://myapp.com/product/123
74
75 let pathComponents = url.pathComponents.filter { $0 != "/" }
76 let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems
77
78 guard let firstComponent = pathComponents.first else {
79 return .unknown(url: url)
80 }
81
82 switch firstComponent {
83 case "product", "p":
84 if let id = pathComponents.dropFirst().first {
85 return .product(id: id)
86 }
87
88 case "user", "u", "profile":
89 if let username = pathComponents.dropFirst().first {
90 return .user(username: username)
91 }
92
93 case "article", "a", "post":
94 if let slug = pathComponents.dropFirst().first {
95 let referrer = queryItems?.first { $0.name == "ref" }?.value
96 return .article(slug: slug, referrer: referrer)
97 }
98
99 case "promo", "redeem":
100 if let code = pathComponents.dropFirst().first {
101 return .promo(code: code)
102 }
103 // Query param olarak da gelebilir
104 if let code = queryItems?.first(where: { $0.name == "code" })?.value {
105 return .promo(code: code)
106 }
107
108 case "share":
109 if let typeStr = pathComponents.dropFirst().first,
110 let type = DeepLink.ShareType(rawValue: typeStr),
111 let id = pathComponents.dropFirst(2).first {
112 return .share(type: type, id: id)
113 }
114
115 case "settings":
116 let sectionStr = pathComponents.dropFirst().first
117 let section = sectionStr.flatMap { DeepLink.SettingsSection(rawValue: $0) }
118 return .settings(section: section)
119
120 case "search":
121 let query = queryItems?.first { $0.name == "q" }?.value
122 return .search(query: query)
123
124 default:
125 break
126 }
127
128 return .unknown(url: url)
129 }
130
131 // MARK: - Consume Pending
132 func consumePendingDeepLink() -> DeepLink? {
133 defer { pendingDeepLink = nil }
134 return pendingDeepLink
135 }
136
137 // MARK: - Analytics
138 private func trackDeepLink(_ deepLink: DeepLink, source: DeepLinkSource) {
139 let eventParams: [String: Any] = [
140 "deep_link_type": String(describing: deepLink),
141 "source": String(describing: source)
142 ]
143 // Analytics.logEvent("deep_link_opened", parameters: eventParams)
144 print("📊 Deep link tracked: \(eventParams)")
145 }
146}

🎨 SwiftUI Entegrasyonu

swift
1import SwiftUI
2 
3// MARK: - App Entry Point
4@main
5struct MyApp: App {
6 @StateObject private var router = DeepLinkRouter()
7 @StateObject private var appState = AppState()
8
9 var body: some Scene {
10 WindowGroup {
11 ContentView()
12 .environmentObject(router)
13 .environmentObject(appState)
14 // Universal Links
15 .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
16 guard let url = activity.webpageURL else { return }
17 router.handle(url: url, source: .universalLink)
18 }
19 // Custom URL Schemes
20 .onOpenURL { url in
21 router.handle(url: url, source: .customScheme)
22 }
23 // Pending deep link (cold start)
24 .task {
25 await router.processPendingDeepLink()
26 }
27 }
28 }
29}
30 
31// MARK: - Deep Link Router (SwiftUI)
32@MainActor
33final class DeepLinkRouter: ObservableObject {
34 // Navigation state
35 @Published var selectedTab: AppTab = .home
36 @Published var navigationPath = NavigationPath()
37
38 // Sheets
39 @Published var presentedSheet: AppSheet?
40
41 // Specific destinations
42 @Published var productToShow: String?
43 @Published var userToShow: String?
44 @Published var articleToShow: String?
45 @Published var promoToApply: String?
46
47 // Alert for promo codes
48 @Published var promoAlert: PromoAlert?
49
50 func handle(url: URL, source: DeepLinkSource) {
51 let deepLink = DeepLinkHandler.shared.parse(url: url)
52 navigate(to: deepLink, source: source)
53 }
54
55 func navigate(to deepLink: DeepLink, source: DeepLinkSource) {
56 // Reset navigation state
57 navigationPath = NavigationPath()
58
59 switch deepLink {
60 case .product(let id):
61 selectedTab = .shop
62 // Delayed navigation for tab switch animation
63 Task {
64 try? await Task.sleep(for: .milliseconds(100))
65 productToShow = id
66 }
67
68 case .user(let username):
69 selectedTab = .profile
70 Task {
71 try? await Task.sleep(for: .milliseconds(100))
72 userToShow = username
73 }
74
75 case .article(let slug, let referrer):
76 selectedTab = .home
77 Task {
78 try? await Task.sleep(for: .milliseconds(100))
79 articleToShow = slug
80 if let referrer {
81 // Track referrer for attribution
82 print("📊 Referrer: \(referrer)")
83 }
84 }
85
86 case .promo(let code):
87 // Show promo code alert
88 promoToApply = code
89 presentedSheet = .promo(code: code)
90
91 case .share(let type, let id):
92 switch type {
93 case .product:
94 productToShow = id
95 case .article:
96 articleToShow = id
97 case .playlist:
98 // Handle playlist
99 break
100 }
101
102 case .settings(let section):
103 selectedTab = .settings
104 if let section {
105 Task {
106 try? await Task.sleep(for: .milliseconds(100))
107 navigationPath.append(section)
108 }
109 }
110
111 case .search(let query):
112 selectedTab = .search
113 if let query {
114 // Pre-fill search query
115 NotificationCenter.default.post(
116 name: .searchQueryReceived,
117 object: nil,
118 userInfo: ["query": query]
119 )
120 }
121
122 case .unknown:
123 // Fallback to home
124 selectedTab = .home
125 }
126 }
127
128 func processPendingDeepLink() async {
129 // Cold start'ta bekleyen deep link var mı?
130 if let pending = DeepLinkHandler.shared.consumePendingDeepLink() {
131 // UI hazır olana kadar bekle
132 try? await Task.sleep(for: .milliseconds(500))
133 navigate(to: pending, source: .customScheme)
134 }
135 }
136}
137 
138// MARK: - Supporting Types
139enum AppTab: String, CaseIterable {
140 case home, shop, search, profile, settings
141}
142 
143enum AppSheet: Identifiable {
144 case promo(code: String)
145 case share(item: Any)
146
147 var id: String {
148 switch self {
149 case .promo(let code): return "promo-\(code)"
150 case .share: return "share"
151 }
152 }
153}
154 
155struct PromoAlert: Identifiable {
156 let id = UUID()
157 let code: String
158 let message: String
159}
160 
161extension Notification.Name {
162 static let searchQueryReceived = Notification.Name("searchQueryReceived")
163}

8. Navigation Destination Handler

swift
1// MARK: - Content View with Deep Link Navigation
2struct ContentView: View {
3 @EnvironmentObject var router: DeepLinkRouter
4
5 var body: some View {
6 TabView(selection: $router.selectedTab) {
7 HomeTab()
8 .tabItem { Label("Ana Sayfa", systemImage: "house") }
9 .tag(AppTab.home)
10
11 ShopTab()
12 .tabItem { Label("Mağaza", systemImage: "bag") }
13 .tag(AppTab.shop)
14
15 SearchTab()
16 .tabItem { Label("Ara", systemImage: "magnifyingglass") }
17 .tag(AppTab.search)
18
19 ProfileTab()
20 .tabItem { Label("Profil", systemImage: "person") }
21 .tag(AppTab.profile)
22
23 SettingsTab()
24 .tabItem { Label("Ayarlar", systemImage: "gear") }
25 .tag(AppTab.settings)
26 }
27 .sheet(item: $router.presentedSheet) { sheet in
28 switch sheet {
29 case .promo(let code):
30 PromoRedeemView(code: code)
31 case .share(let item):
32 ShareSheet(items: [item])
33 }
34 }
35 }
36}
37 
38// MARK: - Shop Tab with Deep Link
39struct ShopTab: View {
40 @EnvironmentObject var router: DeepLinkRouter
41 @State private var products: [Product] = []
42
43 var body: some View {
44 NavigationStack(path: $router.navigationPath) {
45 ProductListView(products: products)
46 .navigationTitle("Mağaza")
47 .navigationDestination(for: String.self) { productId in
48 ProductDetailView(productId: productId)
49 }
50 // Deep link'ten gelen product
51 .onChange(of: router.productToShow) { _, productId in
52 if let productId {
53 router.navigationPath.append(productId)
54 router.productToShow = nil
55 }
56 }
57 }
58 }
59}

📦 Deferred Deep Linking

9. First Install Attribution

swift
1// MARK: - Deferred Deep Link Manager
2final class DeferredDeepLinkManager {
3 static let shared = DeferredDeepLinkManager()
4
5 private let defaults = UserDefaults.standard
6 private let pendingLinkKey = "pendingDeepLink"
7 private let installDateKey = "appInstallDate"
8 private let attributionWindowSeconds: TimeInterval = 7 * 24 * 60 * 60 // 7 gün
9
10 private init() {
11 recordInstallDateIfNeeded()
12 }
13
14 // MARK: - Install Date
15 private func recordInstallDateIfNeeded() {
16 if defaults.object(forKey: installDateKey) == nil {
17 defaults.set(Date(), forKey: installDateKey)
18 }
19 }
20
21 var isFirstLaunch: Bool {
22 // İlk launch kontrolü için daha güvenilir yöntem
23 let hasLaunchedBefore = defaults.bool(forKey: "hasLaunchedBefore")
24 if !hasLaunchedBefore {
25 defaults.set(true, forKey: "hasLaunchedBefore")
26 return true
27 }
28 return false
29 }
30
31 // MARK: - Save Pending Deep Link
32 /// Uygulama yüklenmeden önce yakalanan deep link'i sakla
33 func savePendingDeepLink(_ url: URL) {
34 let data: [String: Any] = [
35 "url": url.absoluteString,
36 "timestamp": Date().timeIntervalSince1970
37 ]
38 defaults.set(data, forKey: pendingLinkKey)
39 print("💾 Pending deep link saved: \(url)")
40 }
41
42 // MARK: - Handle Pending Deep Link
43 /// Uygulama açıldığında bekleyen deep link'i işle
44 @MainActor
45 func handlePendingDeepLink() async -> DeepLink? {
46 guard let data = defaults.dictionary(forKey: pendingLinkKey),
47 let urlString = data["url"] as? String,
48 let timestamp = data["timestamp"] as? TimeInterval,
49 let url = URL(string: urlString) else {
50 return nil
51 }
52
53 // Attribution window kontrolü
54 let savedDate = Date(timeIntervalSince1970: timestamp)
55 guard Date().timeIntervalSince(savedDate) < attributionWindowSeconds else {
56 // Çok eski, temizle
57 clearPendingDeepLink()
58 return nil
59 }
60
61 // İşle ve temizle
62 clearPendingDeepLink()
63
64 return DeepLinkHandler.shared.parse(url: url)
65 }
66
67 // MARK: - Clear
68 func clearPendingDeepLink() {
69 defaults.removeObject(forKey: pendingLinkKey)
70 }
71
72 // MARK: - Clipboard Check (iOS 14+)
73 /// Clipboard'da deep link var mı kontrol et
74 @MainActor
75 func checkClipboardForDeepLink() async -> DeepLink? {
76 // iOS 14+ clipboard access uyarısı gösterir
77 // Kullanıcıdan izin gerekebilir
78
79 guard UIPasteboard.general.hasURLs,
80 let url = UIPasteboard.general.url,
81 url.host == "myapp.com" else {
82 return nil
83 }
84
85 return DeepLinkHandler.shared.parse(url: url)
86 }
87}
88 
89// 💡 Pro Tip: Branch.io veya AppsFlyer ile deferred deep linking
90// çok daha güvenilir çalışır. Fingerprinting kullanırlar.

🧪 Debug ve Troubleshooting

swift
1#if DEBUG
2// MARK: - Debug Tools
3struct UniversalLinksDebugView: View {
4 @State private var testURL = "https://myapp.com/product/123"
5 @State private var parseResult = ""
6 @State private var aasaStatus = ""
7
8 var body: some View {
9 Form {
10 Section("Test URL") {
11 TextField("URL", text: $testURL)
12 .textInputAutocapitalization(.never)
13 .autocorrectionDisabled()
14
15 Button("Parse") {
16 testParse()
17 }
18
19 if !parseResult.isEmpty {
20 Text(parseResult)
21 .font(.caption)
22 .foregroundStyle(.secondary)
23 }
24 }
25
26 Section("AASA Status") {
27 Button("Check AASA") {
28 Task { await checkAASA() }
29 }
30
31 if !aasaStatus.isEmpty {
32 Text(aasaStatus)
33 .font(.caption)
34 }
35 }
36
37 Section("Quick Tests") {
38 Button("Open Product Deep Link") {
39 openURL("myapp://product/test123")
40 }
41 Button("Open Universal Link") {
42 openURL("https://myapp.com/product/test123")
43 }
44 }
45
46 Section("Info") {
47 LabeledContent("Bundle ID", value: Bundle.main.bundleIdentifier ?? "-")
48 LabeledContent("Team ID", value: "YOUR_TEAM_ID")
49 }
50 }
51 .navigationTitle("Deep Link Debug")
52 }
53
54 private func testParse() {
55 guard let url = URL(string: testURL) else {
56 parseResult = "❌ Invalid URL"
57 return
58 }
59
60 let deepLink = DeepLinkHandler.shared.parse(url: url)
61 parseResult = "✅ Parsed: \(deepLink)"
62 }
63
64 private func checkAASA() async {
65 guard let url = URL(string: "https://myapp.com/.well-known/apple-app-site-association") else {
66 return
67 }
68
69 do {
70 let (data, response) = try await URLSession.shared.data(from: url)
71
72 if let httpResponse = response as? HTTPURLResponse {
73 if httpResponse.statusCode == 200 {
74 let json = try JSONSerialization.jsonObject(with: data)
75 aasaStatus = "✅ Valid AASA\n\(json)"
76 } else {
77 aasaStatus = "❌ HTTP \(httpResponse.statusCode)"
78 }
79 }
80 } catch {
81 aasaStatus = "❌ Error: \(error.localizedDescription)"
82 }
83 }
84
85 private func openURL(_ string: String) {
86 guard let url = URL(string: string) else { return }
87 UIApplication.shared.open(url)
88 }
89}
90#endif

markdown
1# 🎁 UNIVERSAL LINKS PRODUCTION CHECKLIST
2 
3## Domain Setup
4- [ ] HTTPS aktif (zorunlu)
5- [ ] SSL sertifikası valid
6- [ ] AASA dosyası doğru path'te
7- [ ] Content-Type: application/json
8- [ ] Redirect yok (200 OK)
9- [ ] CDN cache kontrol edildi
10 
11## AASA File
12- [ ] JSON syntax valid
13- [ ] appIDs doğru format (TEAMID.bundleid)
14- [ ] paths doğru tanımlanmış
15- [ ] exclude paths kontrol edildi
16- [ ] webcredentials eklendi (opsiyonel)
17- [ ] appclips eklendi (opsiyonel)
18 
19## Xcode Setup
20- [ ] Associated Domains capability eklendi
21- [ ] applinks:domain.com doğru format
22- [ ] Staging/production ayrı domain'ler
23- [ ] Entitlements dosyası commit edildi
24 
25## Code Implementation
26- [ ] onOpenURL handler (SwiftUI)
27- [ ] application(_:continue:) (UIKit)
28- [ ] scene(_:openURLContexts:) (SceneDelegate)
29- [ ] Deep link parser
30- [ ] Navigation router
31- [ ] Deferred deep link support
32- [ ] Error handling
33 
34## Testing
35- [ ] Simulator'da Notes app ile test
36- [ ] Gerçek cihazda Safari ile test
37- [ ] iMessage ile test
38- [ ] Cold start test
39- [ ] Warm start test
40- [ ] Associated Domains refresh (Developer Mode)
41 
42## Analytics
43- [ ] Deep link tracking
44- [ ] Source attribution
45- [ ] Conversion tracking
46- [ ] Error logging
47 
48## Troubleshooting Komutları

Okuyucu Ödülü

# AASA kontrol

curl -I https://domain.com/.well-known/apple-app-site-association

# Apple CDN kontrol

curl https://app-site-association.cdn-apple.com/a/v1/domain.com

# Cihazda log

# Console.app > Action > Include Info/Debug Messages

# Filtrelele: "swcd" veya "associated"

swift
1 

💡 Pro Tips ve Best Practices

  1. Universal Links > Custom URL Schemes - Güvenlik ve UX için her zaman Universal Links tercih et.
  1. AASA CDN Cache - Apple AASA'yı cache'ler. Güncelleme yapınca 24-48 saat bekle.
  1. Fallback URL - App yüklü değilse web sitene yönlendir.
  1. Deep Link Analytics - Hangi deep link'lerin en çok kullanıldığını takip et.
  1. A/B Test - Farklı landing page'ler için farklı deep link path'leri kullan.
  1. QR Code - Marketing materyallerinde Universal Link QR kodları kullan.
  1. Smart App Banner - Web sitene <meta name="apple-itunes-app"> ekle.
  1. Branch.io/Firebase - Deferred deep linking için 3rd party SDK'lar daha güvenilir.

swift
1// 5 kez ayarlar ikonuna tıklayınca debug menüsü açılır
2struct SettingsTab: View {
3 @State private var tapCount = 0
4 @State private var showDebug = false
5
6 var body: some View {
7 NavigationStack {
8 // ... settings content ...
9 }
10 .onTapGesture(count: 5) {
11 showDebug = true
12 }
13 .sheet(isPresented: $showDebug) {
14 UniversalLinksDebugView()
15 }
16 }
17}

📖 Sonuç

Deep linking, modern mobil uygulamalar için vazgeçilmez bir özellik. Universal Links ile güvenli, kullanıcı dostu deneyimler sunabilir ve marketing kampanyalarının etkinliğini ölçebilirsin.

Bir sonraki yazıda görüşmek üzere! 🔗


*Sorularını Twitter'da sorabilirsin!*

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Etiketler

#Deep Linking#Universal Links#iOS#Navigation#SwiftUI
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz