-
Notifications
You must be signed in to change notification settings - Fork 0
딥링크로 체크리스트 초대하기
딥링크(DeepLink)는 모바일 애플리케이션에서 특정한 화면으로 사용자를 직접 연결하는 링크입니다.
애플리케이션을 실행하면 홈 화면, 로그인 화면과 같이 기기에 설정된 첫 화면이 나오는데요.
딥링크를 사용하면 앱실행 시 특정 화면으로 바로 이동할 수 있습니다.
iOS의 딥링크 방식은 앱 스킴 방식의 딥링크와 유니버셜 링크가 있습니다.
앱 스킴 방식의 딥링크는 가장 일반적으로 사용할 수 있는 딥링크인데요. 특정 스킴 값을 호출하여 특정 앱을 오픈하는 방식입니다.
앱 스킴 방식도 URL의 구조와 비슷하게 특정 페이지를 구분할 수 있습니다.
{scheme}://{path}
예를들어 오리의 스킴이 openlist
라면 로그인화면은 openlist://login
이 될 수 있는 것이죠.
앱 스킴 방식은 한계가 존재합니다. 만약 앱 스킴이 중복된다면 어떻게할까요? 앱 스킴은 앱을 구분하는 수단이지만 고유한 값은 아니므로 중복될 수 있습니다.
이렇게 중복되는 경우에는 사용자에게 어떤 앱을 실행시킬 것인지 물어봅니다.
오리는 함께 체크리스트에서 친구을 초대하여 같이 체크리스트를 작성할 수 있는데요. 초대 링크를 통해 애플리케이션을 실행한 사용자는 바로 함께 체크리스트 화면으로 이동합니다.
따라서 오리는 초대 링크를 통해 애플리케이션을 실행한 경우 함께 체크리스트 화면으로 이동하는 기능을 구현해야했습니다.
이 때 오리는 딥링크를 통해 해결했습니다. 오리는 간단하게 앱 스킴 방식을 사용하였는데요.
사용자는 초대하기 버튼을 눌러 openlist://shared-checklists?shared-checklistId=\(id)
링크를 친구에게 전달합니다.
링크를 받은 친구는 링크를 열어 애플리케이션을 실행합니다. 이 때 앱의 상태에 따라 다른 델리게이트 메서드를 통해 딥링크를 파싱할 수 있습니다.
// 만약 종료된 상태에서 실행된다면
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
...
if let urlContext = connectionOptions.urlContexts.first {
// parse link
// navigate page
}
}
// 만약 앱이 백그라운드 상태에서 실행된다면
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
// parse link
// navigate page
}
url를 파싱하여 이동할 페이지를 알았다면 이제 이동해야할 화면으로 이동해야합니다.
오리는 화면이동을 담당하는 라우터가 있는데요. 따라서 라우터에게 화면 이동을 시키면 될 것 같습니다.
SceneDelegate에서 어떻게 라우터에게 화면이동을 시킬 수 있을까요?
enum DeepLinkTarget {
/// openlist://shared-checklists?shared-checklistId=\(id)
case routeToSharedCheckList(id: UUID)
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var deepLinkSubject: PassthroughSubject<DeepLinkTarget, Never> = .init()
let component = AppComponent(deepLinkSubject: deepLinkSubject)
...
}
라우터는 딥링크 타입을 가지고 있는 deepLinkSubject
를 구독하고있어 deepLinkSubject
에서 방출된 값에 따라 해당 라우터에서 적절한 화면이동을 할 수 있습니다.
final class WithCheckListRouter {
private var deepLinkSubject: PassthroughSubject<DeepLinkTarget, Never>
private var cancellables: Set<AnyCancellable> = []
private var withCheckListDetailFactory: WithDetailCheckListFactoryable
func bind() {
deepLinkSubject
.receive(on: DispatchQueue.main)
.sink { [weak self] target in
guard let self else { return }
switch target {
case let .routeToSharedCheckList(id):
self.routeToDetailScene(with: id)
}
}
.store(in: &cancellables)
}
}
- [ADR] 아키텍처 의사 결정 기록: iOS 애플리케이션 아키텍처 채택하기
- [ADR] 아키텍처 의사 결정 기록: SwiftLint 채택
- [ADR] 아키텍처 의사 결정 기록: UI 영역에서 Combine 사용 결정
- [ADR] 아키텍처 의사 결정 기록: Presentation영역의 ViewModel에서 Input Output 패턴 도입 결정
- [ADR] 아키텍처 의사 결정 기록: 코디네이터 패턴 도입 결정
- [ADR] 아키텍처 의사 결정 기록: 로컬 스토리지로 코어 데이터 사용 결정
- [ADR] 아키텍처 의사 결정 기록: Custom Network Foundation 라이브러리 구현 및 모듈화 결정
- [ADR] 아키텍처 의사 결정 기록: 이미지캐셔 라이브러리 구현 및 모듈화 결정