Navigating Complexity: Deep Linking with Chain of Responsibility

Saeid Rezaeisadrabadi
3 min readJan 15, 2024

--

In this story, I aim to implement an idea that, I believe, is not entirely new. Let me share a backstory: a friend of mine intends to refactor deep-linking in their company. The current UIKit implementation involves massive controllers, leading to complexity. They seek a cleaner, more maintainable approach, adaptable to SwiftUI.

I suggest leveraging the Chain of Responsibility pattern. I’ll delve into this pattern in the future, but for now, let’s explore its application to deep-linking.

Generated with AI

Idea

The concept is straightforward: we require a DeepLink Manager or Handler. Each feature must implement and handle its own deep link, registering its deep-link manager in the main manager.

Implementation

Let’s begin with the main view. Assuming our app has 3 tabs, I assign a tag to each tab to handle them programmatically.

TabView {
Feature1()
.tabItem {
Text("Feature 1")
}
.tag(Tab.feature1)
Feature2()
.tabItem {
Text("Feature 2")
}
.tag(Tab.feature2)
Feature3()
.tabItem {
Text("Feature 3")
}
.tag(Tab.feature3)
}

enum Tab: Hashable {
case feature1
case feature2
case feature3
}

Next, I need the main DeepLinkManager, responsible for distributing new deep links to each child manager

@Observable
class DeepLinkManager {
var selectedTab: Tab = .feature1
}

I declare selectedTab so that each child can change the selected tab. To store child managers, I use arrays. I also define a protocol that each child will implement to handle deep links.

protocol DeepLinkFeatureManager {
func handle(deepLink: String, selectedTab: inout Tab) -> Bool
}
@Observable
class DeepLinkManager {
var selectedTab: Tab = .feature1
private var features: [DeepLinkFeatureManager]
}

Now I need a DeepLinkManager for each feature, keeping in mind that they need to implement the protocol.

class DeepLinkManagerFeature1: DeepLinkFeatureManager {
func handle(deepLink: String, selectedTab: inout Tab) -> Bool {
if deepLink.contains("feature1") {
selectedTab = .feature1
return true
}

return false
}
}

In the next step, I need to register child managers in the main DeepLinkManager.

init() {
self.features = [
DeepLinkManagerFeature1(),
DeepLinkManagerFeature2(),
DeepLinkManagerFeature3()
]
}

As a final step, I need a method in the main DeepLinkManager to retrieve deep links from SceneDelegate/AppDelegate/App.

func handleDeepLink(deepLink: String) {
for handler in features {
if handler.handle(deepLink: deepLink, selectedTab: &selectedTab) {
return
}
}
}

That’s it, now let’s add the DeepLinkManager to the App.

var body: some Scene {
WindowGroup {
ContentView(deepLinkManager: deepLinkManager)
.onOpenURL(perform: { url in
deepLinkManager.handleDeepLink(deepLink: url.absoluteString.lowercased())
})
}
}

And it works perfectly. In an enterprise app, you’re not just changing tabs via a deep link. Instead, you need to inject your resources into each child manager and handle them there. This approach reduces the complexity of deep linking, making it more maintainable and testable.

Source code is available in my GitHub.

Conclusion

In conclusion, the implementation of the Chain of Responsibility pattern in the context of deep-linking has proven to be a powerful and efficient solution. By introducing a DeepLink Manager and employing child managers for each feature, we’ve successfully addressed the challenges posed by massive controllers and complex UIKit implementations. This approach offers a clean and maintainable structure adaptable to SwiftUI, allowing for seamless handling of deep links across various features.

The simplicity of the idea, where each feature takes responsibility for its own deep links, promotes a modular and scalable architecture.

--

--

Saeid Rezaeisadrabadi
Saeid Rezaeisadrabadi

Written by Saeid Rezaeisadrabadi

Over 8 years of experience in iOS software development

Responses (1)