Skip to the content.

iPhone 14 Pro (Max)에서 Dynamic Island 영역을 투명하게 만들기

iPhone 14 Pro (Max)에서 스크린샷을 찍으면 위 사진처럼 까만색 알약 모양이 뜨는데…

이렇게 없애는 방법을 올립니다. iOS 16.0 기준입니다.

조건

순정 기기에서는 안 됩니다 ㅎㅎ Dynamic Island 영역은 SBSystemApertureWindowSceneSBSystemApertureCurtainWindowScene라고 하는 scene들에 의해 관리되며 이는 SpringBoard 영역이라, 제가 아는 선에서는 절대 불가능합니다 ㅎㅎ

또한 서드파티 앱에서 이를 접근하는 Public/Private API는 전혀 존재하지 않아요. 서트파티 앱에서 Dynamic Island의 위치 및 크기(Safe Area 말하는거 아님)를 받아 오는 것도 존재하지 않아요.

방법

% lldb -n SpringBoard -w

# 새로운 Terminal 창에서 SpringBoard 재시작 (Simulator일 경우)
% xcrun simctl spawn booted launchctl stop com.apple.SpringBoard

# 새로운 Terminal 창에서 SpringBoard 재시작 (실제 iOS 기기일 경우)
% killall -9 backboardd

# breakpoint 설정 - 아직 image load가 전혀 안 된 상태이고 symbolic breakpoint를 설정하는거라 경고가 나지만, 무시하셔도 됩니다.
(lldb) breakpoint set -n '-[CAGainMapLayer setRenderMode:]' -C "register write x2 0x0" -G1
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.

# continue
(lldb) c
Process 75226 resuming

끝…

탈옥 트윅을 만드시면 ‘-[CAGainMapLayer setRenderMode:]’를 hooking (swizzling)하시면 됩니다…

원리

Dynamic Island의 View Hirachy를 보면

- SBSystemApertureCurtainWindowScene 또는 SBSystemApertureWindowScene
    - SBSystemApertureWindow
        - SBFTouchPassThroughView (SBSystemApertureCaptureVisibilityShimViewController)
            - UIView (SBSystemApertureCurtainViewController)
                - _SBSystemApertureMagiciansCurtainView (SBSystemApertureCurtainViewController)
                    - _SBGainMapView
                    - UIView

이렇게 되어 있는데, Dynamic Island의 배경색을 담당하는건 _SBGainMapView 입니다.

(lldb) expression -l objc -O -- [_SBGainMapView _shortMethodDescription]
<_SBGainMapView: 0x10668cd28>:
in _SBGainMapView:
    Class Methods:
        + (Class) layerClass; (0x105c10bd8)
    Properties:
        @property (nonatomic) BOOL supportsHitTesting;  (@synthesize supportsHitTesting = _supportsHitTesting;)
    Instance Methods:
        - (id) gainMapLayer; (0x105c10be4)
        - (id) initWithFrame:(struct CGRect)arg1; (0x105c10be8)
        - (id) hitTest:(struct CGPoint)arg1 withEvent:(id)arg2; (0x105c10c30)
        - (BOOL) supportsHitTesting; (0x105c10cac)
        - (void) setSupportsHitTesting:(BOOL)arg1; (0x105c10cbc)
(UIView ...)

CALayer를 커스텀하고 있는 것으로 보이네요. 이게 뭔지 봅시다.

(lldb) expression -l objc -O -- [_SBGainMapView layerClass]
CAGainMapLayer

CAGainMapLayer라는 것을 쓰고 있네요. QuartzCore에 소속되어 있으며, 아마 Dynamic Island의 View의 Layer를 관리하기 위해 iOS 16 부터 새로 생긴 것으로 추정되네요. 이것도 뭔지 보면

(lldb) expression -l objc -O -- [CAGainMapLayer _shortMethodDescription]
<CAGainMapLayer: 0x1b6e167f8>:
in CAGainMapLayer:
    Class Methods:
        + (id) defaultValueForKey:(id)arg1; (0x187d86154)
        + (BOOL) CA_automaticallyNotifiesObservers:(Class)arg1; (0x187d861e8)
        + (BOOL) _hasRenderLayerSubclass; (0x187d8614c)
    Properties:
        @property (copy) NSString* renderMode;
    Instance Methods:
        - (void) didChangeValueForKey:(id)arg1; (0x187d86278)
        - (void*) _copyRenderLayer:(void*)arg1 layerFlags:(unsigned int)arg2 commitFlags:(unsigned int*)arg3; (0x187d86384)
        - (BOOL) _renderLayerDefinesProperty:(unsigned int)arg1; (0x187d86314)
        - (id) renderMode; (0x187d86520)
        - (void) setRenderMode:(id)arg1; (0x187d86580)
(CALayer ...)

-[CAGainMapLayer setRenderMode:]에서 breakpoint를 찍어보고 $x2 register를 읽어보니 gainFill라는 값을 넣어주고 있네요. 이 값을 날려 버리고 0x0 값을 주입할게요.

(lldb) breakpoint set -n '-[CAGainMapLayer setRenderMode:]' -C "register write x2 0x0" -G1

이렇게 하니 되네요… 과정이 많이 생략되긴 했는데 ㅎㅎ assembly 코드 하나하나 다 읽어주기엔 글이 너무 길어져서 ㅎㅎ

여담

(lldb) expression -l objc -O -- 0x160282710 
<SBSystemApertureWindow: 0x160282710; Jindo Window-0x160282710-4; baseClass = UIWindow; frame = (0 0; 430 932); gestureRecognizers = <NSArray: 0x600001bb1170>; layer = <UIWindowLayer: 0x600001bb0750>>

(lldb) expression -l objc -O -- [0x160282710 accessibilityIdentifier]
jindo
(lldb) expression -l objc -O -- [SBSystemApertureWindow _shortMethodDescription]
<SBSystemApertureWindow: 0x1065ebb20>:
in SBSystemApertureWindow:
    Class Methods:
        + (Class) safeCategoryBaseClass; (0x1538eb478)
    Instance Methods:
        - (BOOL) _accessibilityPrefersNonAttributedHint; (0x1318d2c58)
        - (BOOL) _accessibilityPrefersNonAttributedValue; (0x1318d2c3c)
        - (BOOL) _accessibilityPrefersNonAttributedLabel; (0x1318d2c58)
        - (BOOL) _accessibilityIsInJindo; (0x1538eb488)
        - (BOOL) _accessibilityInvertColorsIsDarkWindow; (0x10605de20)
        - (BOOL) accessibilityIgnoresInvertColors; (0x10605de28)
        - (void) accessibilityApplyInvertFilter; (0x10605de30)
        - (void) setAutorotates:(BOOL)arg1 forceUpdateInterfaceOrientation:(BOOL)arg2; (0x10605de34)
(SBFSecureTouchPassThroughWindow ...)

(lldb) disassemble -a 0x10605de28
SpringBoard`-[SBSystemApertureWindow accessibilityIgnoresInvertColors]:
    0x10605de28 <+0>: mov    w0, #0x1
    0x10605de2c <+4>: ret    
- _SBSystemApertureMagiciansCurtainView (SBSystemApertureCurtainViewController)
    - _SBGainMapView
    - UIView

여기서 UIView의 정체가 뭔지를 모르겠음… 아래처럼 _SBSystemApertureMagiciansCurtainView에서 _clonedGainMapSubstituteView라는 이름으로 들고 있고

(lldb) expression -l objc -O -- [0x146725070 _ivarDescription]
<_SBSystemApertureMagiciansCurtainView: 0x146725070>:
in _SBSystemApertureMagiciansCurtainView:
in _SBSystemApertureGainMapView:
    _gainMapView (_SBGainMapView*): <_SBGainMapView: 0x146725230>
    _clonedGainMapSubstituteView (UIView*): <UIView: 0x1467253f0>

검정색이고… _gainMapView와 frame도 완전히 같음… 근데 isHidden0x0임… 왜 얘를 안 건드렸는데 Dynamic Island가 사라지는 것일까? @_@…

(lldb) expression -l objc -O -- [[0x1467253f0 backgroundColor] isEqual:[UIColor blackColor]]
0x0000000000000001

(lldb) expression -l objc -O -- [0x1467253f0 isHidden]
 nil

궁금한게 참 많은데… ActivityKit이 공개적으로 풀리고 Dynamic Island 관련 API가 나오면 다시 한 번 제대로 보기로…