logo hsb.horse
← 블로그 목록으로 돌아가기

블로그

iOS Shield UI에는 확장 하나가 아니라 둘이 필요하다

ManagedSettings로 차단을 설정했을 때 표시되는 shield 화면은 Action Extension과 Configuration Extension이라는 두 개의 별도 타깃으로 구성된다. 올바른 NSExtensionPointIdentifier와 principal class를 정리했다.

게시일:

ManagedSettings로 차단 규칙을 설정하면 대상 사이트에 접근할 때 shield 화면이 표시된다. 이 화면을 커스터마이즈하려고 할 때 iOS Extension을 처음 만지는 사람이 거의 반드시 한 번 걸리는 포인트가 있다. Extension 타깃 하나만 추가하면 된다고 생각하면 막힌다. 실제로는 두 개가 필요하다.

iOS의 Extension은 메인 앱과 별도로 동작하는 작은 프로세스다. Xcode에서는 타깃을 추가하는 방식으로 만든다. Shield Extension은 shield 화면 전용 Extension이며 메인 앱과 독립적으로 실행된다.

Shield Action Extension

사용자가 shield 화면의 버튼을 눌렀을 때의 동작을 담당한다. principal class, 즉 Extension의 진입점이 되는 클래스는 ShieldActionDelegate를 상속한다.

import ManagedSettings
class ShieldActionExtension: ShieldActionDelegate {
override func handle(
action: ShieldAction,
for application: ApplicationToken,
completionHandler: @escaping (ShieldActionResponse) -> Void
) {
completionHandler(.close)
}
}

Extension Point Identifier는 com.apple.ManagedSettings.shield-action-service다.

이쪽은 ManagedSettingsUI가 아니라 ManagedSettings를 import한다. 여기서 혼동하기 쉬우니 따로 적어 두는 편이 좋다.

Shield Configuration Extension

shield 화면의 비주얼을 커스터마이즈한다. 제목, 본문, 버튼 라벨을 앱의 문맥에 맞게 바꾸고 싶을 때 사용한다. principal class는 ShieldConfigurationDataSource를 상속한다.

import ManagedSettingsUI
import ManagedSettings
class ShieldConfigurationExtension: ShieldConfigurationDataSource {
override func configuration(
shielding application: Application
) -> ShieldConfiguration {
return ShieldConfiguration(
backgroundBlurStyle: .systemUltraThinMaterial,
title: ShieldConfiguration.Label(text: "集中モード中です", color: .label)
)
}
}

Extension Point Identifier는 com.apple.ManagedSettingsUI.shield-configuration-service다. 이쪽은 ManagedSettingsUI를 import한다.

Info.plist 설정

각 타깃의 Info.plist에 올바른 Extension Point Identifier를 설정해야 한다.

Action Extension의 경우:

<key>NSExtensionPointIdentifier</key>
<string>com.apple.ManagedSettings.shield-action-service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ShieldActionExtension</string>

Configuration Extension의 경우:

<key>NSExtensionPointIdentifier</key>
<string>com.apple.ManagedSettingsUI.shield-configuration-service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ShieldConfigurationExtension</string>

App Group 설정도 빼먹지 말 것

두 개의 Extension과 메인 앱은 같은 App Group을 공유해야 한다. App Group은 여러 타깃 간에 데이터를 공유하기 위한 구조다. iOS에서는 보통 앱끼리 직접 데이터를 주고받을 수 없지만, 같은 App Group에 속해 있으면 공유 컨테이너를 통해 읽고 쓸 수 있다.

Xcode의 Signing & Capabilities에서 각 타깃에 App Groups를 추가하고 같은 group ID를 설정한다. 메인 앱, Action Extension, Configuration Extension 세 곳 모두에 필요하다. iOS 개발이 처음이면 “설정한 줄 알았는데 하나 빠져 있었다” 같은 실수를 하기 쉽다. 하나라도 빠지면 실기기에서 데이터가 공유되지 않는다.

shield 화면에서 할 수 있는 것

ShieldActionDelegate가 반환할 수 있는 값은 .close.defer 두 가지뿐이다. .close는 차단을 유지한 채 shield를 닫는다. .defer는 추가 사용자 동작을 트리거할 때 사용한다.

이 인터페이스만으로 shield에서 메인 앱을 직접 열고 언락 화면을 띄우는 동작은 구현할 수 없다. 이것도 iOS 개발을 막 시작했을 때는 왜 안 되는지 감이 잘 오지 않는 부분이다. 그 구현 패턴은 별도 글로 분리해 두었다.