From e001c0834bf2104c769055ea6db56d1b22eadccd Mon Sep 17 00:00:00 2001 From: Andrei Salavei Date: Thu, 7 Dec 2023 16:44:17 +0100 Subject: [PATCH] Prototype UIKit Utils module (#902) Implement UIKit Utils library on Objective-C Kotlin module: compose:ui:ui-uikit Package: androidx.compose.ui.uikit.utils Objc-Library: CMPUIKitUtils Location: ./compose/ui/ui-uikit --- .../kotlin/AbstractComposePublishingTask.kt | 9 +- buildSrc/src/main/kotlin/ComposePlatforms.kt | 6 + compose/ui/ui-uikit/build.gradle | 135 ++++ compose/ui/ui-uikit/src/.gitignore | 3 + .../src/nativeInterop/cinterop/utils.def | 3 + .../compose/ui/utils/Placeholder.uikit.kt | 17 + .../CMPUIKitUtils.xcodeproj/project.pbxproj | 611 ++++++++++++++++++ .../CMPUIKitUtils/CMPUIKitUtils.h | 26 + .../CMPUIKitUtils/CMPUIKitUtils/CMPView.h | 29 + .../CMPUIKitUtils/CMPUIKitUtils/CMPView.m | 33 + .../CMPUIKitUtils/CMPViewController.h | 28 + .../CMPUIKitUtils/CMPViewController.m | 227 +++++++ .../CMPUIKitUtilsTestApp.swift | 17 + .../CMPUIKitUtilsTests-Bridging-Header.h | 17 + .../CMPViewControllerTests.swift | 160 +++++ .../Utils/MockAppDelegate.swift | 37 ++ .../CMPUIKitUtilsTests/Utils/XCTestCase.swift | 47 ++ compose/ui/ui/build.gradle | 5 + .../compose/ui/window/ComposeWindow.uikit.kt | 3 + mpp/build.gradle.kts | 4 + settings.gradle | 1 + 21 files changed, 1416 insertions(+), 2 deletions(-) create mode 100644 compose/ui/ui-uikit/build.gradle create mode 100644 compose/ui/ui-uikit/src/.gitignore create mode 100644 compose/ui/ui-uikit/src/nativeInterop/cinterop/utils.def create mode 100644 compose/ui/ui-uikit/src/uikitMain/kotlin/androidx/compose/ui/utils/Placeholder.uikit.kt create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils.xcodeproj/project.pbxproj create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPUIKitUtils.h create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPView.h create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPView.m create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPViewController.h create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPViewController.m create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTestApp/CMPUIKitUtilsTestApp.swift create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/CMPUIKitUtilsTests-Bridging-Header.h create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/CMPViewControllerTests.swift create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/Utils/MockAppDelegate.swift create mode 100644 compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/Utils/XCTestCase.swift diff --git a/buildSrc/src/main/kotlin/AbstractComposePublishingTask.kt b/buildSrc/src/main/kotlin/AbstractComposePublishingTask.kt index 473fd4b9ad7e4..3f289025e4bb4 100644 --- a/buildSrc/src/main/kotlin/AbstractComposePublishingTask.kt +++ b/buildSrc/src/main/kotlin/AbstractComposePublishingTask.kt @@ -39,8 +39,13 @@ abstract class AbstractComposePublishingTask : DefaultTask() { // To make OEL publishing (for android artifacts) work properly with kotlin >= 1.9.0, // we use decorated `KotlinMultiplatform` publication named - 'KotlinMultiplatformDecorated'. // see AndroidXComposeMultiplatformExtensionImpl.publishAndroidxReference for details. - val kotlinCommonPublicationName = "${ComposePlatforms.KotlinMultiplatform.name}Decorated" - dependsOnComposeTask("${component.path}:publish${kotlinCommonPublicationName}PublicationTo$repository") + if (ComposePlatforms.ANDROID.any { it in component.supportedPlatforms }) { + val kotlinCommonPublicationName = + "${ComposePlatforms.KotlinMultiplatform.name}Decorated" + dependsOnComposeTask("${component.path}:publish${kotlinCommonPublicationName}PublicationTo$repository") + } else { + dependsOnComposeTask("${component.path}:publish${ComposePlatforms.KotlinMultiplatform.name}PublicationTo$repository") + } for (platform in targetPlatforms) { if (platform !in component.supportedPlatforms) continue diff --git a/buildSrc/src/main/kotlin/ComposePlatforms.kt b/buildSrc/src/main/kotlin/ComposePlatforms.kt index cc548796b90fa..59fd00a7a2bad 100644 --- a/buildSrc/src/main/kotlin/ComposePlatforms.kt +++ b/buildSrc/src/main/kotlin/ComposePlatforms.kt @@ -41,6 +41,12 @@ enum class ComposePlatforms(vararg val alternativeNames: String) { ComposePlatforms.AndroidRelease ) + val IOS = EnumSet.of( + ComposePlatforms.UikitX64, + ComposePlatforms.UikitArm64, + ComposePlatforms.UikitSimArm64 + ) + val ANDROID = EnumSet.of( ComposePlatforms.AndroidDebug, ComposePlatforms.AndroidRelease diff --git a/compose/ui/ui-uikit/build.gradle b/compose/ui/ui-uikit/build.gradle new file mode 100644 index 0000000000000..2499e40112b06 --- /dev/null +++ b/compose/ui/ui-uikit/build.gradle @@ -0,0 +1,135 @@ +/* + * Copyright 2023 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 androidx.build.LibraryType + +plugins { + id("AndroidXPlugin") + id("kotlin-multiplatform") +} + +kotlin { + iosX64("uikitX64") { + configureCInterop(it, false) + } + iosArm64("uikitArm64") { + configureCInterop(it, true) + } + iosSimulatorArm64("uikitSimArm64") { + configureCInterop(it, false) + } + + sourceSets { + commonMain {} + def uikitMain = sourceSets.create("uikitMain") + def uikitX64Main = sourceSets.getByName("uikitX64Main") + def uikitArm64Main = sourceSets.getByName("uikitArm64Main") + def uikitSimArm64Main = sourceSets.getByName("uikitSimArm64Main") + + uikitMain.dependsOn(commonMain) + uikitX64Main.dependsOn(uikitMain) + uikitArm64Main.dependsOn(uikitMain) + uikitSimArm64Main.dependsOn(uikitMain) + } +} + +def configureCInterop(target, isDevice) { + def frameworkName = "CMPUIKitUtils" + def schemeName = frameworkName + def objcDir = new File(project.projectDir, "src/uikitMain/objc") + def frameworkSourcesDir = new File(objcDir, frameworkName) + def sdkName + def destination + def architectures + if (isDevice) { + sdkName = "iphoneos" + destination = "generic/platform=iOS" + architectures = "arm64" + } else { + sdkName = "iphonesimulator" + destination = "generic/platform=iOS Simulator" + architectures = "arm64 x86_64" + } + def buildDir = new File(project.buildDir, "objc/${sdkName}.xcarchive") + def frameworkPath = new File(buildDir, "/Products/usr/local/lib/lib${frameworkName}.a") + def headersPath = new File(frameworkSourcesDir, frameworkName) + + def systemFrameworks = ["UIKit", frameworkName] + def linkerFlags = ["-ObjC"] + systemFrameworks.collectMany { + ["-framework", it] + } + def compilerArgs = [ + "-include-binary", frameworkPath.toString(), + ] + linkerFlags.collectMany { + ["-linker-option", it] + } + + target.compilations.main { + def taskName = "${compileTaskProvider.name}ObjCLib" + project.tasks.register(taskName, Exec) { + inputs.dir(frameworkSourcesDir) + .withPropertyName("${frameworkName}-${sdkName}") + .withPathSensitivity(PathSensitivity.RELATIVE) + + outputs.cacheIf { true } + outputs.dir(buildDir) + .withPropertyName("${frameworkName}-${sdkName}-archive") + + workingDir(frameworkSourcesDir) + commandLine("xcodebuild") + args( + "archive", + "-scheme", schemeName, + "-archivePath", buildDir, + "-sdk", sdkName, + "-destination", destination, + "SKIP_INSTALL=NO", + "BUILD_LIBRARY_FOR_DISTRIBUTION=YES", + "VALID_ARCHS=" + architectures, + "MACH_O_TYPE=staticlib" + ) + } + + tasks[compileTaskProvider.name].dependsOn(taskName) + + cinterops { + utils { + headersPath.eachFileRecurse { + if (it.name.endsWith('.h')) { + headers(it) + } + } + } + } + } + + target.binaries.all { + freeCompilerArgs += compilerArgs + } + target.compilations.all { + kotlinOptions { + freeCompilerArgs += compilerArgs + } + } +} + +androidx { + name = "Compose UIKit Utils" + type = LibraryType.PUBLISHED_LIBRARY + inceptionYear = "2023" + description = "Internal iOS UIKit utilities including Objective-C library." + legacyDisableKotlinStrictApiMode = true +} \ No newline at end of file diff --git a/compose/ui/ui-uikit/src/.gitignore b/compose/ui/ui-uikit/src/.gitignore new file mode 100644 index 0000000000000..391131ae81541 --- /dev/null +++ b/compose/ui/ui-uikit/src/.gitignore @@ -0,0 +1,3 @@ +xcuserdata +xcshareddata +build diff --git a/compose/ui/ui-uikit/src/nativeInterop/cinterop/utils.def b/compose/ui/ui-uikit/src/nativeInterop/cinterop/utils.def new file mode 100644 index 0000000000000..75750a07051e7 --- /dev/null +++ b/compose/ui/ui-uikit/src/nativeInterop/cinterop/utils.def @@ -0,0 +1,3 @@ +linkerOpts = -framework UIKit +package = androidx.compose.ui.uikit.utils +language = Objective-C \ No newline at end of file diff --git a/compose/ui/ui-uikit/src/uikitMain/kotlin/androidx/compose/ui/utils/Placeholder.uikit.kt b/compose/ui/ui-uikit/src/uikitMain/kotlin/androidx/compose/ui/utils/Placeholder.uikit.kt new file mode 100644 index 0000000000000..887663e2d7b4b --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/kotlin/androidx/compose/ui/utils/Placeholder.uikit.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023 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. + */ + +package androidx.compose.ui.uikit.utils diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils.xcodeproj/project.pbxproj b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000..247c68cc0ce26 --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils.xcodeproj/project.pbxproj @@ -0,0 +1,611 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 997DFCDB2B18D126000B56B5 /* CMPView.m in Sources */ = {isa = PBXBuildFile; fileRef = 997DFCDA2B18D126000B56B5 /* CMPView.m */; }; + 997DFCDE2B18D135000B56B5 /* CMPViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 997DFCDD2B18D135000B56B5 /* CMPViewController.m */; }; + 997DFCE62B18D99E000B56B5 /* CMPViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DFCE52B18D99E000B56B5 /* CMPViewControllerTests.swift */; }; + 997DFCE72B18D99E000B56B5 /* libCMPUIKitUtils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 996EFEEA2B02CE5D0000FE0F /* libCMPUIKitUtils.a */; }; + 997DFCEE2B18DB7B000B56B5 /* CMPViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 997DFCDD2B18D135000B56B5 /* CMPViewController.m */; }; + 997DFCEF2B18DB7E000B56B5 /* CMPView.m in Sources */ = {isa = PBXBuildFile; fileRef = 997DFCDA2B18D126000B56B5 /* CMPView.m */; }; + 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 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 997DFCE82B18D99E000B56B5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9975AAC12AEABB5600AF155F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 996EFEE92B02CE5D0000FE0F; + remoteInfo = CMPUIKitUtils; + }; + 997DFD082B18E5DC000B56B5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9975AAC12AEABB5600AF155F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 997DFCF92B18E5D3000B56B5; + remoteInfo = CMPUIKitUtilsTestApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 996EFEE82B02CE5D0000FE0F /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 996EFEEA2B02CE5D0000FE0F /* libCMPUIKitUtils.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCMPUIKitUtils.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 996EFEF52B02CE8A0000FE0F /* CMPUIKitUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPUIKitUtils.h; sourceTree = ""; }; + 997DFCD92B18D126000B56B5 /* CMPView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPView.h; sourceTree = ""; }; + 997DFCDA2B18D126000B56B5 /* CMPView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMPView.m; sourceTree = ""; }; + 997DFCDC2B18D135000B56B5 /* CMPViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMPViewController.h; sourceTree = ""; }; + 997DFCDD2B18D135000B56B5 /* CMPViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMPViewController.m; sourceTree = ""; }; + 997DFCE32B18D99E000B56B5 /* CMPUIKitUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CMPUIKitUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 997DFCE52B18D99E000B56B5 /* CMPViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMPViewControllerTests.swift; sourceTree = ""; }; + 997DFCF02B18DBF2000B56B5 /* CMPUIKitUtilsTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CMPUIKitUtilsTests-Bridging-Header.h"; sourceTree = ""; }; + 997DFCF22B18DE59000B56B5 /* MockAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAppDelegate.swift; sourceTree = ""; }; + 997DFCF42B18E276000B56B5 /* XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = ""; }; + 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 996EFEE72B02CE5D0000FE0F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 997DFCE02B18D99E000B56B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 997DFCE72B18D99E000B56B5 /* libCMPUIKitUtils.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 997DFCF72B18E5D3000B56B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 996EFEEB2B02CE5D0000FE0F /* CMPUIKitUtils */ = { + isa = PBXGroup; + children = ( + 996EFEF52B02CE8A0000FE0F /* CMPUIKitUtils.h */, + 997DFCD92B18D126000B56B5 /* CMPView.h */, + 997DFCDA2B18D126000B56B5 /* CMPView.m */, + 997DFCDC2B18D135000B56B5 /* CMPViewController.h */, + 997DFCDD2B18D135000B56B5 /* CMPViewController.m */, + ); + path = CMPUIKitUtils; + sourceTree = ""; + }; + 9975AAC02AEABB5600AF155F = { + isa = PBXGroup; + children = ( + 996EFEEB2B02CE5D0000FE0F /* CMPUIKitUtils */, + 997DFCE42B18D99E000B56B5 /* CMPUIKitUtilsTests */, + 997DFCFB2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp */, + 9975AACB2AEABB5600AF155F /* Products */, + ); + sourceTree = ""; + }; + 9975AACB2AEABB5600AF155F /* Products */ = { + isa = PBXGroup; + children = ( + 996EFEEA2B02CE5D0000FE0F /* libCMPUIKitUtils.a */, + 997DFCE32B18D99E000B56B5 /* CMPUIKitUtilsTests.xctest */, + 997DFCFA2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 997DFCE42B18D99E000B56B5 /* CMPUIKitUtilsTests */ = { + isa = PBXGroup; + children = ( + 997DFCF02B18DBF2000B56B5 /* CMPUIKitUtilsTests-Bridging-Header.h */, + 997DFCE52B18D99E000B56B5 /* CMPViewControllerTests.swift */, + 997DFCF12B18DE47000B56B5 /* Utils */, + ); + path = CMPUIKitUtilsTests; + sourceTree = ""; + }; + 997DFCF12B18DE47000B56B5 /* Utils */ = { + isa = PBXGroup; + children = ( + 997DFCF22B18DE59000B56B5 /* MockAppDelegate.swift */, + 997DFCF42B18E276000B56B5 /* XCTestCase.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 997DFCFB2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp */ = { + isa = PBXGroup; + children = ( + 997DFCFC2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.swift */, + ); + path = CMPUIKitUtilsTestApp; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 996EFEE92B02CE5D0000FE0F /* CMPUIKitUtils */ = { + isa = PBXNativeTarget; + buildConfigurationList = 996EFEF22B02CE5D0000FE0F /* Build configuration list for PBXNativeTarget "CMPUIKitUtils" */; + buildPhases = ( + 996EFEE62B02CE5D0000FE0F /* Sources */, + 996EFEE72B02CE5D0000FE0F /* Frameworks */, + 996EFEE82B02CE5D0000FE0F /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CMPUIKitUtils; + productName = CMPUIKitStatic; + productReference = 996EFEEA2B02CE5D0000FE0F /* libCMPUIKitUtils.a */; + productType = "com.apple.product-type.library.static"; + }; + 997DFCE22B18D99E000B56B5 /* CMPUIKitUtilsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 997DFCEC2B18D99E000B56B5 /* Build configuration list for PBXNativeTarget "CMPUIKitUtilsTests" */; + buildPhases = ( + 997DFCDF2B18D99E000B56B5 /* Sources */, + 997DFCE02B18D99E000B56B5 /* Frameworks */, + 997DFCE12B18D99E000B56B5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 997DFCE92B18D99E000B56B5 /* PBXTargetDependency */, + 997DFD092B18E5DC000B56B5 /* PBXTargetDependency */, + ); + name = CMPUIKitUtilsTests; + productName = CMPUIKitUtilsTests; + productReference = 997DFCE32B18D99E000B56B5 /* CMPUIKitUtilsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 997DFCF92B18E5D3000B56B5 /* CMPUIKitUtilsTestApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 997DFD052B18E5D4000B56B5 /* Build configuration list for PBXNativeTarget "CMPUIKitUtilsTestApp" */; + buildPhases = ( + 997DFCF62B18E5D3000B56B5 /* Sources */, + 997DFCF72B18E5D3000B56B5 /* Frameworks */, + 997DFCF82B18E5D3000B56B5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CMPUIKitUtilsTestApp; + productName = CMPUIKitUtilsTestApp; + productReference = 997DFCFA2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9975AAC12AEABB5600AF155F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 996EFEE92B02CE5D0000FE0F = { + CreatedOnToolsVersion = 15.0; + }; + 997DFCE22B18D99E000B56B5 = { + CreatedOnToolsVersion = 15.0; + TestTargetID = 997DFCF92B18E5D3000B56B5; + }; + 997DFCF92B18E5D3000B56B5 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = 9975AAC42AEABB5600AF155F /* Build configuration list for PBXProject "CMPUIKitUtils" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 9975AAC02AEABB5600AF155F; + productRefGroup = 9975AACB2AEABB5600AF155F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 996EFEE92B02CE5D0000FE0F /* CMPUIKitUtils */, + 997DFCE22B18D99E000B56B5 /* CMPUIKitUtilsTests */, + 997DFCF92B18E5D3000B56B5 /* CMPUIKitUtilsTestApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 997DFCE12B18D99E000B56B5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 997DFCF82B18E5D3000B56B5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 996EFEE62B02CE5D0000FE0F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 997DFCDE2B18D135000B56B5 /* CMPViewController.m in Sources */, + 997DFCDB2B18D126000B56B5 /* CMPView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 997DFCDF2B18D99E000B56B5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 997DFCF52B18E276000B56B5 /* XCTestCase.swift in Sources */, + 997DFCE62B18D99E000B56B5 /* CMPViewControllerTests.swift in Sources */, + 997DFCEE2B18DB7B000B56B5 /* CMPViewController.m in Sources */, + 997DFCF32B18DE59000B56B5 /* MockAppDelegate.swift in Sources */, + 997DFCEF2B18DB7E000B56B5 /* CMPView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 997DFCF62B18E5D3000B56B5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 997DFCFD2B18E5D3000B56B5 /* CMPUIKitUtilsTestApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 997DFCE92B18D99E000B56B5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 996EFEE92B02CE5D0000FE0F /* CMPUIKitUtils */; + targetProxy = 997DFCE82B18D99E000B56B5 /* PBXContainerItemProxy */; + }; + 997DFD092B18E5DC000B56B5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 997DFCF92B18E5D3000B56B5 /* CMPUIKitUtilsTestApp */; + targetProxy = 997DFD082B18E5DC000B56B5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 996EFEF02B02CE5D0000FE0F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = NO; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Debug; + }; + 996EFEF12B02CE5D0000FE0F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = NO; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 9975AADA2AEABB5600AF155F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9975AADB2AEABB5600AF155F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 997DFCEA2B18D99E000B56B5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = ( + "$(inderited)", + "CMPUIKitUtils/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = JetBrains.CMPUIKitUtilsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "CMPUIKitUtilsTests/CMPUIKitUtilsTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CMPUIKitUtilsTestApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CMPUIKitUtilsTestApp"; + }; + name = Debug; + }; + 997DFCEB2B18D99E000B56B5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = ( + "$(inderited)", + "CMPUIKitUtils/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = JetBrains.CMPUIKitUtilsTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "CMPUIKitUtilsTests/CMPUIKitUtilsTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CMPUIKitUtilsTestApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CMPUIKitUtilsTestApp"; + }; + name = Release; + }; + 997DFD062B18E5D4000B56B5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = JetBrains.CMPUIKitUtilsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 997DFD072B18E5D4000B56B5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = JetBrains.CMPUIKitUtilsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 996EFEF22B02CE5D0000FE0F /* Build configuration list for PBXNativeTarget "CMPUIKitUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 996EFEF02B02CE5D0000FE0F /* Debug */, + 996EFEF12B02CE5D0000FE0F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9975AAC42AEABB5600AF155F /* Build configuration list for PBXProject "CMPUIKitUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9975AADA2AEABB5600AF155F /* Debug */, + 9975AADB2AEABB5600AF155F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 997DFCEC2B18D99E000B56B5 /* Build configuration list for PBXNativeTarget "CMPUIKitUtilsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 997DFCEA2B18D99E000B56B5 /* Debug */, + 997DFCEB2B18D99E000B56B5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 997DFD052B18E5D4000B56B5 /* Build configuration list for PBXNativeTarget "CMPUIKitUtilsTestApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 997DFD062B18E5D4000B56B5 /* Debug */, + 997DFD072B18E5D4000B56B5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 9975AAC12AEABB5600AF155F /* Project object */; +} diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPUIKitUtils.h b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPUIKitUtils.h new file mode 100644 index 0000000000000..d80aa8757c991 --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPUIKitUtils.h @@ -0,0 +1,26 @@ +/* + * Copyright 2022 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 + +//! Project version number for CMPUIKitUtils. +FOUNDATION_EXPORT double CMPUIKitUtilsVersionNumber; + +//! Project version string for CMPUIKitUtils. +FOUNDATION_EXPORT const unsigned char CMPUIKitUtilsVersionString[]; + +#import "CMPView.h" +#import "CMPViewController.h" diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPView.h b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPView.h new file mode 100644 index 0000000000000..c5e84b441f5b2 --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPView.h @@ -0,0 +1,29 @@ +/* + * Copyright 2022 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface CMPView : UIView + +@property (nonatomic, copy) void (^windowDidChangeBlock)(void); + +@end + +NS_ASSUME_NONNULL_END + +void test_CMPUIKitUtils(void); diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPView.m b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPView.m new file mode 100644 index 0000000000000..b568da040d209 --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPView.m @@ -0,0 +1,33 @@ +/* + * Copyright 2022 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" + +@implementation CMPView + +- (void)willMoveToWindow:(UIWindow *)newWindow { + [super willMoveToWindow:newWindow]; + + if (self.windowDidChangeBlock) { + self.windowDidChangeBlock(); + } +} + +@end + +void test_CMPUIKitUtils(void) { + NSLog(@"Hello from CMPUIKitUtils"); +} diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPViewController.h b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPViewController.h new file mode 100644 index 0000000000000..68f09e5bfca5b --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPViewController.h @@ -0,0 +1,28 @@ +/* + * Copyright 2022 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface CMPViewController : UIViewController + +- (void)viewControllerDidEnterWindowHierarchy; +- (void)viewControllerDidLeaveWindowHierarchy; + +@end + +NS_ASSUME_NONNULL_END diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPViewController.m b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPViewController.m new file mode 100644 index 0000000000000..fffcabe5e7751 --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtils/CMPViewController.m @@ -0,0 +1,227 @@ +/* + * Copyright 2022 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 "CMPViewController.h" +#import "CMPView.h" +#import + +#pragma mark - CMPDeinitNotifier + +@interface CMPDeinitNotifier: NSObject + +@property (nonatomic, copy) void (^deinitHandlerBlock)(void); + ++ (instancetype)notifierWithDeinitHandler:(void (^)(void))deinitHandlerBlock; + +@end + +@implementation CMPDeinitNotifier + ++ (instancetype)notifierWithDeinitHandler:(void (^)(void))deinitHandlerBlock { + CMPDeinitNotifier *notifier = [CMPDeinitNotifier new]; + notifier.deinitHandlerBlock = deinitHandlerBlock; + return notifier; +} + +- (void)dealloc { + if (self.deinitHandlerBlock) { + self.deinitHandlerBlock(); + } +} + +@end + +#pragma mark - UIViewController + CMPUIKitUtilsPrivate + +@interface UIViewController(CMPUIKitUtilsPrivate) + +@end + +@implementation UIViewController(CMPUIKitUtilsPrivate) + +static const void *const kCMPUIKitUtilsDeinitMonitorKey = @"cmp_uikit_utils_deinit_monitor_key"; + +- (NSMutableArray *)cmp_uikit_utils_deinitMonitor { + NSMutableArray *observers = objc_getAssociatedObject(self, kCMPUIKitUtilsDeinitMonitorKey); + if (observers) { + return observers; + } + observers = [NSMutableArray new]; + objc_setAssociatedObject(self, kCMPUIKitUtilsDeinitMonitorKey, observers, OBJC_ASSOCIATION_RETAIN); + return observers; +} + +@end + + +#pragma mark - CMPViewController + +@interface CMPViewController() + +@property (nonatomic, assign) BOOL cmp_needsVerifyHierarchy; +@property (nonatomic, assign) BOOL cmp_viewControllerInWindowHierarchy; +@property (nonatomic, copy) void (^cmp_terminateComposeSceneTask)(void); +@property (nonatomic, weak) CMPDeinitNotifier *cmp_parentDeinitNotifier; + +@end + +@implementation CMPViewController + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + self.cmp_viewControllerInWindowHierarchy = true; +} + +- (void)viewControllerDidLeaveWindowHierarchy {} + +- (void)viewControllerDidEnterWindowHierarchy {} + +- (void)cmp_deinitScene { + self.cmp_terminateComposeSceneTask = false; +} + +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + + [self cmp_setNeedsVerifyHierarchy]; +} + +- (void)didMoveToParentViewController:(UIViewController *)parent { + self.cmp_parentDeinitNotifier.deinitHandlerBlock = nil; + + if (parent) { + __weak typeof(self) weakSelf = self; + CMPDeinitNotifier *notifier = [CMPDeinitNotifier notifierWithDeinitHandler:^{ + [weakSelf cmp_setNeedsVerifyHierarchy]; + }]; + [parent.cmp_uikit_utils_deinitMonitor addObject:notifier]; + + self.cmp_parentDeinitNotifier = notifier; + } else if (self.parentViewController != nil && self.cmp_parentDeinitNotifier != nil) { + [self.parentViewController.cmp_uikit_utils_deinitMonitor filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(CMPDeinitNotifier *notifier, NSDictionary * _Nullable bindings) { + return notifier != self.cmp_parentDeinitNotifier; + }]]; + } + + [super didMoveToParentViewController:parent]; + + if (parent != nil && !self.cmp_viewControllerInWindowHierarchy) { + [self cmp_verifyHierarchyAndScheduleNotificationIfNeeded]; + } + + [self cmp_setNeedsVerifyHierarchy]; +} + +- (void)removeFromParentViewController { + [super removeFromParentViewController]; + + [self cmp_setNeedsVerifyHierarchy]; +} + +- (void)viewDidLoad { + if ([self.view isKindOfClass:[CMPView class]]) { + __weak typeof(self) weakSelf = self; + ((CMPView *)self.view).windowDidChangeBlock = ^{ + [weakSelf cmp_setNeedsVerifyHierarchy]; + }; + } else { + NSAssert(false, @"The view of CMPViewController should be either CMPView or subclassed from CMPView"); + } + + [super viewDidLoad]; +} + +- (void)loadView { + self.view = [CMPView new]; +} + +- (void)cmp_setNeedsVerifyHierarchy { + if (self.cmp_needsVerifyHierarchy) { + return; + } + self.cmp_needsVerifyHierarchy = true; + + __weak typeof(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + weakSelf.cmp_needsVerifyHierarchy = false; + [weakSelf cmp_verifyHierarchyAndScheduleNotificationIfNeeded]; + }); +} + +- (void)cmp_verifyHierarchyAndScheduleNotificationIfNeeded { + if ([self cmp_isInWindowHierarchy]) { + self.cmp_viewControllerInWindowHierarchy = true; + [self cmp_cancelFinalizeAndNotifyViewControllerIsInHierarchy]; + } else { + [self cmp_setNeedsFinalizeAndNotifyViewControllerIsInHierarchy]; + } +} + +- (void)cmp_setNeedsFinalizeAndNotifyViewControllerIsInHierarchy { + if (self.cmp_terminateComposeSceneTask == nil && self.cmp_viewControllerInWindowHierarchy) { + __weak typeof(self) weakSelf = self; + self.cmp_terminateComposeSceneTask = ^{ + [weakSelf cmp_finalizeAndNotifyViewControllerIsInHierarchy]; + weakSelf.cmp_terminateComposeSceneTask = nil; + }; + dispatch_async(dispatch_get_main_queue(), self.cmp_terminateComposeSceneTask); + } +} + +- (void)cmp_finalizeAndNotifyViewControllerIsInHierarchy { + if ([self cmp_isInWindowHierarchy]) { + self.cmp_viewControllerInWindowHierarchy = true; + } else { + self.cmp_viewControllerInWindowHierarchy = false; + } +} + +- (void)cmp_cancelFinalizeAndNotifyViewControllerIsInHierarchy { + if (self.cmp_terminateComposeSceneTask) { + dispatch_block_cancel(self.cmp_terminateComposeSceneTask); + } + self.cmp_terminateComposeSceneTask = nil; +} + +- (BOOL)cmp_isInWindowHierarchy { + return [self cmp_isViewControllerInWindowHierarchy:self]; +} + +- (BOOL)cmp_isViewControllerInWindowHierarchy:(UIViewController *)viewController { + if (viewController.view.window != nil) { + return YES; + } else if (viewController.parentViewController != nil) { + return [self cmp_isViewControllerInWindowHierarchy:viewController.parentViewController]; + } else { + return NO; + } +} + +- (void)setCmp_viewControllerInWindowHierarchy:(BOOL)cmp_viewControllerInWindowHierarchy { + if (_cmp_viewControllerInWindowHierarchy == cmp_viewControllerInWindowHierarchy) { + return; + } + _cmp_viewControllerInWindowHierarchy = cmp_viewControllerInWindowHierarchy; + + if (cmp_viewControllerInWindowHierarchy) { + [self viewControllerDidEnterWindowHierarchy]; + } else { + [self viewControllerDidLeaveWindowHierarchy]; + } +} + +@end diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTestApp/CMPUIKitUtilsTestApp.swift b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTestApp/CMPUIKitUtilsTestApp.swift new file mode 100644 index 0000000000000..de46d4e975e55 --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTestApp/CMPUIKitUtilsTestApp.swift @@ -0,0 +1,17 @@ +// +// CMPUIKitUtilsTestAppApp.swift +// CMPUIKitUtilsTestApp +// +// Created by Andrei.Salavei on 30.11.23. +// + +import SwiftUI + +@main +struct CMPUIKitUtilsTestApp: App { + var body: some Scene { + WindowGroup { + Color.black + } + } +} diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/CMPUIKitUtilsTests-Bridging-Header.h b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/CMPUIKitUtilsTests-Bridging-Header.h new file mode 100644 index 0000000000000..532f0b81698fd --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/CMPUIKitUtilsTests-Bridging-Header.h @@ -0,0 +1,17 @@ +/* + * Copyright 2022 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 "CMPUIKitUtils.h" diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/CMPViewControllerTests.swift b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/CMPViewControllerTests.swift new file mode 100644 index 0000000000000..7608163128d28 --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/CMPViewControllerTests.swift @@ -0,0 +1,160 @@ +/* + * Copyright 2022 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 XCTest + +final class CMPViewControllerTests: XCTestCase { + var appDelegate: MockAppDelegate! + + override func setUpWithError() throws { + super.setUp() + + appDelegate = MockAppDelegate() + UIApplication.shared.delegate = appDelegate + appDelegate.setUpClearWindow() + } + + override func tearDownWithError() throws { + super.tearDown() + + appDelegate?.cleanUp() + appDelegate = nil + } + + public func testControllerPresent() { + let viewController = TestViewController() + XCTAssertFalse(viewController.viewIsInWindowHierarchy) + + appDelegate.window?.rootViewController?.present(viewController, animated: true) + wait { viewController.viewIsInWindowHierarchy == true } + + appDelegate.window?.rootViewController?.dismiss(animated: true) + wait { viewController.viewIsInWindowHierarchy == false } + } + + public func testChildController() { + let viewController1 = TestViewController() + let viewController2 = TestViewController() + + appDelegate.window?.rootViewController?.present(viewController1, animated: true) + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == false } + + viewController1.addChild(viewController2) + viewController2.didMove(toParent: viewController1) + viewController1.view.addSubview(viewController2.view) + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == true } + + viewController2.removeFromParent() + viewController2.view.removeFromSuperview() + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == false } + + viewController1.addChild(viewController2) + viewController2.didMove(toParent: viewController1) + viewController1.view.addSubview(viewController2.view) + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == true } + + appDelegate.window?.rootViewController?.dismiss(animated: true) + wait { viewController1.viewIsInWindowHierarchy == false } + wait { viewController2.viewIsInWindowHierarchy == false } + } + + public func testNavigationControllerPresentAndPush() { + let viewController1 = TestViewController() + let viewController2 = TestViewController() + let viewController3 = TestViewController() + + // Use autoreleasepool to be sure, navigationController will be properly deleted after dismissal + autoreleasepool { + let navigationController = UINavigationController(rootViewController: viewController1) + + appDelegate.window?.rootViewController?.present(navigationController, animated: true) + + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == false } + wait { viewController3.viewIsInWindowHierarchy == false } + + navigationController.pushViewController(viewController2, animated: true) + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == true } + wait { viewController3.viewIsInWindowHierarchy == false } + + navigationController.present(viewController3, animated: true) + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == true } + wait { viewController3.viewIsInWindowHierarchy == true } + + viewController3.dismiss(animated: true) + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == true } + wait { viewController3.viewIsInWindowHierarchy == false } + + navigationController.dismiss(animated: true) + } + wait { viewController1.viewIsInWindowHierarchy == false } + wait { viewController2.viewIsInWindowHierarchy == false } + wait { viewController3.viewIsInWindowHierarchy == false } + } + + public func testTabBarControllerPresentAndPush() { + let viewController1 = TestViewController() + let viewController2 = TestViewController() + let viewController3 = TestViewController() + + // Use autoreleasepool to be sure, tabBarController will be properly deleted after dismissal + autoreleasepool { + let tabBarController = UITabBarController() + tabBarController.viewControllers = [viewController1, viewController2] + + appDelegate.window?.rootViewController?.present(tabBarController, animated: true) + + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == true } + wait { viewController3.viewIsInWindowHierarchy == false } + + tabBarController.present(viewController3, animated: true) + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == true } + wait { viewController3.viewIsInWindowHierarchy == true } + + viewController3.dismiss(animated: true) + wait { viewController1.viewIsInWindowHierarchy == true } + wait { viewController2.viewIsInWindowHierarchy == true } + wait { viewController3.viewIsInWindowHierarchy == false } + + tabBarController.dismiss(animated: true) + } + + wait { viewController1.viewIsInWindowHierarchy == false } + wait { viewController2.viewIsInWindowHierarchy == false } + wait { viewController3.viewIsInWindowHierarchy == false } + } +} + +private class TestViewController: CMPViewController { + public var viewIsInWindowHierarchy: Bool = false + + override func viewControllerDidEnterWindowHierarchy() { + viewIsInWindowHierarchy = true + } + + override func viewControllerDidLeaveWindowHierarchy() { + viewIsInWindowHierarchy = false + } +} diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/Utils/MockAppDelegate.swift b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/Utils/MockAppDelegate.swift new file mode 100644 index 0000000000000..eaccfa6803ac1 --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/Utils/MockAppDelegate.swift @@ -0,0 +1,37 @@ +/* + * Copyright 2022 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 + +class MockAppDelegate: NSObject, UIApplicationDelegate { + var window: UIWindow? + + func setUpClearWindow() { + window = UIWindow(frame: UIScreen.main.bounds) + window?.backgroundColor = .systemBackground + + UIView.setAnimationsEnabled(false) + window?.layer.speed = 10000 + + window?.rootViewController = UIViewController() + window?.makeKeyAndVisible() + } + + func cleanUp() { + window = nil + UIWindow(frame: UIScreen.main.bounds).makeKeyAndVisible() + } +} diff --git a/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/Utils/XCTestCase.swift b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/Utils/XCTestCase.swift new file mode 100644 index 0000000000000..1a286a97640ac --- /dev/null +++ b/compose/ui/ui-uikit/src/uikitMain/objc/CMPUIKitUtils/CMPUIKitUtilsTests/Utils/XCTestCase.swift @@ -0,0 +1,47 @@ +/* + * Copyright 2022 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 XCTest + +extension XCTestCase { + /// Non UI Thread blocking delay for at least given time interval. + func delay(_ delay: TimeInterval) { + let baseExpectation = expectation(description: name) + DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + delay) { + DispatchQueue.main.async { + baseExpectation.fulfill() + } + } + wait(for: [baseExpectation], timeout: delay + 5) + } + + /// Awaits for expectation without blocking UI thread. + func wait(for expectation: @escaping () -> Bool, + timeout: TimeInterval = 1.0, + file: String = #file, + function: String = #function, + line: Int = #line) { + let start = Date() + var isExpected = expectation() + while !isExpected && Date().timeIntervalSince(start) < timeout { + delay(0.001) + isExpected = expectation() + } + if !isExpected { + XCTFail("Timeout reached for expectation at \(file) - \(function) line \(line)") + } + } +} diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle index a317e9515a730..170d9d2f4472d 100644 --- a/compose/ui/ui/build.gradle +++ b/compose/ui/ui/build.gradle @@ -201,6 +201,11 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) { api(libs.skikoCommon) } } + uikitMain { + dependencies { + api project(":compose:ui:ui-uikit") + } + } notMobileMain.dependsOn(skikoMain) desktopMain { dependsOn(notMobileMain) diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeWindow.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeWindow.uikit.kt index acde7248cc199..020d0191766a5 100644 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeWindow.uikit.kt +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeWindow.uikit.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.scene.ComposeSceneContext import androidx.compose.ui.scene.ComposeScenePointer import androidx.compose.ui.text.input.PlatformTextInputService import androidx.compose.ui.uikit.* +import androidx.compose.ui.uikit.utils.* import androidx.compose.ui.unit.* import kotlin.math.floor import kotlin.math.roundToInt @@ -520,6 +521,8 @@ private class ComposeWindow( override fun viewDidAppear(animated: Boolean) { super.viewDidAppear(animated) + test_CMPUIKitUtils() + NSNotificationCenter.defaultCenter.addObserver( observer = keyboardVisibilityListener, selector = NSSelectorFromString(keyboardVisibilityListener::keyboardWillShow.name + ":"), diff --git a/mpp/build.gradle.kts b/mpp/build.gradle.kts index 065912b84596c..1fc9ceba7fba0 100644 --- a/mpp/build.gradle.kts +++ b/mpp/build.gradle.kts @@ -55,6 +55,10 @@ val mainComponents = ":compose:ui:ui-tooling-preview", supportedPlatforms = ComposePlatforms.JVM_BASED ), + ComposeComponent( + ":compose:ui:ui-uikit", + supportedPlatforms = ComposePlatforms.IOS + ), ComposeComponent(":compose:ui:ui-unit"), ComposeComponent(":compose:ui:ui-util"), ) diff --git a/settings.gradle b/settings.gradle index 8899899c5d03f..215fc5c71b970 100644 --- a/settings.gradle +++ b/settings.gradle @@ -247,6 +247,7 @@ includeProject(":compose:ui:ui-viewbinding") includeProject(":compose:ui:ui-viewbinding:ui-viewbinding-samples", "compose/ui/ui-viewbinding/samples") includeProject(":compose:ui:ui:integration-tests:ui-demos") includeProject(":compose:ui:ui:ui-samples", "compose/ui/ui/samples") +includeProject(":compose:ui:ui-uikit") includeProject(":lint-checks") includeProject(":lint-checks:integration-tests")