diff --git a/gradle.properties b/gradle.properties index 9f9a44a5..d149d3a9 100755 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,8 @@ kotlin.code.style=official kotlin.mpp.stability.nowarn=true kotlin.mpp.androidGradlePluginCompatibility.nowarn=true kotlin.mpp.androidSourceSetLayoutVersion=2 +kotlin.mpp.applyDefaultHierarchyTemplate=false +kotlin.mpp.enableCInteropCommonization=true org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.uikit.enabled=true diff --git a/gradle/moko.versions.toml b/gradle/moko.versions.toml index 5b9b28a3..5214152c 100644 --- a/gradle/moko.versions.toml +++ b/gradle/moko.versions.toml @@ -1,5 +1,5 @@ [versions] -resourcesVersion = "0.24.3" +resourcesVersion = "0.24.4" [libraries] resources = { module = "dev.icerock.moko:resources", version.ref = "resourcesVersion" } diff --git a/resources/build.gradle.kts b/resources/build.gradle.kts index ba13311f..d8464772 100644 --- a/resources/build.gradle.kts +++ b/resources/build.gradle.kts @@ -1,3 +1,7 @@ +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget + /* * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ @@ -25,6 +29,18 @@ kotlin { } } } + + // setup bundle searcher for apple + targets + .withType() + .matching { it.konanTarget.family.isAppleFamily } + .configureEach { + compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) { + val appleNative by cinterops.creating { + defFile(project.file("src/appleMain/def/appleNative.def")) + } + } + } } android { diff --git a/resources/src/appleMain/def/appleNative.def b/resources/src/appleMain/def/appleNative.def new file mode 100644 index 00000000..9278d90a --- /dev/null +++ b/resources/src/appleMain/def/appleNative.def @@ -0,0 +1,3 @@ +# https://kotlinlang.org/docs/native-definition-file.html +language = Objective-C +package = dev.icerock.moko.resources.apple.native diff --git a/resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt b/resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt index e8e6bda0..465916a1 100644 --- a/resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt +++ b/resources/src/appleMain/kotlin/dev/icerock/moko/resources/utils/NSBundleExt.kt @@ -4,15 +4,32 @@ package dev.icerock.moko.resources.utils +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.ObjCObjectBaseMeta import platform.Foundation.NSBundle import platform.Foundation.NSDirectoryEnumerator import platform.Foundation.NSFileManager +import platform.Foundation.NSLog import platform.Foundation.NSURL import platform.Foundation.pathExtension +import kotlin.experimental.ExperimentalObjCName + +@OptIn(ExperimentalObjCName::class, BetaInteropApi::class) +@ObjCName("ObjCAnchorClass") +internal object ObjCAnchorClass : ObjCObjectBaseMeta() fun NSBundle.Companion.loadableBundle(identifier: String): NSBundle { - val bundlePath: String = NSBundle.mainBundle.bundlePath - val enumerator: NSDirectoryEnumerator = requireNotNull(NSFileManager.defaultManager.enumeratorAtPath(bundlePath)) + // we should use search by our class because dynamic framework with resources can be placed in + // external directory, not inside app directory (NSBundle.main). for example in case of + // SwiftUI preview - app directory empty, but dynamic framework with resources will be in + // different directory (DerivedData) + @OptIn(BetaInteropApi::class) + val bundlePath: String = NSBundle.bundleForClass(ObjCAnchorClass).bundlePath + + val enumerator: NSDirectoryEnumerator = requireNotNull( + NSFileManager.defaultManager.enumeratorAtPath(bundlePath) + ) { "can't get enumerator" } + while (true) { val relativePath: String = enumerator.nextObject() as? String ?: break val url = NSURL(fileURLWithPath = relativePath) @@ -22,7 +39,8 @@ fun NSBundle.Companion.loadableBundle(identifier: String): NSBundle { val loadedIdentifier: String? = foundedBundle?.bundleIdentifier if (isBundleSearchLogEnabled) { - println("moko-resources auto-load bundle with identifier $loadedIdentifier at path $fullPath") + // NSLog to see this logs in Console app when debug SwiftUI previews or release apps + NSLog("moko-resources auto-load bundle with identifier $loadedIdentifier at path $fullPath") } if (foundedBundle?.bundleIdentifier == identifier) return foundedBundle diff --git a/resources/src/appleMain/objective-c/MRResourcesBundle.h b/resources/src/appleMain/objective-c/MRResourcesBundle.h new file mode 100644 index 00000000..8ac723b0 --- /dev/null +++ b/resources/src/appleMain/objective-c/MRResourcesBundle.h @@ -0,0 +1,7 @@ +#import +#import + +@interface ResourcesBundleAnchor : NSObject +@end + +NSBundle* getResourcesBundle(); diff --git a/resources/src/appleMain/objective-c/MRResourcesBundle.m b/resources/src/appleMain/objective-c/MRResourcesBundle.m new file mode 100644 index 00000000..9c416428 --- /dev/null +++ b/resources/src/appleMain/objective-c/MRResourcesBundle.m @@ -0,0 +1,14 @@ +// clang -target arm64-apple-ios -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -c MRResourcesBundle.m -o source.o +// ar rcs libMRResourcesBundle.a source.o +// lipo -info libMRResourcesBundle.a + +#import +#import +#import "MRResourcesBundle.h" + +@implementation ResourcesBundleAnchor +@end + +NSBundle* getResourcesBundle() { + return [NSBundle bundleForClass:[ResourcesBundleAnchor class]]; +}