Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
997DFCF32B18DE59000B56B5 /* MockAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DFCF22B18DE59000B56B5 /* MockAppDelegate.swift */; };
997DFCF52B18E276000B56B5 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DFCF42B18E276000B56B5 /* XCTestCase.swift */; };
997DFCFD2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DFCFC2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.swift */; };
99CC4B2E2ECE0838007C5C44 /* CMPView.m in Sources */ = {isa = PBXBuildFile; fileRef = 99CC4B2D2ECE0838007C5C44 /* CMPView.m */; };
99CC4B2F2ECE0838007C5C44 /* CMPView.m in Sources */ = {isa = PBXBuildFile; fileRef = 99CC4B2D2ECE0838007C5C44 /* CMPView.m */; };
99CC4B302ECE0838007C5C44 /* CMPView.m in Sources */ = {isa = PBXBuildFile; fileRef = 99CC4B2D2ECE0838007C5C44 /* CMPView.m */; };
99CC4B322ECE16C8007C5C44 /* CMPViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99CC4B312ECE16C8007C5C44 /* CMPViewTests.swift */; };
99D97A882BF73A9B0035552B /* CMPEditMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = 99D97A872BF73A9B0035552B /* CMPEditMenuView.m */; };
99DCAB0E2BD00F5C002E6AC7 /* CMPTextLoupeSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 99DCAB0D2BD00F5C002E6AC7 /* CMPTextLoupeSession.m */; };
EA4B52962C2EDEF200FBB55C /* CMPGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = EA4B52952C2EDEF200FBB55C /* CMPGestureRecognizer.m */; };
Expand Down Expand Up @@ -87,6 +91,11 @@
997DFCFA2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CMPUIKitUtilsTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
997DFCFC2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMPUIKitUtilsTestApp.swift; sourceTree = "<group>"; };
99BE84D22C3467B100E43826 /* CMPUIKitUtilsTestApp.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMPUIKitUtilsTestApp.xctestplan; sourceTree = "<group>"; };
99CC4B282ECE04AC007C5C44 /* CMPComposeContainerLifecycleDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPComposeContainerLifecycleDelegate.h; sourceTree = "<group>"; };
99CC4B2B2ECE07EA007C5C44 /* CMPComposeContainerLifecycleState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPComposeContainerLifecycleState.h; sourceTree = "<group>"; };
99CC4B2C2ECE0838007C5C44 /* CMPView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPView.h; sourceTree = "<group>"; };
99CC4B2D2ECE0838007C5C44 /* CMPView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMPView.m; sourceTree = "<group>"; };
99CC4B312ECE16C8007C5C44 /* CMPViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMPViewTests.swift; sourceTree = "<group>"; };
99D97A862BF73A9B0035552B /* CMPEditMenuView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPEditMenuView.h; sourceTree = "<group>"; };
99D97A872BF73A9B0035552B /* CMPEditMenuView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMPEditMenuView.m; sourceTree = "<group>"; };
99DCAB0C2BD00F5C002E6AC7 /* CMPTextLoupeSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPTextLoupeSession.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -140,6 +149,8 @@
996EFEEB2B02CE5D0000FE0F /* CMPUIKitUtils */ = {
isa = PBXGroup;
children = (
99CC4B282ECE04AC007C5C44 /* CMPComposeContainerLifecycleDelegate.h */,
99CC4B2B2ECE07EA007C5C44 /* CMPComposeContainerLifecycleState.h */,
EA70A7E62B27106100300068 /* CMPAccessibilityElement.h */,
EA70A7E82B27106100300068 /* CMPAccessibilityElement.m */,
EADD028E2C9846D9003F66E8 /* CMPDragInteractionProxy.h */,
Expand Down Expand Up @@ -174,6 +185,8 @@
996EFEF52B02CE8A0000FE0F /* CMPUIKitUtils.h */,
997DFCDC2B18D135000B56B5 /* CMPViewController.h */,
997DFCDD2B18D135000B56B5 /* CMPViewController.m */,
99CC4B2C2ECE0838007C5C44 /* CMPView.h */,
99CC4B2D2ECE0838007C5C44 /* CMPView.m */,
);
path = CMPUIKitUtils;
sourceTree = "<group>";
Expand Down Expand Up @@ -204,6 +217,7 @@
children = (
997DFCF02B18DBF2000B56B5 /* CMPUIKitUtilsTests-Bridging-Header.h */,
997DFCE52B18D99E000B56B5 /* CMPViewControllerTests.swift */,
99CC4B312ECE16C8007C5C44 /* CMPViewTests.swift */,
997DFCF12B18DE47000B56B5 /* Utils */,
);
path = CMPUIKitUtilsTests;
Expand Down Expand Up @@ -354,6 +368,7 @@
9968C35B2D76FE16005E8DE4 /* CMPPanGestureRecognizer.m in Sources */,
991A97F72E1FB99300B47130 /* CMPScrollView.m in Sources */,
99D97A882BF73A9B0035552B /* CMPEditMenuView.m in Sources */,
99CC4B2E2ECE0838007C5C44 /* CMPView.m in Sources */,
EABD912B2BC02B5F00455279 /* CMPInteropWrappingView.m in Sources */,
EADD02902C9846D9003F66E8 /* CMPDragInteractionProxy.m in Sources */,
992EDDFB2E55EC8400FB44C5 /* CMPKeyValueObserver.m in Sources */,
Expand All @@ -371,6 +386,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
99CC4B2F2ECE0838007C5C44 /* CMPView.m in Sources */,
99CC4B322ECE16C8007C5C44 /* CMPViewTests.swift in Sources */,
997DFCF52B18E276000B56B5 /* XCTestCase.swift in Sources */,
997DFCE62B18D99E000B56B5 /* CMPViewControllerTests.swift in Sources */,
997DFCEE2B18DB7B000B56B5 /* CMPViewController.m in Sources */,
Expand All @@ -387,6 +404,7 @@
EAC703E52B8C826E001ECDA6 /* CMPOSLogger.m in Sources */,
997DFCFD2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.swift in Sources */,
EAC703E62B8C826E001ECDA6 /* CMPOSLoggerInterval.m in Sources */,
99CC4B302ECE0838007C5C44 /* CMPView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <UIKit/UIKit.h>

@protocol CMPComposeContainerLifecycleDelegate

- (void)composeContainerWillAppear;
- (void)composeContainerDidDisappear;
- (void)composeContainerWillDealloc;

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, CMPComposeContainerLifecycleState) {
CMPComposeContainerLifecycleStateInitialized,
CMPComposeContainerLifecycleStateStarted,
CMPComposeContainerLifecycleStateStopped
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ FOUNDATION_EXPORT double CMPUIKitUtilsVersionNumber;
FOUNDATION_EXPORT const unsigned char CMPUIKitUtilsVersionString[];

#import "CMPViewController.h"
#import "CMPView.h"
#import "CMPAccessibilityElement.h"
#import "CMPOSLogger.h"
#import "CMPTextLoupeSession.h"
Expand All @@ -34,3 +35,4 @@ FOUNDATION_EXPORT const unsigned char CMPUIKitUtilsVersionString[];
#import "CMPHoverGestureHandler.h"
#import "CMPScreenEdgePanGestureRecognizer.h"
#import "CMPScrollView.h"
#import "CMPComposeContainerLifecycleDelegate.h"
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <UIKit/UIKit.h>
#import "CMPComposeContainerLifecycleDelegate.h"

NS_ASSUME_NONNULL_BEGIN

@interface CMPView : UIView

- (id)initWithLifecycleDelegate:(id<CMPComposeContainerLifecycleDelegate> _Nullable)delegate;

/// Notifies the view is added to a view hierarchy
- (void)viewDidAppear;

/// Notifies the view was removed from a view hierarchy
- (void)viewDidDisappear;

/// Indicates that view is considered alive in terms of structural containment
- (void)viewDidEnterWindowHierarchy;

/// Indicates that view is considered as closed in terms of structural containment
- (void)viewDidLeaveWindowHierarchy;

/// Indicates that trait interface style trait changed
- (void)userInterfaceStyleDidChange;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "CMPView.h"
#import "CMPComposeContainerLifecycleState.h"

#pragma mark - CMPViewController

@implementation CMPView {
CMPComposeContainerLifecycleState _lifecycleState;
id<CMPComposeContainerLifecycleDelegate> _lifecycleDelegate;
BOOL _isViewInWindowHierarchy;
}

- (id)initWithLifecycleDelegate:(id<CMPComposeContainerLifecycleDelegate>)delegate {
self = [super initWithFrame:CGRectZero];

if (self) {
_lifecycleDelegate = delegate;
_lifecycleState = CMPComposeContainerLifecycleStateInitialized;
_isViewInWindowHierarchy = NO;

__weak typeof(self) weakSelf = self;
if (@available(iOS 17, *)) {
[self registerForTraitChanges:@[[UITraitUserInterfaceStyle class]]
withHandler:^(__kindof id<UITraitEnvironment> _Nonnull traitEnvironment,
UITraitCollection * _Nonnull previousCollection) {
[weakSelf userInterfaceStyleDidChange];
}];
}
}

return self;
}

- (void)didMoveToWindow {
[super didMoveToWindow];

[self updateViewAppearanceState];
}

- (void)didMoveToSuperview {
[super didMoveToSuperview];

[self updateViewAppearanceState];
}

- (void)updateViewAppearanceState {
BOOL isViewInWindowHierarchy = self.superview != nil && self.window != nil;
if (_isViewInWindowHierarchy != isViewInWindowHierarchy) {
_isViewInWindowHierarchy = isViewInWindowHierarchy;
if (isViewInWindowHierarchy) {
[_lifecycleDelegate composeContainerWillAppear];
[self transitViewLifecycleToStarted];
[self viewDidAppear];
} else {
[self viewDidDisappear];
[self scheduleViewHierarchyContainmentCheck];
[_lifecycleDelegate composeContainerDidDisappear];
}
}
}

- (void)transitViewLifecycleToStarted {
switch (_lifecycleState) {
case CMPComposeContainerLifecycleStateInitialized:
case CMPComposeContainerLifecycleStateStopped:
_lifecycleState = CMPComposeContainerLifecycleStateStarted;
[self viewDidEnterWindowHierarchy];
break;
case CMPComposeContainerLifecycleStateStarted:
break;
}
}

- (void)scheduleViewHierarchyContainmentCheck {
double delayInSeconds = 0.5;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
switch (self->_lifecycleState) {
case CMPComposeContainerLifecycleStateInitialized:
NSAssert(false, @"Attempt to schedule hierarchy check without starting the container");
break;
case CMPComposeContainerLifecycleStateStopped:
break;
case CMPComposeContainerLifecycleStateStarted:
// perform check
if (!self->_isViewInWindowHierarchy) {
self->_lifecycleState = CMPComposeContainerLifecycleStateStopped;
[self viewDidLeaveWindowHierarchy];
}
break;
}
});
}

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];

if (@available(iOS 17, *)) {
// Do nothing
} else if (self.traitCollection.userInterfaceStyle != previousTraitCollection.userInterfaceStyle) {
[self userInterfaceStyleDidChange];
}
}

- (void)viewDidAppear {
}

- (void)viewDidDisappear {
}

- (void)viewDidEnterWindowHierarchy {
}

- (void)viewDidLeaveWindowHierarchy {
}

- (void)userInterfaceStyleDidChange {
}

- (void)dealloc {
if (_lifecycleState == CMPComposeContainerLifecycleStateStarted) {
[self viewDidLeaveWindowHierarchy];
}

[_lifecycleDelegate composeContainerWillDealloc];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,22 @@
*/

#import <UIKit/UIKit.h>
#import "CMPComposeContainerLifecycleDelegate.h"

NS_ASSUME_NONNULL_BEGIN

@protocol CMPViewControllerLifecycleDelegate

- (void)viewControllerWillAppear;
- (void)viewControllerDidDisappear;
- (void)viewControllerWillDealloc;

@end

@interface CMPViewController : UIViewController

- (id)initWithLifecycleDelegate:(id<CMPViewControllerLifecycleDelegate> _Nullable)delegate;
- (id)initWithLifecycleDelegate:(id<CMPComposeContainerLifecycleDelegate> _Nullable)delegate;

/// Indicates that view controller is considered alive in terms of structural containment.
/// Overriding classes should call super.
/// Indicates that view controller is considered alive in terms of structural containment
- (void)viewControllerDidEnterWindowHierarchy;

/// Indicates that view controller is considered alive in terms of structural containment
/// Overriding classes should call super.
/// Indicates that view controller is considered closed in terms of structural containment
- (void)viewControllerDidLeaveWindowHierarchy;


// MARK: Unexported methods redeclaration block
// Redeclared to make it visible to Kotlin for override purposes, workaround for the following issue:
// https://youtrack.jetbrains.com/issue/KT-56001/Kotlin-Native-import-Objective-C-category-members-as-class-members-if-the-category-is-located-in-the-same-file

- (void)viewSafeAreaInsetsDidChange;
/// Indicates that trait interface style trait changed
- (void)userInterfaceStyleDidChange;

@end

Expand Down
Loading