# Table of Contents - [What is Skip? | Skip](#what-is-skip-skip) - [Debugging | Skip](#debugging-skip) - [Testing | Skip](#testing-skip) - [App Development | Skip](#app-development-skip) - [Common Topics | Skip](#common-topics-skip) - [Getting Started | Skip](#getting-started-skip) - [Skip Fuse vs Lite | Skip](#skip-fuse-vs-lite-skip) - [C Development Reference | Skip](#c-development-reference-skip) - [Cross-Platform Topics | Skip](#cross-platform-topics-skip) - [Bridging Reference | Skip](#bridging-reference-skip) - [Getting Help | Skip](#getting-help-skip) - [Dependencies | Skip](#dependencies-skip) - [SkipFuseUI | Skip](#skipfuseui-skip) - [Update Guide | Skip](#update-guide-skip) - [SkipFuse | Skip](#skipfuse-skip) - [SkipBridge | Skip](#skipbridge-skip) - [Project Structure | Skip](#project-structure-skip) - [Auth0 | Skip](#auth0-skip) - [SkipUnit | Skip](#skipunit-skip) - [Contributing | Skip](#contributing-skip) - [SkipModel | Skip](#skipmodel-skip) - [SkipLib | Skip](#skiplib-skip) - [RevenueCat | Skip](#revenuecat-skip) - [Porting Swift Packages to Android | Skip](#porting-swift-packages-to-android-skip) - [Lottie | Skip](#lottie-skip) - [Keychain | Skip](#keychain-skip) - [NFC | Skip](#nfc-skip) - [Deployment | Skip](#deployment-skip) - [JavaScriptCore | Skip](#javascriptcore-skip) - [Bluetooth | Skip](#bluetooth-skip) - [AuthenticationServices | Skip](#authenticationservices-skip) - [Zip | Skip](#zip-skip) - [Sentry | Skip](#sentry-skip) - [Showcase (Fuse) | Skip](#showcase-fuse-skip) - [PostHog | Skip](#posthog-skip) - [XML | Skip](#xml-skip) - [AVKit | Skip](#avkit-skip) - [Device Hardware | Skip](#device-hardware-skip) - [Camera & Media | Skip](#camera-media-skip) - [Socket.IO | Skip](#socket-io-skip) - [Ahoy Skipper (Split Fuse) | Skip](#ahoy-skipper-split-fuse-skip) - [Travel Bookings (Fuse) | Skip](#travel-bookings-fuse-skip) - [Gradle Project Reference | Skip](#gradle-project-reference-skip) - [FFI | Skip](#ffi-skip) - [QR Codes | Skip](#qr-codes-skip) - [Showcase (Lite) | Skip](#showcase-lite-skip) - [Transpilation Reference | Skip](#transpilation-reference-skip) - [SkipFoundation | Skip](#skipfoundation-skip) - [Supabase | Skip](#supabase-skip) - [Marketplace | Skip](#marketplace-skip) - [Hello Skip (Lite) | Skip](#hello-skip-lite-skip) - [Skip CLI Reference | Skip](#skip-cli-reference-skip) - [Howdy Skip (Fuse) | Skip](#howdy-skip-fuse-skip) - [Travel Posters (Split) | Skip](#travel-posters-split-skip) - [Accessibility | Skip](#accessibility-skip) - [Travel Bookings (Lite) | Skip](#travel-bookings-lite-skip) - [Fireside (Fuse) | Skip](#fireside-fuse-skip) - [Firebase | Skip](#firebase-skip) - [Notes (Lite) | Skip](#notes-lite-skip) - [Border | Skip](#border-skip) - [Scrumskipper (Lite) | Skip](#scrumskipper-lite-skip) - [Color | Skip](#color-skip) - [Button | Skip](#button-skip) - [Alert | Skip](#alert-skip) - [Data Bake (Lite) | Skip](#data-bake-lite-skip) - [Fireside (Lite) | Skip](#fireside-lite-skip) - [DatePicker | Skip](#datepicker-skip) - [Divider | Skip](#divider-skip) - [ColorScheme | Skip](#colorscheme-skip) - [Background | Skip](#background-skip) - [DisclosureGroup | Skip](#disclosuregroup-skip) - [Weather (Lite) | Skip](#weather-lite-skip) - [Lottie (Lite) | Skip](#lottie-lite-skip) - [WebView | Skip](#webview-skip) - [Audio | Skip](#audio-skip) - [ConfirmationDialog | Skip](#confirmationdialog-skip) - [Label | Skip](#label-skip) - [Gradient | Skip](#gradient-skip) - [GeometryReader | Skip](#geometryreader-skip) - [Form | Skip](#form-skip) - [NavigationStack | Skip](#navigationstack-skip) - [Modifier | Skip](#modifier-skip) - [HapticFeedback | Skip](#hapticfeedback-skip) - [OnSubmit | Skip](#onsubmit-skip) - [ScenePhase | Skip](#scenephase-skip) - [SecureField | Skip](#securefield-skip) - [Frame | Skip](#frame-skip) - [ProgressView | Skip](#progressview-skip) - [Offset | Skip](#offset-skip) - [Sheet | Skip](#sheet-skip) - [Slider | Skip](#slider-skip) - [Spacer | Skip](#spacer-skip) - [Timer | Skip](#timer-skip) - [Toggle | Skip](#toggle-skip) - [Observable | Skip](#observable-skip) - [Pasteboard | Skip](#pasteboard-skip) - [ShareLink | Skip](#sharelink-skip) - [TabView | Skip](#tabview-skip) - [Text | Skip](#text-skip) - [TextEditor | Skip](#texteditor-skip) - [TextField | Skip](#textfield-skip) - [Storage | Skip](#storage-skip) - [Overlay | Skip](#overlay-skip) - [OffsetPosition | Skip](#offsetposition-skip) - [Keyboard | Skip](#keyboard-skip) - [Picker | Skip](#picker-skip) - [Link | Skip](#link-skip) - [VideoPlayer | Skip](#videoplayer-skip) - [Glossary | Skip](#glossary-skip) - [Shadow | Skip](#shadow-skip) - [Localization | Skip](#localization-skip) - [Menu | Skip](#menu-skip) - [Gesture | Skip](#gesture-skip) - [Stack | Skip](#stack-skip) - [Symbol | Skip](#symbol-skip) - [SafeArea | Skip](#safearea-skip) - [Searchable | Skip](#searchable-skip) - [ZIndex | Skip](#zindex-skip) - [Table | Skip](#table-skip) - [Animation | Skip](#animation-skip) - [Image | Skip](#image-skip) - [Grid | Skip](#grid-skip) - [State | Skip](#state-skip) - [ScrollView | Skip](#scrollview-skip) - [Icon | Skip](#icon-skip) - [Transition | Skip](#transition-skip) - [SQLite | Skip](#sqlite-skip) - [Toolbar | Skip](#toolbar-skip) - [Hiya Skip (Split Mixed) | Skip](#hiya-skip-split-mixed-skip) - [Shape | Skip](#shape-skip) - [List | Skip](#list-skip) - [FAQs | Skip](#faqs-skip) - [SkipUI | Skip](#skipui-skip) - [SkipUI Component Samples | Skip](#skipui-component-samples-skip) - [Skip Sample Apps | Skip](#skip-sample-apps-skip) - [Skip Module Index | Skip](#skip-module-index-skip) - [Skip Fuse vs Lite | Skip](#skip-fuse-vs-lite-skip) - [Native Swift Tech Preview | Skip](#native-swift-tech-preview-skip) - [Stripe | Skip](#stripe-skip) --- # What is Skip? | Skip [Skip to content](https://skip.dev/docs/#_top) What is Skip? ============= Skip is a technology for building fully native iOS and Android apps. The mobile app market is divided between the iPhone and Android. The iPhone is popular in the West and leads in app revenue, while Android has many more worldwide users overall.[1](https://skip.dev/docs/#user-content-fn-apprevenue) ### Monetization Power Average Revenue Per User (Annual) iOS (~$140) Android (~$69) ### Market Reach Total Active Users (Billions) ~1.4B iOS ~3.7B Android Global OS Distribution (2026) Neglecting either platform means leaving behind a large percentage of potential users. Skip is the [only dual-platform solution](https://skip.dev/compare/) to deliver fully native apps for the entire mobile market. With Skip, you write your shared code in Swift and SwiftUI. Your Swift is compiled natively for both iOS and Android, with no added runtime interpreter or garbage collector to slow it down. Your SwiftUI runs as-is on iOS, and it uses Google’s recommended [Jetpack Compose ↗](https://developer.android.com/compose) UI toolkit on Android. Your users get a platform-perfect look and feel, not some uncanny-valley replica. Use Skip for bits of shared logic and UI, for your entire app, or anything in between. Skip makes [integration](https://skip.dev/docs/platformcustomization/) between your Swift and Android API as well as your SwiftUI and Compose views a snap, without significant overhead or complex bridging. ![Screenshot](https://assets.skip.dev/images/skip-marketing-preview.jpg) * * * Skip Advantages --------------- [Section titled “Skip Advantages”](https://skip.dev/docs/#advantages) ### Skip versus writing two separate native apps [Section titled “Skip versus writing two separate native apps”](https://skip.dev/docs/#skip-versus-writing-two-separate-native-apps) On the surface, using a cross-platform solution for your mobile development needs is a no-brainer. After all, you can literally cut your development time and maintenance costs in half! Many experienced developers believe, however, that cross-platform development requires too many compromises to make it worthwhile. In the end, they say, you’ll get better results with less hassle by writing separate iOS and Android apps using the native languages, frameworks, and tooling for each platform. Imagine being on a dev team creating separate iOS and Android implementations of a mobile app. You write the iOS version in Swift and SwiftUI, and the Android version in Kotlin and Compose. You have complete freedom to craft two fully native mobile applications. Very quickly, however, you’ll find that your team is writing a lot of code twice: * The same model types. They’ll often have to communicate with the same network APIs, serialize to the same JSON format, and have the same persistence capabilities across platforms. * The same logic to enforce your business rules. * The same unit tests to ensure that your model and business logic are working. * The same onboarding screens, settings screens, forms, lists, and other “standard” UI. Parts of your app might need careful tailoring for each mobile OS, but much of the experience may be identical across iOS and Android. You tell yourselves that the massive duplication of effort and all the extra overhead to coordinate models, data formats, logic, and tests across platforms are worth it. After all, you don’t want to compromise the app by introducing non-native cross-platform code. That has traditionally come with its own very real costs, for example: * Training or hiring for a new programming language (JavaScript, Dart, etc.) and set of application frameworks. * Bloating the app with an added runtime and/or garbage collector. * Degrading the user experience with non-native UI. But what if you could share common code and tests without these costs? What if you could: * Develop with a programming language and set of frameworks you’re already using. * Avoid the overhead of adding another runtime or garbage collector to either platform. * Produce entirely native user interfaces - SwiftUI on iOS and Compose on Android - from common SwiftUI code. * Call native API from your shared code whenever you need to. That is Skip. With Skip, you write your shared code in Swift, using standard iOS frameworks like Foundation and SwiftUI. On iOS, your code is used as-is. On Android, Skip’s tools bridge your Swift and SwiftUI to Kotlin and Compose. The result is fully native code on both platforms. And because the resulting code is native, it means: 1. Your Swift code [integrates perfectly](https://skip.dev/docs/platformcustomization/) with the parts of your app you choose to develop separately on iOS and Android. 2. You are never limited by what Skip does or does not support. You can always implement and call out to platform-specific solutions, just as you would when developing separate iOS and Android apps. Skip can save you enormous amounts of time and effort, without requiring you to compromise your app. ### Skip versus other cross-platform mobile frameworks [Section titled “Skip versus other cross-platform mobile frameworks”](https://skip.dev/docs/#skip-versus-other-cross-platform-mobile-frameworks) Skip has many advantages over other cross platform solutions like Flutter and React Native: * **Genuinely Native.** Skip apps don’t just “look native”, they _are_ native: SwiftUI on iOS and Jetpack Compose on Android. You know the difference, and so do your users. Take full advantage of new platform features and get automatic integration with core functionality like accessibility, which is a requirement for compliance in many fields. * **Featherweight.** With _SkipZero_, your iOS app has no dependencies on any Skip frameworks. And the resulting Android app only depends on open source libraries. * **Memory Efficient.** With no additional managed runtime, Skip apps are as efficient as they can possibly be on both platforms. Skip does not introduce a garbage collected runtime into your iOS app (unlike frameworks that use JavaScript or Dart), ensuring that your app’s memory watermark and battery usage are optimal. * **Idyllic IDE.** Skip’s IDE is Xcode, the premier development environment for Swift. For serious Android customization, incorporate Kotlin written directly in Android Studio. * **Platform Perfect.** Skip supports [thousands of cross-platform modules ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) . It also bridges seamlessly with Kotlin and Compose, so integrating with Android libraries is painless. * **Foundationally open.** Swift-on-Android is only half the story. Skip’s app development frameworks are all free and open-source. * **Ejectable.** Even if you stop using Skip, you have a pristine, fully functional iOS app. Check out our [comparison matrix](https://skip.dev/compare/) to see how Skip compares to several popular cross-platform solutions across various technical axes. Footnotes --------- [Section titled “Footnotes”](https://skip.dev/docs/#footnote-label) 1. Spending Power: iOS users spend $140 annually on apps versus $69 for Android users. iOS also dominates subscription revenue, with 5× higher average revenue per user (ARPU). Source: [iOS vs Android: Subscription Metrics Compared — appenure.com ↗](https://www.appeneure.com/blog/ios-vs-android-subscription-metrics-compared/seobot-blog) [↩](https://skip.dev/docs/#user-content-fnref-apprevenue) --- # Debugging | Skip [Skip to content](https://skip.dev/docs/debugging/#_top) Debugging ========= Skip generates fully native apps for both iOS and Android. That means you can utilize the power of each platform’s native debugging tools. Using the Debugger ------------------ [Section titled “Using the Debugger”](https://skip.dev/docs/debugging/#using-the-debugger) For bugs affecting the shared or iOS-only Swift portions of your app, use Xcode’s debugging tools just as you would for any other iOS app. Any fixes you make to shared logic written in Swift will of course fix the same logic bug in your Android build. And you can always write [unit tests](https://skip.dev/docs/testing/) to help. To debug an Android-specific problem, you can run your Android app [right in Android Studio](https://skip.dev/docs/platformcustomization/#android-studio) and utilize its own debugging tools. Compiled Swift -------------- [Section titled “Compiled Swift”](https://skip.dev/docs/debugging/#compiled-swift) A crash in native Swift code on Android will surface similarly to a crash caused by an uncaught Kotlin or Java exception, but instead of a detailed stack trace with line numbers, you will only receive the mangled name of the Swift function in which the crash took place. Mangled names are close enough to actual Swift class and function names, however, for you to find the offending function. Following is an abridged sample of the Logcat contents when a crash occurs from a call to `fatalError("CRASHME")` in native code: 12-02 14:33:26.163 12075 12075 F SwiftRuntime: HelloSwiftModel/ViewModel.swift:92: Fatal error: CRASHME12-02 14:33:26.171 12075 12075 F libc : Fatal signal 5 (SIGTRAP), code 1 (TRAP_BRKPT), fault addr 0x763f841d04 in tid 12075 (.xyz.HelloSwift), pid 12075 (.xyz.HelloSwift)12-02 14:33:26.198 12146 12146 I crash_dump64: obtaining output fd from tombstoned, type: kDebuggerdTombstoneProto12-02 14:33:26.200 207 207 I tombstoned: received crash request for pid 1207512-02 14:33:26.201 12146 12146 I crash_dump64: performing dump of process 12075 (target tid = 12075)12-02 14:33:26.636 163 163 I logd : logdr: UID=10296 GID=10296 PID=12146 n tail=500 logMask=8 pid=12075 start=0ns deadline=0ns12-02 14:33:26.648 163 163 I logd : logdr: UID=10296 GID=10296 PID=12146 n tail=500 logMask=1 pid=12075 start=0ns deadline=0ns12-02 14:33:26.663 12146 12146 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***12-02 14:33:26.663 12146 12146 F DEBUG : Build fingerprint: 'google/sdk_gphone64_arm64/emu64a:14/UE1A.230829.050/12077443:userdebug/dev-keys'12-02 14:33:26.663 12146 12146 F DEBUG : Revision: '0'12-02 14:33:26.663 12146 12146 F DEBUG : ABI: 'arm64'12-02 14:33:26.663 12146 12146 F DEBUG : Timestamp: 2024-12-02 14:33:26.204647577-050012-02 14:33:26.663 12146 12146 F DEBUG : Process uptime: 44s12-02 14:33:26.663 12146 12146 F DEBUG : Cmdline: com.xyz.HelloSwift12-02 14:33:26.663 12146 12146 F DEBUG : pid: 12075, tid: 12075, name: .xyz.HelloSwift >>> com.xyz.HelloSwift <<<12-02 14:33:26.663 12146 12146 F DEBUG : uid: 1029612-02 14:33:26.663 12146 12146 F DEBUG : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)12-02 14:33:26.663 12146 12146 F DEBUG : pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)12-02 14:33:26.663 12146 12146 F DEBUG : signal 5 (SIGTRAP), code 1 (TRAP_BRKPT), fault addr 0x000000763f841d0412-02 14:33:26.663 12146 12146 F DEBUG : Abort message: 'HelloSwiftModel/ViewModel.swift:92: Fatal error: CRASHME'12-02 14:33:26.663 12146 12146 F DEBUG : x0 0087000000000000 x1 00000079939ab7c4 x2 00000079939ab7c4 x3 0000007fcd3d0cd812-02 14:33:26.663 12146 12146 F DEBUG : 392 total frames12-02 14:33:26.663 12146 12146 F DEBUG : backtrace:12-02 14:33:26.663 12146 12146 F DEBUG : #00 pc 000000000035dd04 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk!libswiftCore.so (offset 0x999c000) ($ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_SSAHSus6UInt32VtF+560) (BuildId: 2e6f82a13c9518ce846bebc261661833fa34b7de)12-02 14:33:26.663 12146 12146 F DEBUG : #01 pc 00000000000381b0 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C9saveItems33_AA1DA8893D92B109DC6527A80C9D3046LLyyF+1244)12-02 14:33:26.663 12146 12146 F DEBUG : #02 pc 000000000003c63c /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C6_items33_AA1DA8893D92B109DC6527A80C9D3046LLSayAA4ItemVGvW+24)12-02 14:33:26.663 12146 12146 F DEBUG : #03 pc 000000000003c704 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C6_items33_AA1DA8893D92B109DC6527A80C9D3046LLSayAA4ItemVGvs+96)12-02 14:33:26.663 12146 12146 F DEBUG : #04 pc 0000000000038514 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C5itemsSayAA4ItemVGvsyyXEfU_+64)12-02 14:33:26.663 12146 12146 F DEBUG : #05 pc 000000000003aa8c /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C5itemsSayAA4ItemVGvsyyXEfU_TA+24)12-02 14:33:26.663 12146 12146 F DEBUG : #06 pc 000000000000cd04 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk!libswiftObservation.so (offset 0xa3bc000) ($s11Observation0A9RegistrarV12withMutation2of7keyPath_q0_x_s03KeyG0Cyxq_Gq0_yKXEtKAA10ObservableRzr1_lF+88) (BuildId: 0698d39971241cca206c1100e2aa89fe94109d35)12-02 14:33:26.663 12146 12146 F DEBUG : #07 pc 000000000010d844 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0x5cdc000) ($s10SkipBridge11ObservationV0C9RegistrarV12withMutation2of7keyPath_q0_x_s03KeyI0Cyxq_Gq0_yKXEtKAB10ObservableRzr1_lF+336)12-02 14:33:26.663 12146 12146 F DEBUG : #08 pc 000000000003d44c /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C12withMutation7keyPath_q_s03KeyH0CyACxG_q_yKXEtKr0_lF+252)12-02 14:33:26.663 12146 12146 F DEBUG : #09 pc 000000000003c890 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C5itemsSayAA4ItemVGvs+136)12-02 14:33:26.663 12146 12146 F DEBUG : #10 pc 000000000003d260 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04ViewC0C4save4itemyAA4ItemV_tF+456)12-02 14:33:26.663 12146 12146 F DEBUG : #11 pc 0000000000040e98 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) ($s15HelloSwiftModel04Viewc1_B7_save_3yySpySPySo18JNINativeInterfaceVGSgG_Svs5Int64VSvtF+324)12-02 14:33:26.663 12146 12146 F DEBUG : #12 pc 0000000000040d48 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (offset 0xabcc000) (Java_hello_swift_model_ViewModel_Swift_1save_13+8)12-02 14:33:26.663 12146 12146 F DEBUG : #13 pc 0000000000377030 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: b10f5696fea1b32039b162aef3850ed3)12-02 14:33:26.663 12146 12146 F DEBUG : #14 pc 00000000003605a4 /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: b10f5696fea1b32039b162aef3850ed3)12-02 14:33:26.663 12146 12146 F DEBUG : #15 pc 00000000004906b4 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+1248) (BuildId: b10f5696fea1b32039b162aef3850ed3)12-02 14:33:26.663 12146 12146 F DEBUG : #16 pc 000000000050a5d4 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp(art::interpreter::SwitchImplContext*)+2380) (BuildId: b10f5696fea1b32039b162aef3850ed3)12-02 14:33:26.663 12146 12146 F DEBUG : #17 pc 00000000003797d8 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: b10f5696fea1b32039b162aef3850ed3)12-02 14:33:26.663 12146 12146 F DEBUG : #18 pc 00000000000020b8 /data/app/~~sl7j-wAIm5OGrYysAOiRvQ==/com.xyz.HelloSwift-l1KlzOX9df6cdVUNJI0-kw==/base.apk (hello.swift.model.ViewModel.save+0) To extract a human-readable name from the manged swift function, you can use the `xcrun swift-demangle` command, like so: zap ~ % echo '$s15HelloSwiftModel04ViewC0C9saveItems33_AA1DA8893D92B109DC6527A80C9D3046LLyyF+1244' | xcrun swift-demangleHelloSwiftModel.ViewModel.(saveItems in _AA1DA8893D92B109DC6527A80C9D3046)() -> ()+1244 * * * Logging ------- [Section titled “Logging”](https://skip.dev/docs/debugging/#logging) Messages that you `print` do not appear in Android logging. Instead, Skip supports the standard `OSLog.Logger` API for dual-platform logging. Import `SkipFuse` to use `OSLog` APIs across iOS and Android: import SkipFuse...let logger = Logger(subsystem: "my.subsystem", category: "MyCategory")...logger.info("My message") When you log a message in your app, the `OSLog` messages from the Swift side of the app will appear in Xcode’s console as you’d expect. Skip’s Android implementation of `OSLog.Logger`, on the other hand, forwards log messages to [Logcat ↗](https://developer.android.com/tools/logcat) , which is Android’s native logging mechanism. Using the Logcat tab in Android Studio is a good way to browse and filter the app’s log messages on the Android side. You can also view Logcat output in the Terminal using the command `adb logcat`, which has a variety of filtering flags that can applied to the default (verbose) output. * * * Accessing Generated Source -------------------------- [Section titled “Accessing Generated Source”](https://skip.dev/docs/debugging/#accessing-transpiled-output) The location of the Kotlin and Swift source Skip generates during bridging and transpiling differs for app and framework targets. We discuss how to access your generated source in Xcode below. The Cross-Platform Topics documentation covers how to work with your Android code [in Android Studio](https://skip.dev/docs/platformcustomization/#android-studio) . ![Framework Transpilation Browse Screenshot](https://assets.skip.dev/framework-dev/framework-xcode-test-browse.png) ### Dual-Platform Apps [Section titled “Dual-Platform Apps”](https://skip.dev/docs/debugging/#dual-platform-apps) For dual-platform apps, Xcode places Skip’s generated source in a `plugins` directory deep within Xcode’s `DerivedData` folder. Skip surfaces this directory as the `SkipStone/plugins` group in your Xcode project. ![Transpiled source location](https://assets.skip.dev/images/transpiled-source-location.png) ![Transpiled source location](https://assets.skip.dev/images/plugins-folder.png) ### Frameworks [Section titled “Frameworks”](https://skip.dev/docs/debugging/#skiplink) Skip includes the `Create SkipLink` command plugin to link to the plugin output of your framework targets in Xcode. To invoke the command, control-click your Skip-ified package or highlight the package and use the `File → Packages` menu. ![Framework SkipLink Screenshot](https://assets.skip.dev/framework-dev/framework-xcode-skiplink.png) After running the `Create SkipLink` command, the new `SkipLink` Xcode group will allow you to access your framework’s Android project and generated source. ### Jumping to Generated Source [Section titled “Jumping to Generated Source”](https://skip.dev/docs/debugging/#jumping-to-generated-source) After linking to the generated source files using the instructions above, you can jump to generated files using Xcode’s `Open Quickly (cmd-shift-O)` command, just as you can jump to your own Swift files. Generated files have the same names as their Swift counterparts, but use the `_Bridge.swift` suffix for bridging files, or the `.kt` file extension for Kotlin files. Keep in mind that in order to work around limitations on Kotlin extension properties and functions, Skip typically moves code from your Swift extensions into the declaring Kotlin class during generation. If you defined an extension in a separate file from its declaring type, you may find its representation in the Kotlin file of the declaring type rather than the Kotlin file for the extension. You can also jump to a generated file from an Xcode build error message. When the Android Swift or Kotlin compiler outputs an error message, Skip’s Xcode plugin surfaces it as an Xcode. Clicking the build error for the generated source file will jump to the offending bridging or Kotlin code. --- # Testing | Skip [Skip to content](https://skip.dev/docs/testing/#_top) Testing ======= Unit Testing ------------ [Section titled “Unit Testing”](https://skip.dev/docs/testing/#unit-testing) ![Testing Diagram](https://assets.skip.dev/diagrams/skip-diagrams-testing.svg) Skip always transpiles your XCTest unit tests into JUnit tests for Android. This means that you can only perform Android tests on compiled Swift that has been [bridged](https://skip.dev/docs/modes/#bridging) to Kotlin/Java. Unit tests involving unbridged types should be excluded from Android testing. final class MyNativeSwiftTests: XCTestCase { ... #if !os(Android) func testSomeUnbridgedSwift() { ... } #endif ...} ### Robolectric Testing [Section titled “Robolectric Testing”](https://skip.dev/docs/testing/#robolectric-testing) The [SkipUnit module](https://skip.dev/docs/modules/skip-unit/) documentation describes the ability to unit test your code in an Android environment running on your Mac, which can be faster than using the Android emulator. If you choose to test on your Mac, Skip uses a simulated Android environment called [Robolectric ↗](https://robolectric.org/) . ### Android Emulator/Device Testing [Section titled “Android Emulator/Device Testing”](https://skip.dev/docs/testing/#android-emulatordevice-testing) To run your test cases on an Android device or emulator, you can specify the `ANDROID_SERIAL` environment variable in the Xcode scheme’s Run action arguments for the target. The default emulator is usually “emulator-5554”, and to see the list of identifiers for any connected devices, run the command `adb devices`. ![Configuring running tests on emulator in Xcode](https://assets.skip.dev/screens/xcode-testing-scheme-emulator.png){: style=“width: 80%; text-align: center; margin: auto;”} Running tests against an emulator or device is slower than running against Robolectric, but the results will be much more representative of how the code will behave in the real world. This is true for both purely transpiled projects, as well as a natively-compiled SkipFuse project. For an example of a repository that utilizes SkipFuse bridge testing, along with GitHub CI actions that automatically perform the testing against an Android emulator, see [https://github.com/skiptools/skip-fuse-samples ↗](https://github.com/skiptools/skip-fuse-samples) . Non-Skip Packages ----------------- [Section titled “Non-Skip Packages”](https://skip.dev/docs/testing/#non-skip-packages) Testing of native Swift packages that compile for both iOS and Android and do not have a `skip.yml` - such as the thousands of third-party packages tracked by [swiftpackageindex.com ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) ’s Android compatibility testing - is discussed in the [Porting Guide](https://skip.dev/docs/porting/#testing) . Performance Testing ------------------- [Section titled “Performance Testing”](https://skip.dev/docs/testing/#performance-testing) There is often a significant difference between Debug and Release build performance on Android devices. Always [run on a device](https://skip.dev/docs/app-development/#running-on-device) **using a Release build** when testing real-world performance. --- # App Development | Skip [Skip to content](https://skip.dev/docs/app-development/#_top) App Development =============== Skip allows you to share as much or as little code as you want between the iOS and Android versions of your app. The [Cross-Platform Topics](https://skip.dev/docs/platformcustomization/) chapter details how to integrate Android-specific or iOS-specific code. This chapter focuses on shared dual-platform development. The following sections do not attempt to teach you iOS development. There are other available resources for that. Rather, we focus on where dual-platform Skip development differs from standard iOS development, including how to use Skip’s tools and what to do when things go wrong. Philosophy ---------- [Section titled “Philosophy”](https://skip.dev/docs/app-development/#philosophy) We designed Skip from the ground up knowing that all multi-platform tools have limitations, and Skip is no exception. That is why we concentrated on making it trivial to [exclude unsupported iOS code](https://skip.dev/docs/platformcustomization/#compiler-directives) from your Android build. You can truly use _any_ iOS features, right inline, without compromise and without re-architecting your app. Of course, using an iOS feature without Android support means you’ll need to provide an Android fallback or alternative. You may be able to find a solution among the [thousands of cross-platform modules ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) Skip already supports, or to [port](https://skip.dev/docs/porting/) a Swift package that doesn’t yet compile for Android. Skip also makes it easy to [integrate Android-specific solutions](https://skip.dev/docs/platformcustomization/) , whether to work around limitations or to differentiate your Android experience. So remember: build errors show you what may not yet be supported out of the box on Android. They might require extra work to overcome, but they are not blockers! Building and Running -------------------- [Section titled “Building and Running”](https://skip.dev/docs/app-development/#building-and-running) In order to run and test your app on Android, you will need either an Android emulator or a paired Android device with developer mode enabled. You can set up an emulator by running `skip android emulator create` and then `skip android emulator launch`, as described in the [command line reference](https://skip.dev/docs/skip-cli/#android-emulator-create) . Alternatively, you can install and launch `Android Studio.app`, and then open the Device Manager from the ellipsis menu of the Welcome dialog to create an emulator of your choice. You can then use the Device Manager to launch the emulator, or you can run it from the terminal with a command like `~/Library/Android/sdk/emulator/emulator @Pixel_6_API_33`. ![Screenshot of the Android Studio Device Manager](https://assets.skip.dev/intro/device_manager.png) ### Running on an Android Device [Section titled “Running on an Android Device”](https://skip.dev/docs/app-development/#running-on-device) In order to install and run an app on a connected Android device, you must enable USB debugging on the device, as per the [ADB documentation ↗](https://developer.android.com/tools/adb#Enabling) . Then pair the Android device with your development machine. Make sure only one device _or_ emulator is running at a time. Otherwise Skip cannot know where to launch your app. Alternatively, set the `ANDROID_SERIAL` variable in your project’s `.xcconfig` file to the desired device or emulator’s identifier. Running the `/opt/homebrew/bin/adb devices` command will show the available paired identifiers. ### Dual-Platform Apps [Section titled “Dual-Platform Apps”](https://skip.dev/docs/app-development/#dual-platform-apps) Assuming you followed the [app creation](https://skip.dev/docs/gettingstarted/) instructions using `skip create`, each successful build of your Skip app will automatically attempt to launch it on the running Android emulator or device. Exactly one emulator or device must be running in order for the Skip project’s `Launch APK` script phase to install and run the app successfully. If you are having trouble with Skip’s Xcode plugin, check the [Troubleshooting](https://skip.dev/docs/help/#troubleshooting) section for help. #### Building and Running iOS-Only [Section titled “Building and Running iOS-Only”](https://skip.dev/docs/app-development/#ios-only) By default, whenever you run your iOS app from Xcode, Skip will also create and run the Android app. Building and running the app side-by-side is very useful for ensuring that both sides of the app look and behave the same while iterating on the app. However, you may sometimes want to run only the iOS side of the app for certain time periods, such as when debugging an iOS-specific issue. To do this, you can edit the `AppName.xcconfig` file, and change the `SKIP_ACTION = launch` to `SKIP_ACTION = build`. This will cause the Android side of the app to be built, but not run. If you want to skip over the entire Android build process, you can instead set `SKIP_ACTION = none` property in the `AppName.xcconfig` file. This can increase development velocity considerably. ### Separate iOS and Android Apps [Section titled “Separate iOS and Android Apps”](https://skip.dev/docs/app-development/#separate-ios-and-android-apps) If you’ve chosen to create separate iOS and Android apps that share dual-platform Swift frameworks, then you will build and run each app in its respective IDE. The [Project Types](https://skip.dev/docs/project-types/#separate-apps) guide contains tips for integrating dual-platform frameworks into your development workflow. ### Frameworks [Section titled “Frameworks”](https://skip.dev/docs/app-development/#frameworks) Building a dual-platform framework in Xcode builds your iOS code and runs the SkipStone build plugin. It does **not**, however, perform an Android build. Due to limitations on Xcode plugins, the only way to invoke the Android compiler is to run the module’s unit test suite against the macOS destination, or to export the framework’s build artifacts. For more information, see the [testing](https://skip.dev/docs/testing/) and [deployment](https://skip.dev/docs/deployment/) documentation. ![Framework Test Development Screenshot](https://assets.skip.dev/framework-dev/framework-xcode-test-failure.png) * * * Coding ------ [Section titled “Coding”](https://skip.dev/docs/app-development/#coding) Writing dual-platform code with Skip resembles coding a standard iOS app, and it can feel magical to see your Swift and SwiftUI run on Android. But in the end Skip is a tool that you work with, and writing for two platforms does introduce complications not found in pure iOS development: 1. At some point, you will likely find yourself wanting to use an iOS API, framework, or feature that is not yet supported on Android. Don’t give up! [This section](https://skip.dev/docs/app-development/#unsupported-ios-features) discusses your options when you encounter a limitation in dual-platform coverage. 2. Our [porting](https://skip.dev/docs/porting/) guide covers some of the common issues you’ll run into when compiling cross-platform Swift. Additionally, compiled Swift must use [bridging](https://skip.dev/docs/modes/#bridging) to interact with Android’s Kotlin and Java APIs. 3. Writing a dual-platform apps means using dual-platform libraries. Our documentation on [dependencies](https://skip.dev/docs/dependencies/) discusses how to use other dual-platform libraries as well as iOS and Android-specific libraries. ### Build Errors [Section titled “Build Errors”](https://skip.dev/docs/app-development/#build-errors) Skip tries to warn you as quickly as possible when you’re going down the wrong path. For example, the Skip build plugin may report warnings and errors even before Skip attempts to compile your project for Android. Skip also attempts to map all errors back to the offending Swift source code and surface them in Xcode, whether they come from bridging, transpilation, Kotlin compilation, or native compilation. Each error message therefore typically appears in two places: once inline in your Swift source code, and once in Xcode’s sidebar issue navigator. Clicking an entry will jump you to the offending code. ![Framework Development Screenshot](https://assets.skip.dev/development/kotlin-compiler-error.png) The most common build errors are: * Using an API without Android support. We discuss your options when an API you want to use has not yet been ported to Android [below](https://skip.dev/docs/app-development/#unsupported-ios-features) . * Needing to modify your `imports` for cross-platform Swift. Consult the [porting](https://skip.dev/docs/porting/) guide for details. ### Runtime Errors and Debugging [Section titled “Runtime Errors and Debugging”](https://skip.dev/docs/app-development/#runtime-errors-and-debugging) Dealing with errors is an integral part of development. Be sure to read the [Debugging](https://skip.dev/docs/debugging/) chapter to learn how to access generated code, view your log statements, and debug the Android side of your Skip framework or application. * * * UI and View Model Coding ------------------------ [Section titled “UI and View Model Coding”](https://skip.dev/docs/app-development/#ui) Google recommends [Jetpack Compose ↗](https://developer.android.com/develop/ui/compose) for Android user interface development. Skip can translate a [large subset](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) of SwiftUI into Compose, allowing you to build cross-platform iOS and Android UI in SwiftUI. Or you can write a separate Android UI in pure Compose using your Android IDE of choice. Skip even allows you to move fluidly between SwiftUI and Compose, as described in our [Cross-Platform Topics](https://skip.dev/docs/platformcustomization/#swiftui-and-compose) documentation. In the end, the choice between using SwiftUI, Jetpack Compose, or a combination of the two is up to you. ### `@Observables` [Section titled “@Observables”](https://skip.dev/docs/app-development/#observables) Regardless of whether you use Skip’s translated SwiftUI or write to the Compose API in Kotlin, Skip ensures that your `@Observable` model types participate in Compose state tracking. This allows them to seamlessly power your Android user interface just as they power your iOS one. `@Observable` integration is transparent, but some caveats apply when you use a Swift model to power a bespoke Compose UI: * You must `import SkipFuse` in your `@Observable's` Swift file in order to fuse it to the Android runtime. The SkipStone build plugin will warn you if this import is missing. * In order to use an `@Observable` from a Kotlin UI, ensure that your `@Observable` is [bridged](https://skip.dev/docs/modes/#bridging) . * Finally, if you are writing a bespoke Compose UI, you must add a SwiftPM dependency on `SkipModel` for your `@Observables` to work properly on Android, as in the following example. This is not necessary when you use a SwiftUI interface, because `SkipModel` will be included in your dependency on `SkipFuseUI`. ...let package = Package( name: "travel-posters-model", ... dependencies: [ .package(url: "https://source.skip.tools/skip.git", from: "1.2.0"), .package(url: "https://source.skip.tools/skip-model.git", from: "1.0.0"), // <-- Insert .package(url: "https://source.skip.tools/skip-fuse.git", from: "1.0.0") ], targets: [ .target(name: "TravelPostersModel", dependencies: [ .product(name: "SkipFuse", package: "skip-fuse"), .product(name: "SkipModel", package: "skip-model") // <-- Insert ], plugins: [.plugin(name: "skipstone", package: "skip")]), ... ]) The [`CityManager` ↗](https://github.com/skiptools/skipapp-travelposters-native/blob/main/travel-posters-model/Sources/TravelPostersModel/CityManager.swift) type in the [TravelPosters ↗](https://github.com/skiptools/skipapp-travelposters-native) sample is an example of an `@Observable` that is shared between separate iOS and Android apps. ### SwiftUI [Section titled “SwiftUI”](https://skip.dev/docs/app-development/#swiftui) Writing cross-platform SwiftUI and watching it appear on both the iOS simulator and Android emulator can feel magical. It is a wonderful way to share all or parts of your user interface. And because Skip translates your SwiftUI calls to Jetpack Compose on Android, the result is a fully native user interface on both platforms, not an uncanny-valley replica. On Android, `import SwiftUI` vends an implementation of the SwiftUI API called `SkipFuseUI` that is bridged to Jetpack Compose. Make sure you also **use default or public visibility for Views and their SwiftUI properties**. Skip cannot access private SwiftUI components on Android. Here is an example of a valid cross-platform SwiftUI view: import SwiftUI struct MyView : View { @State var counter = 1 // Use internal or public access for all SwiftUI types and members private let title = "..." // OK to use private for non-SwiftUI members ... var body: some View { ... }} Under the hood, SkipFuseUI uses the SkipUI user interface library. Its documentation includes a list of [supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . We highly recommend that you browse this list and the associated documentation on various [SkipUI topics](https://skip.dev/docs/modules/skip-ui/#topics) . It will help you avoid many common errors when writing cross-platform SwiftUI. * * * iOS Features without Android Support ------------------------------------ [Section titled “iOS Features without Android Support”](https://skip.dev/docs/app-development/#unsupported-ios-features) Skip is continually adding functionality, but there are still many iOS APIs, frameworks, and features that are not yet available for Android. ### API [Section titled “API”](https://skip.dev/docs/app-development/#api) Using an iOS API that is not yet supported on Android will result in either an unavailable API error or build error from the Android Swift compiler. If you encounter an error, check the [porting guide](https://skip.dev/docs/porting/#skipfuse) to see if the API is actually available, but requires different imports for Android. If you are writing SwiftUI code, consult the [SkipUI module](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) to learn what is supported. When you encounter missing API on Android, you have options! You may be able to use alternate, supported APIs to accomplish the task. The [Swift Package Index ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) site lists many cross-platform Swift packages that are known to build for Android. If you can’t find an existing solution, you can use Skip’s [iOS and Android integration](https://skip.dev/docs/platformcustomization/) techniques to implement separate iOS and Android code paths, taking advantage of each platforms’ respective native solutions. And if the API you want to use is in a framework already mirrored for Android - either as a [Skip open source library ↗](https://source.skip.tools/) or a [community library](https://skip.dev/docs/contributing/#community-libraries) - you may be able to easily add the missing API to the existing library. If you augment an existing library, please consider [contributing](https://skip.dev/docs/contributing/) your improvements back to the Skip community. Follow the instructions [here](https://skip.dev/docs/contributing/#local-libraries) to configure Xcode for local Skip library development. ### Frameworks [Section titled “Frameworks”](https://skip.dev/docs/app-development/#frameworks-1) Skip Fuse supports [thousands of third-party modules ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) . If these do not meet your needs and there are no [Skip](https://skip.dev/docs/modules/) or [community](https://skip.dev/docs/contributing/#community-libraries) libraries available for the desired functionality, you might consider [creating your own](https://skip.dev/docs/dependencies/#implementation) dual-platform library or shared API. Again, please consider contributing your work as a [community library](https://skip.dev/docs/contributing/#community-libraries) . ### Features [Section titled “Features”](https://skip.dev/docs/app-development/#features) Some iOS app extensions and features are not yet implemented for Android, or have no direct Android counterpart. Use the techniques in [Cross-Platform Topics](https://skip.dev/docs/platformcustomization/) to implement iOS-only or Android-only solutions. For example, you might use a [compiler directive](https://skip.dev/docs/platformcustomization/#compiler-directives) to exclude your iOS widget from your Android build, and include a [Kotlin file](https://skip.dev/docs/platformcustomization/#kotlin-files) implementing a native widget for Android instead. * * * Common Topics ------------- [Section titled “Common Topics”](https://skip.dev/docs/app-development/#common-topics) For instructions on how to handle common development tasks like localization, resource and image loading, and JSON coding across platforms with Skip, see [Common Topics](https://skip.dev/docs/development-topics/) . --- # Common Topics | Skip [Skip to content](https://skip.dev/docs/development-topics/#_top) Common Topics ============= This chapter covers how to perform a variety of common development tasks across platforms with Skip. Configuration with `Skip.env` ----------------------------- [Section titled “Configuration with Skip.env”](https://skip.dev/docs/development-topics/#configuration-with-skipenv) Skip app customization should be primarily done by directly editing the included `Skip.env` file, rather than changing the app’s settings in Xcode. Only properties that are set in the `Skip.env` file, such as `PRODUCT_NAME`, `PRODUCT_BUNDLE_IDENTIFIER` and `MARKETING_VERSION`, will carry through to both `HelloSkip.xcconfig` and `AndroidManifest.xml`. This is how a subset of the app’s metadata can be kept in sync between the iOS and Android versions, but it requires that you forgo using the Xcode Build Settings interface (which does not modify the `.xcconfig` file, but instead overrides the settings in the local `.xcodeproj/` folder). * * * Localization ------------ [Section titled “Localization”](https://skip.dev/docs/development-topics/#localization) Localizing your app into multiple languages gives it the maximum possible reach. Localization is a critical part of making your app accessible to users all around the world. Skip helps unlock the promise of true universality for your app by bringing SwiftUI to Android, but the other half of the equation is ensuring that your users can understand the content of your app. Skip embraces the `xcstrings` catalog format, new in Xcode 15, to provide a simple and easy solution to adding support for multiple languages to your app. ### Localization example [Section titled “Localization example”](https://skip.dev/docs/development-topics/#example) Consider the following SwiftUI snippet from the default “Hello” app of a screen in a tab bar with a “Hello Skipper!” message. VStack { Text("Hello \(name)!") Image(systemName: "heart.fill") .foregroundStyle(.red)}.font(.largeTitle).tabItem { Label("Welcome", systemImage: "heart.fill") } For users with their device language set to French, we want the tab item to be “Bienvenue” and the message to be displayed as “Bonjour Skipper!”. This can be accomplished by editing the `Localizable.xcstrings` file in Xcode and filling in the translations for each supported language. String interpolation is handled by substituting the variable (e.g., `"\(name)"`) with the token `"%@"`, which will cause the translated string to insert any variables that need to be substituted at runtime. To localize strings used outside of SwiftUI, use iOS’s standard `NSLocalizedString` function, which also requires you to insert tokens for any variables: let localizedTitle = NSLocalizedString("License key for %@")sendLicenseKey(key, to: user, title: String(format: localizedTitle, user.fullName)) Xcode 15 has native support for editing string catalogs with a convenient user interface: ![Xcode screenshot of editing an .xcstrings file](https://assets.skip.dev/screens/xcode-xcstrings-edit.png) The result is that by updating this single file, you can localize your app into many languages, enabling native speakers of those languages to use your app with ease. For example, the default “Hello” app has localizations for English, French, Spanish, Japanese, and Chinese, shown here for both the iOS and Android versions of the app: ![Localization screenshot](https://assets.skip.dev/hello/en/ios/welcome_light_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/fr/ios/welcome_dark_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/es/ios/welcome_light_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/ja/ios/welcome_dark_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/zh/ios/welcome_light_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/en/android/welcome_dark_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/fr/android/welcome_light_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/es/android/welcome_dark_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/ja/android/welcome_light_framed.png) ![Localization screenshot](https://assets.skip.dev/hello/zh/android/welcome_dark_framed.png) ### The `xcstrings` Format [Section titled “The xcstrings Format”](https://skip.dev/docs/development-topics/#xcstrings) The Skip plugin handles the `.xcstrings` localization format, which is used by Xcode 15 as a single source of truth for the app’s localization. The default project created by `skip init --appid=… hello-skip HelloSkip` will create a `Sources/HelloSkip/Resources/Localizable.xcstrings` file which can be used as a starting point for adding new languages and string translations to your project. The `Localizable.xcstrings` file is a simple JSON file which can be either edited through Xcode or handed off to specialist translators and then re-integrated back into your application. The structure is simple, and can be edited either by hand or using machine translation tools. An excerpt of the format is as follows: { "sourceLanguage" : "en", "strings" : { "Hello %@!" : { "localizations" : { "es" : { "stringUnit" : { "state" : "translated", "value" : "¡Hola %@!" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Bonjour %@!" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "こんにちは、%@!" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "你好,%@!" } } } }, "Welcome" : { "localizations" : { "es" : { "stringUnit" : { "state" : "translated", "value" : "Bienvenido" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Bienvenue" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "ようこそ" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "欢迎" } } } } }, "version" : "1.0"} The following table lists the supported tokens that will be substituted at runtime: | Token | Meaning | | --- | --- | | $@ | String or stringified instance | | %ld or %lld | integer number | | %lf or %llf | floating point number | | %.3f | formatted floating point number | | %1$@ | maually-specified positional argument | | %% | literal escaped percent sign | More information on the Xcode editor for the `xcstrings` format can be found at [https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog ↗](https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog) . ### Localizing modules [Section titled “Localizing modules”](https://skip.dev/docs/development-topics/#modules) SwiftUI components that accept a `String`, such as `Text("Hello \(name)!")` or `Button("Click Me") { doSomething() }`, as well as the standard `NSLocalizedString` function assume that the localized string is defined in the `main` bundle. On the Android side, `Bundle.main` uses the resources defined in the primary app module, which is the module in the app that contains the entry point and top-level resources. However, when you modularize your app by breaking it up into separate SwiftPM modules, references to these string keys will still assume that they are defined in the `main` bundle, rather than in the `module` bundle. This assumption is built in to both SwiftUI as well as the transpiled SkipUI. This means that if you create a separate library module of UI components to be shared between apps, you need to manually specify the bundle that should be referenced for the translated keys. Each SwiftUI component that accepts a localization string will also have a constructor that accepts a `Bundle` parameter. The `NSLocalizedString` function takes an optional `Bundle` as well. You generally want to use the `Bundle.module` value as the argument, which will cause it to reference the component’s module. For example: VStack { Text("Hello \(name)!", bundle: .module) Button { doSomething() } label: { Text("Click Me", bundle: .module) }} ... let localizedTitle = NSLocalizedString("License key for %@", bundle: .module)sendLicenseKey(key, to: user, title: String(format: localizedTitle, user.fullName)) The additional bundle parameter makes your code more verbose, but has the advantage that the component can be used irrespective of whether it is defined in the top-level app package on in a separate module. ### Localizing raw strings [Section titled “Localizing raw strings”](https://skip.dev/docs/development-topics/#NSLocalizedString) In addition to localizing SwiftUI components, the string localization dictionary can be accessed using the Foundation function `NSLocalizedString`, which enables the localization of strings that are constructed outside the context of user-interface elements. For example, to create a local variable with a localized string: let helloWorld = NSLocalizedString("Hello World", bundle: .module, comment: "greeting string") The `comment` element is required and is used to provide context to translators. Parameterizing strings localized in this manner is a bit more complex than SwiftUI elements, since automatic string interpolation being converted into the `%@` tokens does not take place: let personName = "Skipper"let greeting = String(format: NSLocalizedString("Hello, %@!", bundle: .module, comment: "parameterized greeting"), personName) ### Limitations [Section titled “Limitations”](https://skip.dev/docs/development-topics/#limitations) * * * Notifications ------------- [Section titled “Notifications”](https://skip.dev/docs/development-topics/#notifications) Skip supports the [core API](https://skip.dev/docs/modules/skip-ui/#supported-usernotifications) of Apple’s `UserNotifications` framework so that your iOS notification-handling code works across platforms. The setup for integrating notification functionality into your app, however, will vary depending on the push service you are using. Skip’s [Firebase support](https://skip.dev/docs/modules/skip-firebase/) includes push messaging out of the box. Follow [its instructions](https://skip.dev/docs/modules/skip-firebase/#messaging) to support push notifications on top of Firebase Cloud Messaging in your dual-platform app. You often want to take a user to a particular part of your app when they tap on a notification. To do so, we recommend sending a [deep link](https://skip.dev/docs/development-topics/#deep-links) URL in the notification metadata. The next section discusses how to support deep links in your cross-platform app, and our [FireSide sample app ↗](https://github.com/skiptools/skipapp-fireside-fuse) demonstrates push notifications, deep linking, and using them together. Here is an excerpt showing how you might send a user to a location in your app from your `UNUserNotificationCenterDelegate`: public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { // Look for a custom 'deep_link' key in the notification payload, which should be a URL with our app's scheme let content = response.notification.request.content if let deepLink = content.userInfo["deep_link"] as? String, let url = URL(string: deepLink) { Task { @MainActor in await UIApplication.shared.open(url) } }} * * * Deep Links ---------- [Section titled “Deep Links”](https://skip.dev/docs/development-topics/#deep-links) Deep links allow you to bring a user to a particular part of your app. Skip supports custom URL schemes and SwiftUI deep link handling. ### Darwin Setup [Section titled “Darwin Setup”](https://skip.dev/docs/development-topics/#darwin-setup) To support deep links in your iOS build, first follow Apple’s instructions to register your [custom URL scheme ↗](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) in Xcode. ![Xcode screenshot of adding a custom URL scheme](https://assets.skip.dev/screens/xcode-customurlscheme.png) ### Android Setup [Section titled “Android Setup”](https://skip.dev/docs/development-topics/#android-setup) Edit your Android build’s `AndroidManifest.xml` to add an `intent-filter` for your custom URL scheme. For example to support `myurlscheme`, add the following to your `AndroidManifest.xml`: ... ... ... ### SwiftUI [Section titled “SwiftUI”](https://skip.dev/docs/development-topics/#swiftui) SwiftUI uses the [`onOpenURL` ↗](https://developer.apple.com/documentation/swiftui/view/onopenurl(perform:)) view modifier to intercept and process deep links. Place the modifier on a view that will be rendered when the app opens, and use its action to process the given URL. This will typically involve updating your navigation bindings to take the user to a specified location in the app, as in the following sample: enum Tab : String { case cities, favorites, settings} public struct ContentView: View { @AppStorage("tab") var tab = Tab.cities @State var cityListPath = NavigationPath() public var body: some View { TabView(selection: $tab) { NavigationStack(path: $cityListPath) { CityListView() } ... .tag(Tab.cities) NavigationStack { FavoriteCityListView() } ... .tag(Tab.favorites) SettingsView() ... .tag(Tab.settings) } // travel://[/], e.g. travel://cities/London or travel://favorites .onOpenURL { url in if let tabName = url.host(), let tab = Tab(rawValue: tabName) { self.tab = tab // Select the encoded tab if tab == .cities, let city = city(forName: url.lastPathComponent)) { // iOS needs an async dispatch after switching tabs to read navigationDestinations DispatchQueue.main.async { // Set nav stack to root + specified city cityListPath.removeLast(cityListPath.count) cityListPath.append(city.id) } } } } }} ### Testing [Section titled “Testing”](https://skip.dev/docs/development-topics/#testing) On iOS, the easiest way to test your deep link handling is by entering a URL with your custom scheme into Safari. You can also write a URL into a Calendar event or a Note. iOS will linkify the text so that tapping it will open your app. Android includes an `adb` command for sending intents to the running emulator or device, including deep links. Building on our SwiftUI example above, enter a command like the following in Terminal: % adb shell am start -W -a android.intent.action.VIEW -d "travel://cities/London" * * * `singleTop` ----------- [Section titled “singleTop”](https://skip.dev/docs/development-topics/#singletop) When a user taps a notification or deep link for your app on Android, the system fires an `Intent` to open your app. By default, this will initialize a new instance of your app’s main `Activity`, even if your app is already running. That means that any transient UI state may be lost. If you’d like the more iOS-like behavior of keeping your UI as-is when your app is brought to the foreground via a notification or deep link, you can use the `singleTop` launch mode. Edit your `Android/app/src/main/AndroidManifest.xml` as follows: ... ... ... Read more about launch modes and other `Activity` options [here ↗](https://developer.android.com/guide/topics/manifest/activity-element) . * * * Resources --------- [Section titled “Resources”](https://skip.dev/docs/development-topics/#resources) Place shared resources in the `Sources/ModuleName/Resources/` folder. Skip will copy the files in this folder to your Android build and make them available to the standard `Bundle` loading APIs. For example, the following Swift loads the `Sources/ModuleName/Resources/sample.dat` file on both iOS and Android: let resourceURL = Bundle.module.url(forResource: "sample", withExtension: "dat")let resourceData = Data(contentsOf: resourceURL) Resources are embedded in the `assets/` folder of the Android APK, and are accessed through a custom URL handler for the “asset:” protocol that reads assets from the specified path. ### Colors [Section titled “Colors”](https://skip.dev/docs/development-topics/#colors) Skip supports named colors defined in asset catalogs as well as defining an app-wide `AccentColor` asset. See the [SkipUI module documentation](https://skip.dev/docs/modules/skip-ui/#colors) for instructions on how to use named colors across both iOS and Android. ### Fonts [Section titled “Fonts”](https://skip.dev/docs/development-topics/#fonts) Skip allows you to use your own custom fonts on iOS and Android. The [SkipUI module documentation](https://skip.dev/docs/modules/skip-ui/#custom-fonts) details how to install and use custom fonts. ### Images [Section titled “Images”](https://skip.dev/docs/development-topics/#images) Skip supports iOS asset catalogs containing PNG, JPG, and PDF image files, as well as exported [Google Material Icons ↗](https://fonts.google.com/icons) and [SF Symbols ↗](https://developer.apple.com/sf-symbols/) SVG files. Skip also supports network images and bundled image files. The [SkipUI module documentation](https://skip.dev/docs/modules/skip-ui/#images) details where to place shared assets catalogs, how to supply SF Symbols to your Android app, and how to load and display images in your SwiftUI. ### App Icons [Section titled “App Icons”](https://skip.dev/docs/development-topics/#app-icons) Skip can help manage your app icons by generating and updating icons from PNG or SVG files. For example: $ skip icon --open-preview --foreground white --random-background https://raw.githubusercontent.com/google/material-design-icons/refs/heads/master/symbols/web/crowdsource/materialsymbolsrounded/crowdsource_40px.svg For more information, see the [`skip icon` CLI reference](https://skip.dev/docs/skip-cli/#icon) . * * * Themes ------ [Section titled “Themes”](https://skip.dev/docs/development-topics/#themes) Skip fully supports iOS and Android system color schemes, as well as SwiftUI styling modifiers like `.background`, `.foregroundStyle`, `.tint`, and so on. You may, however, want to customize aspects of your Android UI’s colors and components that cannot be configured through SwiftUI’s standard modifiers. Skip provides additional Android-only API for this purpose. These SwiftUI add-ons allow you to reach “under the covers” and manipulate Skip’s underlying use of Compose. They are detailed in the SkipUI module documentation’s [Material](https://skip.dev/docs/modules/skip-ui/#material) topic. --- # Getting Started | Skip [Skip to content](https://skip.dev/docs/gettingstarted/#_top) Getting Started =============== 1. Install [Xcode ↗](https://developer.apple.com/xcode) 2. Install [Android Studio ↗](https://developer.android.com/studio) 3. Install [Homebrew ↗](https://brew.sh/) 4. Once Homebrew has been setup, install Skip by running the Terminal command: brew install skiptools/skip/skip This will download and install the `skip` tool itself, along with the Android SDK and `gradle` build tool that is necessary for building and running the Android side of your apps. 5. You can ensure that the basic development prerequisites are satisfied by running: skip checkup [✓] Skip version 1.7.0 (= 1.7.0)[✓] macOS version 15.7.2 (> 13.5.0)[✓] macOS architecture: ARM[✓] Swift version 6.2.3 (> 5.9.0)[✓] Swiftly version 1.0.1 (> 1.0.0)[✓] Xcode version 26.2 (> 15.0.0)[✓] Xcode tools SDKs: 5[✓] Homebrew version 5.0.9 (> 4.1.0)[✓] Gradle version 9.2.1 (> 8.6.0)[✓] Java version 25.0.1 (> 17.0.0)[✓] Android Debug Bridge version 1.0.41 (> 1.0.40)[✓] Resolve dependencies (10.43s)[✓] Build hello-skip (56.08s)[✓] Test Swift (15.43s)[✓] Test Kotlin (26.19s)[✓] Check project schemes (5.65s)[✓] Archive iOS ipa (13.13s)[✓] Assemble HelloSkip-debug.ipa 175 KB[✓] Verifying HelloSkip-debug.ipa: 175 KB[✓] Assembling Android apk (61.1s)[✓] Verify HelloSkip-debug.apk: 80 MB[✓] Check Skip Updates: 1.7.0[✓] Skip checkup succeeded in 190.21s If the installation or checkup fails, try running again with `skip checkup --verbose` to get more details, and check the [FAQ](https://skip.dev/docs/faq/) for common solutions or reach out to the community on [Slack](https://skip.dev/slack/) or the [Discussion Forums ↗](https://forums.skip.dev/) . 6. Create a new Skip app project with: skip create You will be guided through a series of questions about the app you want to create. The first two are especially important: 1. **App or Library?** The App option creates a single Swift and SwiftUI app that uses Skip’s Xcode plugin to automatically build for Android. The Library option creates a Swift library that you can use in a unified Skip app, or use as shared business logic in [separate iOS and Android apps](https://skip.dev/docs/project-types/#separate-apps) . Neither option prevents you from using a combination of Swift and Kotlin, SwiftUI and Compose in your final application. 2. **Skip Fuse or Skip Lite?** Information about the different modes can be found in the [Native and Transpiled Modes](https://skip.dev/docs/modes/) documentation. In this example, we create a Skip Fuse app, which uses the official [Swift SDK for Android ↗](https://www.swift.org/documentation/articles/swift-sdk-for-android-getting-started.html) . If you’re new to Skip, we recommend starting your exploration with an App project. An example session for creating a `hello-skip/HelloSkip` project (which generates a project identical to the minimal [Skip Fuse sample app ↗](https://github.com/skiptools/skipapp-howdy) or [Skip Lite sample app ↗](https://github.com/skiptools/skipapp-hello) ) might look like: $ skip create Welcome to Skip! Select type of project to create: 1: App: mobile application for Android and iOS 2: Library: library project with one or more modulesEnter selection (default: App) [1..2] 1 Select the mode of the project: 1: Skip Fuse: natively compiled project 2: Skip Lite: transpiled projectEnter selection [1..2] 1 Enter the project-name for the App: hello-skip Enter the CamelCase name of the App module: HelloSkip Optionally enter additional module names: Enter the app bundle identifier: com.example.HelloSkip Create a free open-source project? (y/n) [n]: nInitialize git repository for the project? (y/n) [n]: nInitialize a Fastlane configuration for the project? (y/n) [y]: yPre-build the project? (y/n) [y]: yInstall the Swift Android SDK? (y/n) [y]: yOpen the Xcode project after initialization? (y/n) [y]: y [✓] Install Swift Android SDK (48.4s)[✓] Resolve dependencies (12.12s)[✓] Build hello-skip (53.35s)[✓] Check project schemes (6.12s)[✓] Archive iOS ipa (17.18s)[✓] Assemble HelloSkip-debug.ipa 1 MB[✓] Verifying HelloSkip-debug.ipa: 1 MB[✓] Assembling Android apk (78.91s)[✓] Verify HelloSkip-debug.apk: 80 MB[✓] Opening Xcode project (0.10s)[✓] Skip create succeeded in 168.63s You can alternatively create a Skip project using the non-interactive `skip init` command, as described in the [command line reference](https://skip.dev/docs/skip-cli/#init) . 7. Once your app pre-builds and opens in Xcode, you are _almost_ ready to go. One more step is needed, which is to create and launch an Android emulator (which is the Android equivalent of the iPhone Simulator and is used for local app development). The `skip` tool has a command to quickly install and configure an Android emulator for your project: skip android emulator create [✓] Configure Android SDK Manager (1.55s) [✓] Install platform-tools (0.77s) [✓] Install emulator (0.75s) [✓] Install platforms;android-34 (0.76s) [✓] Install system-images;android-34;google_apis;arm64-v8a (0.75s) [✓] Create emulator: emulator-34-medium_phone (0.76s) [✓] Create Android emulator succeeded in 5.35s Once this command completes successfully, you will have an Android emulator installed, which you can launch and observe the logging output with: skip android emulator launch 8. Now you should be ready to launch your new app! In Xcode, ensure the “HelloSkip App” scheme is selected along with a simulator destination (e.g., “iPhone 17”), and then Run the scheme. You may be prompted to “Trust and Enable” the Skip plugin, and then the project will build and launch simultaneously on both the iPhone Simulator and Android Emulator. ![Screenshot of Skip running in both the iOS Simulator and Android Emulator](https://assets.skip.dev/intro/skip_xcode.png) Next Steps ---------- [Section titled “Next Steps”](https://skip.dev/docs/gettingstarted/#next-steps) Congratulations! You have created and launched your first dual-platform app. At this point, you should tinker with the project, making changes and re-launching the app to get a feel for how Skip compliments your normal edit-build-run cycle for app development. From here you should proceed to: * Join the [Skip Slack](https://skip.dev/slack/) to connect with the Skip developer community. * Peruse the [Skip Sample Apps](https://skip.dev/docs/samples/) to see common design patterns and best practices. * Check the [Swift Package Index ↗](https://swiftpackageindex.com/) to see if your favorite Swift packages are among the thousands available for Android, and browse the various [Skip Integration Frameworks](https://skip.dev/docs/modules/) to unlock additional capabilities for your dual-platform app. * Read about the advantages of Skip over other cross-platform frameworks like Flutter and React Native in our [Comparison Guide](https://skip.dev/compare/) . * Read the [App Development](https://skip.dev/docs/app-development/) documentation to learn about developing with Skip. * Explore the other [Project Types](https://skip.dev/docs/project-types/) that can be created with Skip, including multi-module apps, transpiled apps, and sharing business logic in separate iOS and Android apps. * Consider whether [Skip Fuse or Skip Lite](https://skip.dev/docs/modes/) is the best fit for your project needs. * Review the [Glossary of Terms](https://skip.dev/docs/glossary/) to familiarize yourself with some of the new and unfamiliar terminology you may encounter while using Skip for Android app development. * If you have an existing app you want to bring into Skip, read the [Migration Guide](https://skip.dev/docs/project-types/#existing_development) . Updating Skip ------------- [Section titled “Updating Skip”](https://skip.dev/docs/gettingstarted/#updating) To update the `skip` command line tool to the latest version: skip upgrade To update your Xcode project to use the latest version of the Skip plugin and libraries allowed by your `Package.swift` configuration, use the `File -> Packages -> Update to Latest Package Versions` Xcode menu option. Additional Resources -------------------- [Section titled “Additional Resources”](https://skip.dev/docs/gettingstarted/#resources) * Use `skip help` for a complete list of `skip` tool commands, and see the [command line reference](https://skip.dev/docs/skip-cli/) . * Check the general [help](https://skip.dev/docs/help/) page for troubleshooting and contact information. * Continue browsing this [documentation](https://skip.dev/docs/) to learn more about developing with Skip. --- # Skip Fuse vs Lite | Skip [Skip to content](https://skip.dev/docs/modes/#_top) Skip Fuse vs Lite ================= Skip supports both _native_ mode - in which your Swift is compiled natively for Android - and _transpiled_ mode - in which your Swift is converted to Kotlin. The mode is specified at the level of a Swift module. We refer to apps that primarily use native mode as **Skip Fuse** apps, and apps that primarily use transpiled mode as **Skip Lite** apps. It is not uncommon, however, to use both native and transpiled modules within a single Swift-on-Android app. Native Mode: Skip Fuse ---------------------- [Section titled “Native Mode: Skip Fuse”](https://skip.dev/docs/modes/#native) Skip’s _native_ mode compiles your Swift natively with the [Swift SDK for Android ↗](https://www.swift.org/documentation/articles/swift-sdk-for-android-getting-started.html) , and it is the mode we recommend for most apps. It is a combination of: * A native Swift toolchain for Android. * Integration of Swift functionality like logging and networking with the Android operating system. * Bridging technology for using Kotlin/Java API from compiled Swift, and for using compiled Swift API from Kotlin/Java. * Xcode integration and tooling to compile and deploy your Swift across both iOS and Android. The following diagram depicts a native project: ![Skip Native Diagram](https://assets.skip.dev/diagrams/skip-diagrams-native.svg) ### Advantages [Section titled “Advantages”](https://skip.dev/docs/modes/#advantages) Skip’s documentation discusses its [advantages](https://skip.dev/docs/#advantages) over writing separate apps or using other cross-platform technologies. But why use native rather than [transpiled](https://skip.dev/docs/modes/#transpiled) Swift? Here are several important reasons you might prefer compiled Swift: * **Full Swift language**. While Skip’s intelligent Swift-to-Kotlin transpiler can translate [the vast majority](https://skip.dev/docs/swiftsupport/) of the Swift language, there are some language features that cannot be mapped to Kotlin. * **Faithful runtime behavior**. Some aspects of Swift’s runtime behavior will never be the same when translated to Kotlin and run atop the JVM. For example, Swift’s deterministic object deallocation cannot be replicated on the JVM, which uses indeterministic garbage collection to manage memory. * **Full stdlib and Foundation**. The [SkipLib](https://skip.dev/docs/modules/skip-lib/) and [SkipFoundation](https://skip.dev/docs/modules/skip-foundation/) libraries replicate much of the Swift standard library and Foundation in Kotlin. Native Swift on Android, however, has more complete coverage. * **Third-party Swift libraries**. [Thousands of Swift packages ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) compile for Android, and it often [isn’t difficult](https://skip.dev/docs/porting/) to port others. This gives you access to a world of third-party packages without needing to turn them into Skip modules. * **Seamless C and C++ integration**. While the [SkipFFI](https://skip.dev/docs/modules/skip-ffi/) framework enables transpiled Kotlin to interface with native C code on Android, the process of creating the interface between the two languages can be cumbersome. Native Swift on Android unlocks Swift’s excellent integration with the C and C++ languages. * **High performance**. Java on Android is very fast, but it is a garbage-collected and heap-allocated runtime. Swift’s value types like structs and enums, which can be stack-allocated, offer the fastest bare-metal performance possible on a device. And deterministic deallocations can keep memory watermarks low and avoid hitches that result from garbage collection pauses. ### Disadvantages [Section titled “Disadvantages”](https://skip.dev/docs/modes/#disadvantages) Using native Swift doesn’t come without tradeoffs. Here are some notable disadvantages versus Skip’s transpilation: * **App size**. Bundling the Swift standard library, Foundation, and Swift internationalization libraries adds approximately 60 megabytes to your final Android app bundle size. We are exploring ways to reduce this overhead in future releases. * **Kotlin/Java Bridging**. Transpiled Swift can interact directly with Kotlin and Java API. For this reason, Skip allows your native Swift to [incorporate transpiled code](https://skip.dev/docs/platformcustomization/#calling-kotlin-api) . But moving data between compiled Swift and Kotlin or Java still requires [bridging](https://skip.dev/docs/modes/#bridging) . * **Debugging**. [Debugging](https://skip.dev/docs/debugging/) native code on Android is more difficult than debugging generated Kotlin, where you can take full advantage of Android Studio’s built-in Kotlin/Java debugging tools. * **Build time**. Building and deploying native Swift using the Android toolchain is slower than building using transpilation, due to overhead with the native compilation and packaging of the shared object files into the `.apk`. * **Ejectability**. One benefit of purely-transpiled code is that you can always “eject” from Skip and continue to iterate separately on _both_ your Swift and transpiled Kotlin code. If you eject Skip when using native mode, only your iOS app would remain fully intact. ### Conclusion [Section titled “Conclusion”](https://skip.dev/docs/modes/#conclusion) We believe that Skip Fuse’s advantages outweigh its disadvantages for most use cases, and that even these disadvantages will lessen over time as it matures. We expect, therefore, that most developers will choose to use native mode where possible. Transpiled mode, however, is excellent for cross-platform libraries that must interface intimately with Kotlin/Java API, so many apps will depend on transpiled libraries for using platform services. Transpiled Mode: Skip Lite -------------------------- [Section titled “Transpiled Mode: Skip Lite”](https://skip.dev/docs/modes/#transpiled) Transpilation is the process of converting one computer language into another language with a similar level of abstraction. Skip’s _transpiled_ mode converts your Swift source code into the equivalent Kotlin for running on Android. It is a combination of: * A Swift-to-Kotlin transpiler. * A suite of open source [libraries](https://skip.dev/docs/modules/) that mirror core frameworks like the Swift standard library and Foundation for transpiled Swift. * Xcode integration and tooling to transpile and deploy your Swift across both iOS and Android. The following diagram depicts a pure transpiled project: ![Skip Non-Native Diagram](https://assets.skip.dev/diagrams/skip-diagrams-non-native.svg) ### Advantages [Section titled “Advantages”](https://skip.dev/docs/modes/#advantages-1) Transpiled mode’s advantages and disadvantages are mirror opposites of native mode’s. * **Integration with Android APIs**. The primary benefit of transpilation is near-perfect integration with Android’s Kotlin and Java APIs. Because your Swift code is converted to Kotlin, it can [directly call other Kotlin API](https://skip.dev/docs/platformcustomization/) , just as if it were calling Swift. And because the Kotlin language features seamless integration with Java, your Swift code can call Java API as well. Unlike native mode, no [bridging](https://skip.dev/docs/modes/#bridging) is required. * **Transparency**. Transpilation allows you to see and understand all of Skip’s output. Skip’s Kotlin is fully human-readable and even overridable: Skip includes the ability to [insert or substitute literal Kotlin](https://skip.dev/docs/platformcustomization/#skip-comments) inline with your Swift. This is of particular use during [debugging](https://skip.dev/docs/debugging/) , where you get full stack traces and can take full advantage of Andriod’s debugging tools to step through your generated Kotlin. * **Ejectability**. If Skip were to disappear, transpiled mode still gives you the full source code to both the iOS and Android versions of your app. You could continue to evolve the app as separate iOS and Android codebases (which is how many dual-platform apps are developed). Skip does not have any required runtime components other than the libraries it uses to provide the Foundation, SwiftUI, etc APIs on Android, and these libraries are all [free and open-source ↗](https://source.skip.tools/) . * **App size**. Transpiled apps don’t have to bundle anything but Skip’s relatively slim compatibility libraries, while native apps have to include the much larger Swift Foundation and internationalization libraries. * **Build time**. The combination of Skip’s transpiler and the Android Kotlin compiler is faster than building with the full native Swift toolchain when iterating on your Android code. ### Disadvantages [Section titled “Disadvantages”](https://skip.dev/docs/modes/#disadvantages-1) * **It isn’t real Swift**. While Skip’s intelligent Swift-to-Kotlin transpiler can translate [the vast majority](https://skip.dev/docs/swiftsupport/) of the Swift language, there are some language features that cannot be mapped to Kotlin. Additionally, some aspects of Swift’s runtime behavior will never be the same when translated to Kotlin and run atop the JVM. For example, Swift’s deterministic object deallocation cannot be replicated on the JVM, which uses indeterministic garbage collection to manage memory. * **Limited stdlib and Foundation**. The [`SkipLib`](https://skip.dev/docs/modules/skip-lib/) and [`SkipFoundation`](https://skip.dev/docs/modules/skip-foundation/) libraries replicate much of the Swift standard library and Foundation in Kotlin. Native Swift on Android, however, has more complete coverage. * **Few third-party libraries**. There are relatively few third-party transpiled libraries for Skip. Meanwhile, [thousands of Swift packages ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) compile for Android, and it often [isn’t difficult](https://skip.dev/docs/porting/) to port others. * **C and C++ integration**. While the [SkipFFI](https://skip.dev/docs/modules/skip-ffi/) framework enables transpiled Kotlin to interface with native C code on Android, the process of creating the interface between the two languages can be cumbersome. Native Swift on Android unlocks Swift’s excellent integration with the C and C++ languages. * **Performance**. Java on Android is very fast, but it is a garbage-collected and heap-allocated runtime. It often has a high memory watermark. Native Swift’s value types like structs and enums, which can be stack-allocated, offer the fastest bare-metal performance possible on a device. ### Conclusion [Section titled “Conclusion”](https://skip.dev/docs/modes/#conclusion-1) Transpiled mode’s ease of interacting with Kotlin and Java API makes it ideal for use cases involving tight integration with Android platform services, such as cross-platform libraries like [Skip Keychain ↗](https://github.com/skiptools/skip-keychain) . For shared business logic and other general use cases, however, we believe that most developers will choose native mode - primarily for its complete language coverage and the availability of third-party packages. Many apps will use a combination of native app logic and transpiled libraries for accessing platform services. Configuration ------------- [Section titled “Configuration”](https://skip.dev/docs/modes/#configuration) Every Skip module must include a `Skip/skip.yml` file in its source directory. The `skip create` command creates this file for you automatically when you [start a new project](https://skip.dev/docs/gettingstarted/) . The `mode` that you declare in `skip.yml` determines whether a module is compiled as native Swift or transpiled into Kotlin: skip: mode: 'native'|'transpiled' If the `mode` is not specified, it defaults to `'transpiled'`. A project can contain a mix of native and transpiled modules. As we will see later, you also configure [bridging](https://skip.dev/docs/modes/#bridging) between the native Swift and transpiled Swift/Kotlin/Java in `skip.yml`. The `skip create` command will also generate the appropriate dependencies for your target mode in `Package.swift`. For model-level code, a native module will typically depend on SkipFuse and SkipModel, while a transpiled module will depend on SkipModel alone. For UI code, a native module will depend on SkipFuseUI, while a transpiled module will depend on SkipUI. Here is an example `Package.swift` for a native app with separate model and logic modules: import PackageDescription let package = Package( name: "skipapp-hiya", defaultLocalization: "en", platforms: [.iOS(.v17), .macOS(.v14), .tvOS(.v17), .watchOS(.v10), .macCatalyst(.v17)], products: [ .library(name: "HiyaSkipApp", type: .dynamic, targets: ["HiyaSkip"]), .library(name: "HiyaSkipModel", type: .dynamic, targets: ["HiyaSkipModel"]), .library(name: "HiyaSkipLogic", type: .dynamic, targets: ["HiyaSkipLogic"]), ], dependencies: [ .package(url: "https://source.skip.tools/skip.git", from: "1.2.7"), .package(url: "https://source.skip.tools/skip-model.git", from: "1.0.0"), .package(url: "https://source.skip.tools/skip-fuse.git", from: "1.0.0"), .package(url: "https://source.skip.tools/skip-fuse-ui.git", from: "1.0.0") ], targets: [ .target(name: "HiyaSkip", dependencies: [ "HiyaSkipModel", .product(name: "SkipFuseUI", package: "skip-fuse-ui") ], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]), .target(name: "HiyaSkipModel", dependencies: [ "HiyaSkipLogic", .product(name: "SkipModel", package: "skip-model"), .product(name: "SkipFuse", package: "skip-fuse") ], plugins: [.plugin(name: "skipstone", package: "skip")]), .testTarget(name: "HiyaSkipModelTests", dependencies: [ "HiyaSkipModel", .product(name: "SkipTest", package: "skip") ], plugins: [.plugin(name: "skipstone", package: "skip")]), .target(name: "HiyaSkipLogic", dependencies: []), ]) Bridging -------- [Section titled “Bridging”](https://skip.dev/docs/modes/#bridging) Under normal circumstances, the only way for native code to communicate with Kotlin/Java is through the limited and cumbersome [Java Native Interface ↗](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/) , and the only way for Kotlin/Java to communicate with native code is through “native” or “external” [functions ↗](https://docs.oracle.com/en/java/javase/21/docs/specs/jni/design.html#compiling-loading-and-linking-native-methods) . Just as Xcode can generate “bridging headers” that allow your Swift and Objective C to interoperate, however, the SkipStone build plugin can generate code that enables transparent interaction between your native Swift and the Kotlin/Java world of Android services and UI. Bridging allows you to automatically project your _compiled_ Swift types and API to Kotlin/Java, and to project your _transpiled_ Swift as well as other Kotlin/Java types and API to native Swift. Bridged API can be used exactly as if it were written in the target language, just as Swift can use bridged Objective C and vice versa. ### Configuration [Section titled “Configuration”](https://skip.dev/docs/modes/#configuration-1) You can use bridging with any Skip module - that is, any module that includes the SkipStone build [plugin](https://skip.dev/docs/dependencies/) and has a [`skip.yml`](https://skip.dev/docs/modes/#configuration) file. To bridge a type or API, add a `// SKIP @bridge` [Skip comment](https://skip.dev/docs/platformcustomization/#skip-comments) to its declaration. The following example bridges the `Person` class minus its `age` property. // SKIP @bridgepublic class Person { // SKIP @bridge public init() { ... } // SKIP @bridge public var firstName = "" // SKIP @bridge public var lastName = "" public var age = 0 // SKIP @bridge public func fullName() -> String { ... } ...} Assuming this is a native Swift module, bridging allows `Person` to be used from Kotlin code: fun personClient(): String { val p = Person() p.firstName = "Tim" p.lastName = "Apple" // p.age = 10 This would be a compile error! Age is not bridged return p.fullName()} Following sections will cover [Swift to Kotlin/Java](https://skip.dev/docs/modes/#bridgetokotlin) and [Kotlin/Java to Swift](https://skip.dev/docs/modes/#bridgetoswift) bridging in more detail. To bridge a type and include its members by default, use `// SKIP @bridgeMembers`. This will bridge all members with the same or greater declared visibility as the type. In other words, adding this to a `public` type will bridge all of its `public` members, while adding it to a type with default visibility will bridge all members with at least default visibility. To exclude a member from being bridged, add the `// SKIP @nobridge` comment. Here is the `Person` class using `@bridgeMembers`: // SKIP @bridgeMemberspublic class Person { public init() { ... } public var firstName = "" public var lastName = "" // SKIP @nobridge public var age = 0 public func fullName() -> String { ... } ...} Rather than annotate every bridged API, you can use `skip.yml` to enable auto-bridging. When auto-bridging is enabled for a module, all public API is bridged by default. To turn on auto-bridging, simply add `bridging: true` to your module’s `skip.yml`. No additional configuration is required. For example, the following `skip.yml` configures a native Swift module whose public types and API will be bridged for consumption by Kotlin/Java code: skip: mode: 'native' bridging: true Use `// SKIP @nobridge` to exclude a type or API from auto-bridging. Here is the equivalent of our previous `Person` definition under auto-bridging: public class Person { public init() { ... } public var firstName = "" public var lastName = "" // SKIP @nobridge public var age = 0 public func fullName() -> String { ... } ...} Note that `bridging: true` is actually shorthand for the following full configuration syntax, which we’ll see more of below: skip: mode: 'native' bridging: auto: true ### Bridging Swift to Kotlin/Java [Section titled “Bridging Swift to Kotlin/Java”](https://skip.dev/docs/modes/#bridgetokotlin) Enabling bridging on a native Swift module allows its API to be used from Kotlin/Java code. The most common use case is writing your app’s business logic in native Swift, then enabling bridging so that your bespoke Android UI layer can access it. When Skip bridges native Swift to Kotlin, it generates Kotlin wrappers that delegate to your native Swift. By default, these wrappers are optimized for consumption by Skip’s transpiled Swift. For example: // In module NetworkUtils public class URLManager { public var urls: [URL] = [] public func perform(_ action: (URL) -> Void) { ... } ...} Generates a Kotlin wrapper like the following: package network.utils class URLManager { var urls: skip.lib.Array get() { // Delegate to native code ... } set(newValue) { // Delegate to native code ... } fun perform(action: (skip.foundation.URL) -> Unit) { // Delegate to native code... } ...} This allows any other Kotlin/Java code to interact with this API, but notice its use of `skip.lib.Array` and `skip.foundation.URL`. These are types from Skip’s suite of [open-source libraries](https://skip.dev/docs/modules/) that mirror Swift API in Kotlin. Using them makes sense when your API will be consumed by transpiled Swift, because they are the same types Skip uses in its transpilation output. You get perfect interoperability, including expected Swift behavior like value semantics for the `Array` struct. ### Kotlin Compatibility Option [Section titled “Kotlin Compatibility Option”](https://skip.dev/docs/modes/#kotlincompat) If you plan on consuming your native Swift directly from Kotlin/Java instead - for example, a Compose UI for Android - you can configure `skip.yml` to optimize for Kotlin compatibility: skip: mode: 'native' bridging: auto: true options: 'kotlincompat' Under this configuration, the generated Kotlin wrapper will use standard Kotlin/Java types: package network.utils class URLManager { var urls: kotlin.collections.List get() { // Delegate to native code ... } set(newValue) { // Delegate to native code ... } fun perform(action: (java.net.URI) -> Unit) { // Delegate to native code... } ...} See the [Bridging Support Reference](https://skip.dev/docs/bridging/) for additional information on what standard types and Swift language constructs can be bridged. ### Bridging Kotlin/Java to Swift [Section titled “Bridging Kotlin/Java to Swift”](https://skip.dev/docs/modes/#bridgetoswift) Skip offers multiple options for consuming Kotlin/Java API in native Swift. The most comprehensive is to bridge a transpiled Swift module. Skip’s ability to transpile Swift to Kotlin is one of its strengths. Transpiled Swift code effectively _is_ Kotlin, which gives it a superpower: it can directly call any other Kotlin/Java APIs, with no bridging or configuration, just as if they were written in Swift. The Skip [documentation](https://skip.dev/docs/platformcustomization/#calling-kotlin-api) provides details. When you enable bridging on a transpiled module and expose its API to native Swift, you are also exposing this superpower. How? Let’s consider an example. Suppose that your native Swift wants to store sensitive data in Android’s encrypted shared preferences, which are only accessible via Kotlin. We choose to make a reusable library to expose this functionality to Swift. First, we create a transpiled module and enable bridging: skip: mode: 'transpiled' bridging: true Then we write our Swift interface to the Android service, directly calling Android’s Kotlin API as needed. Recall that we can do this because Skip is transpiling our Swift into Kotlin for the Android build. #if !SKIP_BRIDGE#if os(Android)import Foundation import android.content.Contextimport android.content.SharedPreferencesimport android.security.keystore.KeyGenParameterSpecimport android.security.keystore.KeyPropertiesimport androidx.security.crypto.EncryptedSharedPreferencesimport androidx.security.crypto.MasterKeys /// Secure storage using Android encrypted shared preferences.public final class SecureStorage { /// The shared keychain. public static let shared = SecureStorage() private let lock = NSLock() /// Retrieve a value. public func string(forKey key: String) -> String? { let prefs = initializePreferences() return prefs.getString(key, nil) } // Additional getters omitted... /// Store a key value pair. public func set(_ string: String, forKey key: String) { let prefs = initializePreferences() let editor = prefs.edit() editor.putString(key, string) editor.apply() } // Additional setters omitted... private var preferences: SharedPreferences? private func initializePreferences() -> SharedPreferences { lock.lock() defer { lock.unlock() } if let preferences { return preferences } let context = ProcessInfo.processInfo.androidContext let alias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) preferences = EncryptedSharedPreferences.create("tools.skip.SecureStorage", alias, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM) return preferences! }}#endif#endif Finally, we add this module as a dependency of our native Swift module. Now our native Swift can import and use `SecureStorage` exactly as if it were just another Swift type! #### Example Bridging Diagram: SkipKeychain [Section titled “Example Bridging Diagram: SkipKeychain”](https://skip.dev/docs/modes/#example-bridging-diagram-skipkeychain) The following visualization summarizes how the Skip generated bridging works in the context of the [SkipKeychain ↗](https://github.com/skiptools/skip-keychain) framework, which exposes a direct Swift interface to the iOS-only [Security Keychain services ↗](https://developer.apple.com/documentation/security/keychain-services) framework, and whose transpiled version uses the equivalent functionality in the [`androidx.security.crypto.EncryptedSharedPreferences` ↗](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences) package on Android. ![Skip Bridging Diagram](https://assets.skip.dev/diagrams/skip-diagrams-bridging.svg) ### `AnyDynamicObject` [Section titled “AnyDynamicObject”](https://skip.dev/docs/modes/#anydynamicobject) We believe that creating an ecosystem of Swift-API modules like [SkipKeychain ↗](https://github.com/skiptools/skip-keychain/tree/main) to access both cross-platform and Android-only services is the best long-term approach, and that using transpiled Skip modules is a great pathway. You can also [embed transpiled Swift](https://skip.dev/docs/platformcustomization/#calling-kotlin-api) in a native module. Sometimes, however, you may want to use a few Kotlin/Java types from your native Swift without using transpiled Swift. For this use case, we provide the `AnyDynamicObject` type. `AnyDynamicObject` is a native Swift class vended by the SkipFuse framework that can represent any Kotlin/Java type. It uses Swift’s dynamic features to allow you to access any property or call any function, and it uses Kotlin/Java’s powerful reflection abilities to invoke your call on the underlying JVM object. The following is an example of using `AnyDynamicObject` in its bare form: import SkipFuse ... // Specify the Kotlin or Java class name and any constructor argumentslet date = try AnyDynamicObject(className: "java.util.Date", 999) // java.util.Date(999) // All calls must specify their return type and unwrap if non-optional// All function calls are `throws`let time1: Int64 = try date.getTime()! // 999 // Like Kotlin, `AnyDynamicObject` allows property syntax for Java getters and setters// Property access is not `throws`let time2: Int64 = date.time! // 999 // Java calls must use positional args. Kotlin calls may use labeled args and omit defaulted args// Even `Void` function calls need to specify a return typetry date.setTime(1000) as Void // But property setters do notdate.time = 1001 // Retrieve other objects as `AnyDynamicObject`let instant: AnyDynamicObject? = date.instantlet s1: String? = try instant?.toString() // Freely chain calls through other objectslet s2: String = try date.instant!.toString()!let s3: String = try date.getInstant()!.toString()! // Types with bridging support can be assigned from their Kotlin/Java equivalents// See the Bridging Support Referencelet urls: [URL] = // ... call that returns any kotlin.collections.List This raw use of `AnyDynamicObject` has certain disadvantages, though. Constructing an object relies on always specifying the full class name, and there is no way to `typealias` it. Accessing static members is non-intuitive: it requires creating another `AnyDynamicObject` that uses a different constructor: let dateStatics = try AnyDynamicObject(forStaticsOfClassName: "java.util.Date")let date: AnyDynamicObject? = try? dateStatics.parse(dateString) Skip offers some optional syntactic sugar for these issues. To use it, set a `dynamicroot` string in your `skip.yml` configuration. The specified string turns into a magic namespace from which you can access fully-qualified Kotlin/Java types. For example, with the following `skip.yml`: skip: mode: 'native' dynamicroot: 'D' You can write code like the following: let date = try D.java.util.Date(999)let time: Int64 = date.time! How does this work? Skip’s build plugin detects your use of `D.java.util.Date` and generates a matching subclass of `AnyDynamicObject`. To access static members, the generated subclass includes a `Companion(options:)` function, mirroring Kotlin’s use of `Companion` for statics. let date: AnyDynamicObject = try D.java.util.Date.Companion().parse(dateString)! Because `D.java.util.Date` is a real type, you can create a `typealias`: typealias JDate = D.java.util.Date ... let d1 = JDate(999)let s: String? = try d1.instant?.toString()let d2: JDate? = try JDate.Companion().parse(dateString) If you have a reference to a base `AnyDynamicObject` value and you want to convert it to a generated `dynamicroot` type, use the `as()` function: let object: AnyDynamicObject = ...let date: D.java.util.Date = object.as(D.java.util.Date.self) // or... let date: JDate = object.as(JDate.self) When using `dynamicroot`, keep the following in mind: 1. Generated types have `internal` visibility, so they are only visible to your current module. This prevents conflicts when multiple linked modules use the same Kotlin/Java types. 2. The types are only generated for the Android build of your code. After all, iOS can’t use Kotlin/Java API. So you must only use these generated types in code guarded with `#if os(Android)`. See [Cross-Platform Topics](https://skip.dev/docs/platformcustomization/) . ### Bridging with `AnyDynamicObject` [Section titled “Bridging with AnyDynamicObject”](https://skip.dev/docs/modes/#bridging-with-anydynamicobject) The previous sections described two approaches to using Kotlin/Java from native Swift: bridging transpiled Swift code or using `AnyDynamicObject`. These approaches can also be combined. Suppose that you’ve wrapped a Kotlin API in transpiled Swift for bridging. You may still want to expose the underlying Kotlin objects to your Android code for certain advanced use cases. Skip’s [Firebase package](https://skip.dev/docs/modules/skip-firebase/) mapping the iOS Firebase API to Android uses this pattern often. Here is a code excerpt: #if SKIPpublic final class FirebaseApp { // Allow code to access underlying Kotlin object if needed public let app: com.google.firebase.FirebaseApp public init(app: com.google.firebase.FirebaseApp) { self.app = app } public var name: String { app.name } public var options: FirebaseOptions { FirebaseOptions(options: app.options) } ...}#endif But how will the `com.google.firebase.FirebaseApp` type be represented to native Swift? This is a native Kotlin type, so Skip has no knowledge of its API. The answer is that Skip will map it to `AnyDynamicObject`. The bridged Swift implementation of `FirebaseApp` would look something like: public final class FirebaseApp { public var app: AnyDynamicObject { // Delegate to Kotlin code ... } public init(app: AnyDynamicObject) { // Delegate to Kotlin code ... } public var name: String { // Delegate to Kotlin code ... } ...} You must specify an explicit type and use the fully-qualified Kotlin/Java class name on any Swift API that should map to `AnyDynamicObject`. The following transpiled Swift will correctly bridge to `AnyDynamicObject`: let app: com.google.firebase.FirebaseApp = ... func f(app: com.google.firebase.FirebaseApp) { ...} But the following examples will produce build-time errors: import com.google.firebase.FirebaseApp let app: FirebaseApp = ... // Not fully qualifiedlet app = com.google.firebase.FirebaseApp() // No explicit type func f(app: FirebaseApp) { // Not fully qualified ...} The combination of transpiled bridging and `AnyDynamicObject` allows you to provide a natural Swift API to important Kotlin and Java services while still exposing the raw Kotlin or Java objects for advanced use cases. Migrating Between Modes ----------------------- [Section titled “Migrating Between Modes”](https://skip.dev/docs/modes/#migration) Whether you are using native or transpiled modes, you are writing your code in Swift. Migrating between them, therefore, is much easier than moving between different programming languages - in fact it is often trivial. This is particularly true when migrating from transpiled to native mode, given that transpiled mode supports only a subset of native mode’s Swift syntax, and native mode offers far more third-party libraries. The first step in migration is to update your `skip.yml` and `Package.swift` files, as described in the [Configuration](https://skip.dev/docs/modes/#configuration) section. Then it is a matter of migrating any code that behaves differently under each mode. See the chapters on [Development](https://skip.dev/docs/app-development/) and [Cross-Platform Topics](https://skip.dev/docs/platformcustomization/) in particular. --- # C Development Reference | Skip [Skip to content](https://skip.dev/docs/c-development/#_top) C Development Reference ======================= One of the advantages of Skip’s [native mode](https://skip.dev/docs/modes/#native) is the ability to take full advantage of Swift’s excellent interoperability with C libraries. But thanks to the magic of Skip’s [`skip-ffi`](https://skip.dev/docs/modules/skip-ffi/) module, you can interoperate with C in [transpiled mode](https://skip.dev/docs/modes/#transpiled) as well! See the [Sharing C code between Swift and Kotlin for iPhone and Android apps](https://skip.dev/blog/sharing-c-between-swift-and-kotlin/) blog entry to learn more about using C from transpiled Swift. --- # Cross-Platform Topics | Skip [Skip to content](https://skip.dev/docs/platformcustomization/#_top) Cross-Platform Topics ===================== Whether to work around limitations in Skip’s Android support, differentiate your iOS and Android experiences, take advantage of OS-specific features, or simply because you prefer to write parts of your app separately for each platform, you will likely find yourself wanting to write iOS-only or Android-only code. Skip makes this easy. Compiler Directives ------------------- [Section titled “Compiler Directives”](https://skip.dev/docs/platformcustomization/#compiler-directives) The most common and convenient mechanism for writing iOS or Android-only code is [conditional compiler directives ↗](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Conditional-Compilation-Block) . You probably already use these in your Swift. For example: #if DEBUG…#else…#endif Use the `os(Android)` condition to create blocks of code that are only compiled into your iOS or Android app. The following code blocks would all print “Android” on Android and “iOS” on iOS: #if os(Android)print("Android")#endif #if !os(Android)print("iOS")#endif #if os(Android)print("Android")#elseprint("iOS")#endif You can also use compiler directives in SwiftUI modifier chains: Text("Hello World") #if os(Android) .italic() #else .bold() #endif Calling Kotlin and Java API --------------------------- [Section titled “Calling Kotlin and Java API”](https://skip.dev/docs/platformcustomization/#calling-kotlin-api) As we have seen, `os(Android)` blocks allow you to easily customize your code path for Android. But Skip also recognizes another conditional symbol: `SKIP`. And this symbol unlocks a Skip superpower: the ability to directly call Kotlin and Java API. Consider the following code: func printFormatted(time timeInMills: Int64) { #if os(Android) let formatted = androidTimeString(milliseconds: timeInMills) #else let formatted = … iOS code path … #endif print(formatted)} #if SKIPfunc androidTimeString(milliseconds: Int64) -> String { let dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", java.util.Locale.getDefault()) dateFormat.timeZone = java.util.TimeZone.getTimeZone("GMT") return dateFormat.format(java.util.Date(milliseconds))}#endif What is going on here? 1. The `printFormatted` function uses `#if os(Android)` to call `androidTimeString(milliseconds:)` in the Android build. 2. `androidTimeString(milliseconds:)` is defined within a `#if SKIP` block. All code within `#if SKIP` blocks is [transpiled](https://skip.dev/docs/modes/#transpiled) to Kotlin. So while this looks like Swift code, it becomes Kotlin code during the Android build process. That means it can **call any other Kotlin or Java API** naturally, just as if the API were written in Swift. Here we use various Java date formatting functions. 3. Skip automatically [bridges](https://skip.dev/docs/modes/#bridging) all of the **non-private** code in your `SKIP` blocks so that you can interact with it from your compiled Swift. While the example above is rather contrived, consider the broader implications. You can use this mechanism to directly interact with **any** Android libraries, including third-party dependencies or your own custom Kotlin. The ability so easily utilize both iOS-only and Android-only APIs - right inline - differentiates Skip from many other cross-platform solutions, where calling platform-native code can be difficult. [Later](https://skip.dev/docs/platformcustomization/#swiftui-and-compose) , we’ll see how to take advantage of this to mix SwiftUI and Compose views. ### Syntax [Section titled “Syntax”](https://skip.dev/docs/platformcustomization/#kotlin-in-swift-syntax) When you call Kotlin and Java code from within transpiled `#if SKIP` blocks, remember that you are still writing _in Swift_. The code is still parsed by Xcode, so it must have valid Swift syntax. But it is excluded from your iOS build and invisible to the Swift compiler, which allows it to make any syntactically valid API call without causing Xcode errors. Just write natural Swift code, and imagine that the Kotlin or Java API you’re calling is a Swift library API. Skip’s transpiler will take care of the rest. If you’re attempting to cut and paste Kotlin inline, you’ll have to turn it into valid Swift to avoid syntax errors. Luckily, the languages are extremely similar. You’ll typically only have to change a few calling conventions: * Named parameter values in Kotlin are specified with an equals (`=`) sign, whereas in Swift they use a colon (`:`). Note that parameter names are never required in Kotlin. * Closure arguments in Kotlin are specified by an arrow (`->`), whereas in Swift they use the `in` keyword. * To import everything in a Kotlin package, use `import com.xyz.__` rather than `import com.xyz.*`. The latter is not valid Swift. For example, the following Kotlin: val start = 1var result = 0for (i in 1 until 10) { result += someFunction(value = start + i, block = { arg -> arg + 1 })} Would use the following syntax when embedded in a Swift `#if SKIP` block: let start = 1var result = 0for i in 1..<10 { result += someFunction(value: start + i, block: { arg in arg + 1 })} If you find yourself wanting to include Kotlin, consider adding [Kotlin files](https://skip.dev/docs/platformcustomization/#kotlin-files) to your project rather than translating it into embedded Swift. ### Complex Types [Section titled “Complex Types”](https://skip.dev/docs/platformcustomization/#complex-types) You may want to pass complex data types back and forth between your compiled Swift and transpiled code in your `#if SKIP` blocks, as well as between your transpiled Swift and various Kotlin and Java APIs. In order to pass types in and out of transpiled `#if SKIP` functions, those types must be bridged. Many common types support bridging out of the box, and bridging your own types is often a simple matter of annotating them. See the [bridging documentation](https://skip.dev/docs/modes/#bridging) for details. Passing complex data back and forth to Kotln API is typically easy, because [the Skip transpiler](https://skip.dev/docs/swiftsupport/) unifies the Swift and Kotlin type systems. When Skip does use bespoke types to represent common data structures, though, we have standardized on a simple method for converting these types to their Kotlin equivalents: * `.kotlin(nocopy: Bool = false)`: Skip implements this function for its Swift types on Android to return the equivalent standard Kotlin object. For example, when your Swift code uses an `Array`, Skip’s transpiled output uses `skip.lib.Array`, a Skip type designed to mirror the API and value semantics of Swift arrays. Calling `someArray.kotlin()` returns a `kotlin.collections.List` containing the same elements. By default, Skip returns a copy which also recursively invokes `.kotlin()` on each element. You can use the optional `nocopy` parameter to hint that this is not necessary. In that case, Skip will return the `Array`’s backing `List` instance directly if possible. Here is an implementation of this function for `Calendar` in the [SkipFoundation](https://skip.dev/docs/modules/skip-foundation/) transpiled package. Note that SkipFoundation’s `Calendar` uses an internal `platformValue` member of type `java.util.Calendar` to take advantage of Java’s existing calendaring functionality. #if SKIPextension Calendar: KotlinConverting { public func kotlin(nocopy: Bool = false) -> java.util.Calendar { return nocopy ? platformValue : platformValue.clone() as java.util.Calendar }}#endif Again, the `.kotlin()` function is only needed when passing builtin types with different representations to Kotlin and Java API. It isn’t needed for basic types like `Int` or `String`, and types that you define yourself can directly implement Kotlin or Java protocols. * * * Compose Integration ------------------- [Section titled “Compose Integration”](https://skip.dev/docs/platformcustomization/#swiftui-and-compose) Skip’s SwiftUI implementation includes additional API that allows you to move fluidly between SwiftUI and pure Compose code. This powerful capability is useful for several reasons: * If you prefer to code portions of your app entirely in Compose, Skip’s interoperability allows you to share as much or as little of your UI as you like between the platforms, without worrying about fighting the framework. You can [add Kotlin libraries](https://skip.dev/docs/dependencies/) and [include Kotlin files](https://skip.dev/docs/platformcustomization/#kotlin-files) full of custom Kotlin and Compose functions that you call using the techniques in this chapter. * Easily embed Android-specific UI components and access Android-specific features from anywhere in your app. * Skip hasn’t yet implemented every SwiftUI API for Android. Being able to trivially mix Compose views into your UI enables you to work around any of Skip’s temporary shortcomings. The `MapView` example below uses Apple Maps on iOS and Google Maps on Android. To embed Google Maps, it utilizes Skip’s Android-only `ComposeView`, which is a native SwiftUI wrapper around a transpiled `ContentComposer`. For a detailed discussion of embedding Compose in SwiftUI and vice versa, see the [`ComposeView`](https://skip.dev/docs/modules/skip-ui/#composeview) topic in the SkipUI module. import SwiftUIimport MapKit struct MapView : View { let latitude: Double let longitude: Double var body: some View { #if os(Android) // on Android platforms, we use com.google.maps.android.compose.GoogleMap within in a ComposeView ComposeView { MapComposer(latitude: latitude, longitude: longitude) } #else // on Darwin platforms, we use the SwiftUI Map type Map(initialPosition: .region(MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)))) #endif }} #if SKIPimport com.google.maps.android.compose.__import com.google.android.gms.maps.model.CameraPositionimport com.google.android.gms.maps.model.LatLng struct MapComposer : ContentComposer { let latitude: Double let longitude: Double @Composable func Compose(context: ComposeContext) { GoogleMap(cameraPositionState: rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(LatLng(latitude, longitude), Float(12.0)) }) }}#endif Skip also provides Android-specific SwiftUI API that allows you to customize Skip’s underlying Compose components. Read more in the SkipUI module’s [Material](https://skip.dev/docs/modules/skip-ui/#material) documentation. * * * Model Integration ----------------- [Section titled “Model Integration”](https://skip.dev/docs/platformcustomization/#model-integration) Skip ensures that the `@Observable` types you define in your shared Swift business logic can power your Compose UI as well, as described in the Development [chapter](https://skip.dev/docs/app-development/#observables) . Transparent model integration is an additional way that Skip allows you to move seamlessly between shared and platform-specific code. * * * Android Context --------------- [Section titled “Android Context”](https://skip.dev/docs/platformcustomization/#android-context) Many Android system calls require a [`Context` ↗](https://developer.android.com/reference/android/content/Context) or [`Activity` ↗](https://developer.android.com/reference/android/app/Activity) reference. ### Skip Fuse [Section titled “Skip Fuse”](https://skip.dev/docs/platformcustomization/#android-context-fuse) [`SkipFuse` ↗](https://github.com/skiptools/skip-android-bridge/blob/main/Sources/SkipAndroidBridge/ProcessInfo.swift) and [`SkipFuseUI` ↗](https://github.com/skiptools/skip-fuse-ui/blob/main/Sources/SkipSwiftUI/UIKit/UIApplication.swift) provide Swift extensions to retrieve these references for compiled Skip Fuse code: import SkipFuse #if os(Android)let applicationContext = ProcessInfo.processInfo.dynamicAndroidContext()if let packageName: String = try? applicationContext.getPackageName() { …}…#endif import SwiftUI #if os(Android)if let activity = UIApplication.shared.dynamicAndroidActivity() { …}#endif These Android objects are returned to your native code as `AnyDynamicObject` subclasses. Learn more about `AnyDynamicObject` [here](https://skip.dev/docs/modes/#anydynamicobject) . ### Skip Lite [Section titled “Skip Lite”](https://skip.dev/docs/platformcustomization/#android-context-lite) Similarly, [`SkipFoundation` ↗](https://github.com/skiptools/skip-foundation/blob/main/Sources/SkipFoundation/ProcessInfo.swift) and [`SkipUI` ↗](https://github.com/skiptools/skip-ui/blob/main/Sources/SkipUI/SkipUI/UIKit/UIApplication.swift) provide Swift extensions to retrieve these references in transpiled Skip Lite code: import Foundation #if SKIPlet applicationContext = ProcessInfo.processInfo.androidContextlet packageName = applicationContext.getPackageName()…#endif import SwiftUI #if SKIPif let androidActivity: androidx.appcompat.app.AppCompatActivity = UIApplication.shared.androidActivity { let intent = androidActivity.getIntent() …}#endif * * * iOS and Android Libraries ------------------------- [Section titled “iOS and Android Libraries”](https://skip.dev/docs/platformcustomization/#ios-and-android-libraries) In addition to Skip’s own dual-platform libraries, [thousands of Swift packages ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) are available for Android. You can use these packages in your [Skip Fuse](https://skip.dev/docs/status/#skip_fuse) app just as you would in an iOS-only app. To learn how to integrate iOS-only or Android-only dependencies, however, read the [Dependencies](https://skip.dev/docs/dependencies/) documentation chapter. * * * Skip Comments ------------- [Section titled “Skip Comments”](https://skip.dev/docs/platformcustomization/#skip-comments) Skip treats any `//` or `/* */` comment line beginning with “SKIP” as a build instruction. Skip supports several instructions, all of which influence Skip’s output or behavior. * `SKIP ` This instruction applies Skip-specific attributes to the target element. Skip currently recognizes five attributes: `@nocopy`, `@nodispatch`, `@bridge`, `@bridgeMembers`, and `@nobridge`. See the [Struct](https://skip.dev/docs/swiftsupport/#structs) transpilation topic for an explanation of `@nocopy`. Use `@nodispatch` on a transpiled async function to prevent Skip from inserting Kotlin to run it on a `Dispatcher`. `@bridge`, `@bridgeMembers`, and `@nobridge` help control what API is [bridged](https://skip.dev/docs/modes/#bridging) between your compiled Swift and Kotlin. // SKIP @bridgestruct S { …} * `SKIP DECLARE: ` Replace the target declaration with custom Kotlin in transpiled code. This is useful to customize how a type, function, or property is declared in Kotlin, without affecting the transpilation of its body. // SKIP DECLARE: override fun SaverScope.save(value: Any): Any?override func save(value: Any) -> Any? { …} * `SKIP EXTERN`: Denote an external native function in transpiled code. This instruction is used in C integration. * `SKIP INSERT: ` Insert arbitrary Kotlin into transpiled code. // SKIP INSERT: var count by remember { mutableStateOf(100) }var countString = count.description * `SKIP NOWARN` Place this comment on the offending line to silence a Skip warning or error. // SKIP NOWARNlet dict = obj as? Dictionary * `SKIP REPLACE: ` Replace the target statement with arbitrary Kotlin in transpiled code. Unlike `SKIP DECLARE`, this includes the body. // SKIP REPLACE:// fun printOS() {// print("Android")// }public func printOS() { print("iOS")} * `SKIP SYMBOLFILE` This instruction is used at the top of a transpiled Swift source file. It tells Skip that the file should only be used to gather symbols about declared API. Skip expects that you will implement the API with a corresponding [Kotlin file](https://skip.dev/docs/platformcustomization/#kotlin-files) . In effect, it turns the Swift source into a header file. [SkipLib](https://skip.dev/docs/modules/skip-lib/) uses this technique extensively to implement aspects of the Swift standard library in Kotlin. Instructions that specify Kotlin code can span multiple comment lines. Skip assumes that the instruction’s Kotlin continues until Skip sees one of: * A new instruction * A blank comment line * The end of the comment block Thus, this: // SKIP INSERT: var count by remember { mutableStateOf(100) } And this are both valid: // SKIP INSERT:// var count by remember {// mutableStateOf(100)// } * * * Kotlin and Java Files --------------------- [Section titled “Kotlin and Java Files”](https://skip.dev/docs/platformcustomization/#kotlin-files) The mechanisms covered so far allow you to [use Kotlin and Java API from Swift](https://skip.dev/docs/platformcustomization/#compiler-directives) or to [embed bits of pure Kotlin](https://skip.dev/docs/platformcustomization/#skip-comments) within your transpiled Swift source. But Skip also allows you to include entire Kotlin and Java source files in your Xcode project. Simply use the standard `.kt` and `.java` extensions and place your source files into the `Sources//Skip` directory alongside your `skip.yml` file. Skip will include them in the resulting Android build. [SkipLib](https://skip.dev/docs/modules/skip-lib/) uses this technique to implement aspects of the Swift standard library in Kotlin. When working on Kotlin and Java files, we typically prefer to [open the generated Android project](https://skip.dev/docs/platformcustomization/#android-studio) in Android Studio to take advantage of its syntax highlighting, autocompletion, and other niceties. In fact, you could have a dedicated Android team working to extend and customize the Android version of your app using Android Studio, while other teams work on the iOS and shared portions of the codebase. ### Kotlin Package Names [Section titled “Kotlin Package Names”](https://skip.dev/docs/platformcustomization/#kotlin-package-names) Skip transforms your CamelCase Swift module names into dot-separated lowercase Kotlin package names: * `MyPackage` becomes `my.package` * `MyHTTPLibrary` becomes `my.http.library` * Kotlin package names must have at least two segments, so `Product` becomes `product.module` Skip also removes the `Tests` suffix from module names during translation, so that your tests and source end up in the same Kotlin package. Kotlin does not have an equivalent of Swift’s `@testable` attribute, so this is the only way to allow your tests to access internal module API. To avoid having to explicitly `import` your Kotlin and Java API when using it in your `#if SKIP` blocks, set the package name of your Kotlin and Java files to the derived package name of your Swift module. For example, if you are including the file `Sources/MyCoolApp/Skip/CustomKotlin.kt`, the package header should be `package my.cool.app`. * * * Working in Android Studio ------------------------- [Section titled “Working in Android Studio”](https://skip.dev/docs/platformcustomization/#android-studio) Skip generates a complete gradle project from your dual-platform app or framework. You can open this project in [Android Studio ↗](https://developer.android.com/studio/) . This is often useful to [debug](https://skip.dev/docs/debugging) Android-specific issues and unit tests or iterate on your [Kotlin files](https://skip.dev/docs/platformcustomization/#kotlin-files) . ### Setup [Section titled “Setup”](https://skip.dev/docs/platformcustomization/#android-studio-setup) By default, Xcode and Android Studio build to _different locations_: Xcode uses `DerivedData`, while Android Studio uses a `.build` folder in your project directory. This won’t affect you if you’re building and running only from one IDE or the other, but it can be problematic if: 1. You want to be able to build and run the same output from both Xcode and Android Studio, moving back and forth between the two. 2. You’re using an Xcode Workspace to iterate on local copies of SwiftPM dependencies alongside your app. Android Studio won’t see your Workspace copies, and instead will always use the dependency sources defined in `Package.swift`. If you’d like to share the same build location, you can do so by pointing Android Studio at Xcode’s `DerivedData` location for your project. Edit your project’s `Android/settings.gradle.kts` file to un-comment the line setting the `BUILT_PRODUCTS_DIR` system property, and specify the path to your project’s `Debug-iphonesimulator` folder. The result should look something like: pluginManagement { // Initialize the Skip plugin folder and perform a pre-build for non-Xcode builds val pluginPath = File.createTempFile("skip-plugin-path", ".tmp") // overriding outputs for an Android IDE can be done by un-commenting and setting the Xcode path: System.setProperty("BUILT_PRODUCTS_DIR", "${System.getProperty("user.home")}/Library/Developer/Xcode/DerivedData/MySkipProject-aqywrhrzhkbvfseiqgxuufbdwdft/Build/Products/Debug-iphonesimulator") … ### Dual-Platform Apps [Section titled “Dual-Platform Apps”](https://skip.dev/docs/platformcustomization/#dual-platform-apps) To open your dual-platform Xcode app in Android Studio: 1. Control-click the `Android/settings.gradle.kts` file and select `Open with External Editor` from the resulting context menu. 2. In Android Studio, select `File → Sync Project with Gradle Files` ![Open in Android Studio](https://assets.skip.dev/images/transpiled-android-studio.png) Once your app is open in Android Studio, you can run and debug it from there. Keep in mind that while you can iterate on your app’s custom [Kotlin files](https://skip.dev/docs/platformcustomization/#kotlin-files) with Android Studio, any edits you make to transpiled files will get overwritten the next time you update the source Swift. If you’d like to update your Swift, make the desired edits in Xcode, then rebuild in Android Studio. If you’ve pointed Android Studio at `DerivedData` as described in the previous section, you can choose to rebuild from Xcode as well. #### Unit Testing [Section titled “Unit Testing”](https://skip.dev/docs/platformcustomization/#unit-testing) To run your unit tests in Android Studio, first run them in Xcode so that they get transpiled. Then find your `Test` module’s output folder within `SkipStone/plugins`, and use `Open with External Editor` on its `settings.gradle.kts` file. Running your unit tests in Android Studio will allow you to use Android’s native debugging tools to debug your tests as well. ![Open in Android Studio](https://assets.skip.dev/images/transpiled-tests-android-studio.png) ### Separate iOS and Android Apps [Section titled “Separate iOS and Android Apps”](https://skip.dev/docs/platformcustomization/#separate-ios-and-android-apps) If you’ve created separate iOS and Android apps that share dual-platform frameworks, running your Android app in Android Studio is the same as running any other Android app. See the [Project Types](https://skip.dev/docs/project-types/#separate-apps) guide for tips on integrating dual-platform frameworks into your Android development workflow. ### Frameworks [Section titled “Frameworks”](https://skip.dev/docs/platformcustomization/#frameworks) To open your dual-platform framework in Android Studio: 1. Make sure that you have built your project in Xcode. 2. Follow the instructions to [create the SkipLink Xcode group](https://skip.dev/docs/debugging/#skiplink) . 3. Control-click the `Android/settings.gradle.kts` file and select `Open with External Editor` from the resulting context menu. To run your framework unit tests in Android Studio, first run them in Xcode so that they get transpiled. They will then be available in the gradle project. Running your unit tests in Android Studio will allow you to use Android’s native debugging tools to debug your tests as well. --- # Bridging Reference | Skip [Skip to content](https://skip.dev/docs/bridging/#_top) Bridging Reference ================== Skip’s [documentation](https://skip.dev/docs/modes/#bridging) describes Skip’s technology for bridging between compiled Swift and transpiled Swift or Kotlin and Java. This reference details the Swift language features and types that can be bridged. Bridging capabilities are symmetrical unless otherwise noted. That is, if something is marked as bridgeable, then you can use it whether you are bridging from native Swift to Kotlin/Java, or from transpiled Kotlin/Java to native Swift. Language Features ----------------- [Section titled “Language Features”](https://skip.dev/docs/bridging/#language-features) The following table details Skip’s support for bridging various Swift language features. A ✓ indicates that a feature is fully or very strongly supported. A ~ indicates that a feature is partially supported. And a ✕ indicates that a feature is not supported, or is only weakly supported. Future releases may address some unsupported language features, but others reflect deep incompatibilities between the Swift and Kotlin languages and runtimes. * ✓ Classes * ✓ Inheritance - up to 4 levels * ✓ Structs - see the [Mutable Structs](https://skip.dev/docs/bridging/#structs) topic below * ✓ Constructor synthesis * ✓ `Equatable` synthesis * ✓ `Hashable` synthesis * ✓ Protocols * ✓ Inheritance * ✓ Property requirements * ✓ Function requirements * ✕ Constructor requirements * ✕ Static requirements * ✓ Enums * ✓ Enums without associated values * ✓ Enums with associated values * ✕ Mutating properties and functions * ✓ Nested types * ~ Extensions * ✓ Concrete type extensions * ~ Protocol extensions * ✓ Extending a type within the current module * ~ Extending a type in another module (Kotlin to Swift only) * ~ Generic types - see the [Generics](https://skip.dev/docs/bridging/#generics) topic below * ✓ Tuples - up to 5 elements * ✕ Not supported as collection elements or closure parameters * ✓ Typealiases * ✓ Properties * ✓ Globals * ✓ Members * ✓ `let` * ✓ `var` * ✓ Static properties * ✓ Stored properties * ✓ Computed properties * ✓ Throwing properties * ✕ Lazy properties * ✓ Functions * ✓ Globals * ✓ Members * ✓ Overloading on types * ✓ Overloading on param labels * ✕ Overloading on return type * ✓ Static functions * ~ Generic functions - see the [Generics](https://skip.dev/docs/bridging/#generics) topic below * ✓ Throwing functions * ✓ Default parameter values * ✕ `inout` parameters * ✕ Variadic parameters * ✕ `@autoclosure` parameters * ✕ Parameter packs * ✓ Constructors * ✕ Optional constructors * ✓ Deconstructors * ✓ Closures - up to 5 parameters * ✕ Not supported as collection elements or parameters to other closures * ✓ Errors - see the [Errors](https://skip.dev/docs/bridging/#errors) topic below * ~ Concurrency * ✓ Async functions * ✓ Async properties * ✓ Async closures * ✓ `@MainActor` * ✓ Custom actors * Non-private mutable properties not supported. Use functions to mutate state * ~ Operators * ✓ Custom `Equatable` with `==` * ✓ Custom `Hashable` with `hash(into:)` * ✓ Custom `Comparable` with `<` * ✕ Custom subscript operators * ✕ `callAsFunction` support * ✕ Other custom operators * ✕ Key paths Builtin Types ------------- [Section titled “Builtin Types”](https://skip.dev/docs/bridging/#builtin-types) The following table details Skip’s support for bridging builtin Swift standard library types. * ✓ `Any` * ✓ `AnyHashable` * ✓ `AnyObject` * ✓ `Bool` * ✕ `Character` * ✓ Numeric types * ~ `Int` and `UInt` are 32 bit on JVM * ✓ `String` * ✓ Optionals * ✕ Compound types (e.g. `A & B`) * ✓ Fully-qualified Kotlin/Java types - translate to `AnyDynamicObject` * ✓ `Array` - translates to `kotlin.collections.List` in `kotlincompat` mode * ✓ `AsyncStream` - translates to `kotlinx.coroutines.flow.Flow` in `kotlincompat` mode * ✓ `AsyncThrowingStream<*, Error>` - translates to `kotlinx.coroutines.flow.Flow` in `kotlincompat` mode * ✓ `Data` - translates to `[byte]` in `kotlincompat` mode * ✓ `Date` - translates to `java.util.Date` in `kotlincompat` mode * ✓ `Dictionary` - translates to `kotlin.collections.Map` in `kotlincompat` mode * ✓ `Error` - translates to `kotlin.Throwable` in `kotlincompat` mode * ✓ `NSNumber` - translates to `java.lang.Number` * ✕ `OptionSet` * ✓ `Result` - translates to `kotlin.Pair` in `kotlincompat` mode * ✓ `Set` - translates to `kotlin.collections.Set` in `kotlincompat` mode * ✓ `2-Tuple` - translates to `kotlin.Pair` in `kotlincompat` mode * ✓ `3-Tuple` - translates to `kotlin.Triple` in `kotlincompat` mode * ✓ `URL` - translates to `java.net.URI` in `kotlincompat` mode * ✓ `UUID` - translates to `java.util.UUID` in `kotlincompat` mode Special Topics -------------- [Section titled “Special Topics”](https://skip.dev/docs/bridging/#special-topics) ### Equality [Section titled “Equality”](https://skip.dev/docs/bridging/#equality) **Do not** rely on object identity and `===` comparisons of bridged instances. The same object may get wrapped by multiple bridging instances. Because Kotlin/Java types have built-in `equals` and `hashCode` functions that default to using identity, Skip’s Kotlin projections of your native types will implement `equals` and `hashCode` so that wrappers around the same native instance will compare equal and have the same hash. Skip supports bridging of `Equatable`, `Hashable`, and `Comparable` types, so you should implement these protocols for any additional needs. ### Errors [Section titled “Errors”](https://skip.dev/docs/bridging/#errors) Skip supports bridging your custom `Error` types as well as functions that may throw errors. Keep in mind the following: * Any `Error`\-conforming type will extend `Exception` when translated to Kotlin, so your bridged Swift `Error` types cannot be subclasses. * You can bridge functions that throw `Error` types that are not themselves bridged. You must treat these throwing functions as if they might throw any error at all: use general `catch` blocks that do not rely on catching a specific type of error. ### Generics [Section titled “Generics”](https://skip.dev/docs/bridging/#generics) Kotlin and Swift have very different generic implementation strategies. Swift generics are built deep into the language as first-class citizens of its type system. Kotlin generics, on the other hand, don’t exist at the JVM level and are only present at compile time. Additionally, the Java Native Interface (JNI) API that allows Swift and Kotlin to communicate is based on the C language, which has no generics at all. This makes it impossible to preserve generic information across the JNI boundary. All of these factors conspire to limit the bridging of generic functions and types. Whether bridging from compiled Swift to Kotlin or vice versa, the following restrictions apply: * You cannot bridge a subclass of a generic type. * Static members of generic types are limited. Skip can only support static members that either don’t use the defining type’s generics or that can be converted into a generic function that is defined independently of the defining type’s generics. * Generic specializations by type extensions (e.g. `extension C where T: Equatable`) cannot be bridged. * Inner types on generic outer types are not supported. * Kotlin does not allow constructor functions to use generics other than those of the defining type. * Kotlin does not allow `typealiases` to include generic constraints (e.g. `where T: Equatable`). Bridging from transpiled Kotlin to compiled Swift has this additional limitation: * If a transpiled Kotlin generic type is returned from a property or function whose exact type is unknown (i.e. is of type `Any` or is a protocol type, etc), the exact typing is lost in the transfer to Swift. For example if an instance of `C` is read from a bridged Kotlin `Any` property, the Swift side will receive an instance of `C`, regardless of `T`’s type on the Kotlin side. Similarly, `C` will bridge from an `Any` field as `C

`. This does not apply when bridging the other direction, from compiled Swift to Kotlin. That direction, however, has the following restrictions instead: * You cannot construct instances of a bridged Swift generic type from Kotlin. So your Swift code can create and expose generic types to Kotlin, but your Kotlin code has no way to construct Swift generic instances. You must comment any public constructors on Swift generic types with `// SKIP @nobridge` so that they are not bridged to Kotlin. * Kotlin also cannot access static members of Swift generic types. You must comment public static members with `// SKIP @nobridge` as well. * Global generic functions do not retain generic type information when called from Kotlin. So when called from Kotlin, a global Swift function `f(p: T)` will always be invoked as if `T` is of type `Any`. Similarly, `f(p: P)` will always be invoked as if `T` is of type `P`. ### Mutable Structs [Section titled “Mutable Structs”](https://skip.dev/docs/bridging/#structs) We only recommend bridging native mutable structs if their projection will be consumed by transpiled Swift, where Skip can maintain value semantics on the Kotlin side. If you plan on using your native types from pure Kotlin or Java code, stick to classes, which mirror Kotlin/Java’s reference semantics. We also recommend against bridging transpiled mutable structs to native Swift, as they require a significant amount of object copying on the JVM side. --- # Getting Help | Skip [Skip to content](https://skip.dev/docs/help/#_top) Getting Help ============ The best way to get help and connect with fellow Skip users is to join the community [Slack](https://skip.dev/slack/) , where you can find real-time support and the community expertise to help you succeed with your project. You can also use the Skip [discussion forums ↗](http://forums.skip.dev/) to send feedback, ask questions, or search for solutions to issues. For bug reports, use the [issue tracker ↗](https://source.skip.dev/skip/issues) . Please include the output of the `skip checkup` command in any communication related to technical issues. Troubleshooting Common Issues ----------------------------- [Section titled “Troubleshooting Common Issues”](https://skip.dev/docs/help/#troubleshooting) Skip’s architecture relies on recent advances in the plugin system used by Xcode 15 and Swift Package Manager 5.9. When unexpected issues arise, often the best first step is to clean your Xcode build (`Product` → `Clean Build Folder`) and reset packages (`File` → `Packages` → `Reset Package Caches`). Restarting Xcode is sometimes warranted, and trashing the local `DerivedData` folder as well as your app directory’s `.build` folder might even be needed. Specific known error conditions are listed below. Search the [documentation](https://skip.dev/docs) , [issues ↗](https://source.skip.dev/skip/issues) , and [discussions ↗](http://forums.skip.dev/) for more information and to report problems. --- # Dependencies | Skip [Skip to content](https://skip.dev/docs/dependencies/#_top) Dependencies ============ Skip project dependencies come in four forms: 1. A dependency on a dual-platform Skip package. 2. A dependency on a pure Swift Package Manager package. 3. A dependency on a Kotlin or Java package library used only by your Android code. 4. A dependency on common functionality implemented by separate iOS and Android libraries. Read on for a discussion of each use case. Adding Skip Package Dependencies -------------------------------- [Section titled “Adding Skip Package Dependencies”](https://skip.dev/docs/dependencies/#skip) Use `Package.swift` to add a dependency on an external Skip SwiftPM package, just as you would for a standard Swift package. Skip will detect the presence of the dependency’s `Skip/skip.yml` file and add a corresponding Gradle dependency on that project. In other words, adding a SwiftPM dependency on the Swift side will automatically create a Gradle dependency on the Android side. So when your project depends only on other Skip projects, you generally don’t need to perform any additional customization in your own `skip.yml` file. * * * Adding Pure Swift Package Manager Dependencies ---------------------------------------------- [Section titled “Adding Pure Swift Package Manager Dependencies”](https://skip.dev/docs/dependencies/#ios) Thousands of pure SwiftPM packages can build for both iOS and Android. We track known Android-compatible packages at the [Swift Package Index ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) . Add these dependencies to your `Package.swift` in the standard manner. To exclude an iOS-only SwiftPM package from your Android build’s dependencies, use `Package.swift`’s conditional dependency syntax: let package = Package( ... .target(name: "MyPackage", dependencies: [ .product(name: "Lottie", package: "lottie-ios", condition: .when(platforms: [.macOS, .iOS])) ], plugins: [.plugin(name: "skipstone", package: "skip")]), ...) * * * Adding Java/Kotlin Dependencies ------------------------------- [Section titled “Adding Java/Kotlin Dependencies”](https://skip.dev/docs/dependencies/#android) Sometimes the Android side of a project might need to utilize external Java or Kotlin libraries that aren’t provided by the Skip frameworks. An example of this would be a Skip framework that provides a unified dual-platform API, but whose underlying implementation depends on an external project artifact. As a concrete example, the [SkipScript](https://skip.dev/docs/modules/skip-script/) framework relies on the built-in JavaScriptCore framework on iOS (and thus has no external dependencies on the iOS side), but on Android it depends on external `jsc-android` libraries that contain the script engine and other supporting functionality. In this case, you can use `skip.yml` to add dependencies to the Gradle side of the Skip project. We discuss `skip.yml` and Gradle [here](https://skip.dev/docs/gradle) . For example, here is the `skip-script/Sources/SkipScript/Skip/skip.yml` file containing parameters that specify its dependencies on external `jsc` libraries. These will be aggregated and included in the resulting `build.gradle.kts` file: # the blocks to add to build.gradle.ktsbuild: contents: - block: 'dependencies' contents: - 'implementation("org.webkit:android-jsc-cppruntime:r245459@aar")' - 'implementation("org.webkit:android-jsc:r245459@aar")' # the blocks to add to settings.gradle.ktssettings: contents: - block: 'dependencyResolutionManagement' contents: - block: 'repositories' contents: - 'maven("https://github.com/jectivex/jsc-android/raw/main/releases")' In many cases, dependencies will be available from the built-in `mavenCentral` and `google` repositories. When a dependency is provided by one of these built-in repositories, only the `build` block needs to be added with a list of dependencies to add to the project, and the `settings` block can be excluded. The resulting `build.gradle.kts` file contains the aggregated blocks from this module and all the dependent modules, and will look something like this: // build.gradle.kts generated by Skip for the SkipScript module.// This file is generated by the Skip transpiler plugin and is// derived from the aggregate Skip/skip.yml files from the SwiftPM project.// Edits made directly to this file will be overwritten.dependencies { testImplementation("org.json:json:20180813") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") testImplementation("org.robolectric:robolectric:4.10.3") androidTestImplementation("androidx.test:runner:1.5.2") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") androidTestImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("androidx.test:core:1.5.0") androidTestImplementation("androidx.test:core:1.5.0") testImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5") implementation("net.java.dev.jna:jna:5.13.0@aar") testImplementation("net.java.dev.jna:jna:5.13.0") implementation("org.webkit:android-jsc-cppruntime:r245459@aar") implementation("org.webkit:android-jsc:r245459@aar") implementation(project(":SkipFoundation")) implementation(project(":SkipLib")) testImplementation(project(":SkipUnit")) androidTestImplementation(project(":SkipUnit")) implementation(project(":SkipFFI"))} plugins { kotlin("android") version "1.9.0" id("com.android.library") version "8.1.0"} kotlin { jvmToolchain(17)} android { namespace = group as String compileSdk = 34 defaultConfig { minSdk = 29 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" } testOptions { unitTests { isIncludeAndroidResources = true } }} tasks.withType() { kotlinOptions { suppressWarnings = true }} tasks.withType().configureEach { systemProperties.put("robolectric.logging", "stdout") systemProperties.put("robolectric.graphicsMode", "NATIVE") testLogging { this.showStandardStreams = true }} And in this case, since the `settings` block was used, the resulting `settings.gradle.kts` file will include the customized repositories: // This is the top-level Gradle settings for the project.// The module dependencies it contains may be symbolic links to peer folders.//// This file is generated by the Skip transpiler plugin and is// derived from the aggregate Skip/skip.yml files from the SwiftPM project.// Edits made directly to this file will be overwritten.//// Open with External Editor to build and run this project in an IDE.//pluginManagement { repositories { gradlePluginPortal() mavenCentral() google() }} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { mavenCentral() google() maven("https://github.com/jectivex/jsc-android/raw/main/releases") }} rootProject.name = "skip.script"include(":SkipScript")include(":SkipFoundation")include(":SkipLib")include(":SkipUnit")include(":SkipFFI") The generated build files will be overwritten the next time the Skip plugin is run, and so the generated output shouldn’t be edited directly. Instead, any changes to the module’s `Skip/skip.yml` properties will result in the `build.gradle.kts` and `settings.gradle.kts` being regenerated and included as part of the build. To use an Android package in [transpiled](https://skip.dev/docs/modes/#transpiled) Swift, import it as usual: #if SKIPimport com.xyz.SomeAndroidType... use Android-only API ...#endif To use a Kotlin or Java package in _compiled_ Swift, you have to use [bridging](https://skip.dev/docs/modes/#bridging) . Dependencies on Separate iOS and Android Libraries -------------------------------------------------- [Section titled “Dependencies on Separate iOS and Android Libraries”](https://skip.dev/docs/dependencies/#implementation) You often want to use functionality that has existing libraries for both iOS and Android, but does not yet have a cross-platform library available. Most popular third-party frameworks publish both iOS and Android versions, and a plethora of other common functionality is available from independent developers on both platforms. To access this functionality, you have several options: 1. [Conditionally](https://skip.dev/docs/platformcustomization/#compiler-directives) import and call the iOS or the Android library API at each usage site. This is only viable if you don’t use the functionality in many places. 2. Write a simple common wrapper API within your application that delegates to the appropriate platform API internally. 3. [Create your own](https://skip.dev/docs/project-types/#framework_development) Skip dual-platform library. The advantage of this approach is that you can reuse your library in other projects, and [contribute](https://skip.dev/docs/contributing/) it to the open-source community for other Skip users to use and improve. Regardless of which option you choose, the implementation strategy is the same: 1. If you’re creating a library, follow the [Getting Started instructions](https://skip.dev/docs/project-types/#framework_development) for new dual-platform frameworks. You may be integrating closely with the Android Kotlin or Java dependency, so consider creating a Skip Lite [transpiled](https://skip.dev/docs/modes/#transpiled) module whose API you [bridge](https://skip.dev/docs/modes/#bridging) to native Swift. 2. Add a [Swift Package dependency](https://skip.dev/docs/dependencies/#ios) on the iOS library, and add a [Java/Kotlin dependency](https://skip.dev/docs/dependencies/#android) on the Android library you want to use. 3. If you’re writing a common wrapper class or library, write the wrapper API. 4. Within the implementation code, use Skip [compiler directives](https://skip.dev/docs/platformcustomization/#compiler-directives) to conditionally import and call the API for the appropriate library. Use the techniques in [Cross-Platform Topics](https://skip.dev/docs/platformcustomization) to call Kotlin and Java APIs. The resulting implementation pattern looks something like this: #if !os(Android)import SomeIOSLibrary#elseimport com.xyz.someandroidlibrary.__#endif public struct MyCommonAPI { #if !os(Android) private let libraryInstance = SomeIOSLibraryType() #else private let libraryInstance = SomeAndroidLibraryType() #endif public func myCommonAPIFunc() -> String { #if !os(Android) return libraryInstance.someIOSLibraryFunction() #else return libraryInstance.someAndroidLibraryFunction() #endif } ...} You can see this pattern clearly in the source of Skip frameworks like [SkipKeychain ↗](https://github.com/skiptools/skip-keychain/blob/main/Sources/SkipKeychain/SkipKeychain.swift) , which is a great example to learn from. When writing a dual-platform library that vends the functionality of a standard or well-known iOS framework, consider exactly mirroring that framework’s iOS API, just as Skip mirrors Foundation and other frameworks for [transpiled](https://skip.dev/docs/modes/#transpiled) code. This follows Skip’s philosophy of _transparent adoption_. Then rather than writing to your custom API, users can write to the exact iOS API. At the usage site, only the import will be different: #if !os(Android)import StandardIOSLibrary#elseimport SkipStandardIOSLibrary#endif // Use iOS library API... This pattern also means you don’t have to write any docs, nor do you have to implement any wrapper code for iOS! The iOS side of Skip apps will build directly against the existing iOS library. Skip’s [FireStore support source ↗](https://github.com/skiptools/skip-firebase/blob/main/Sources/SkipFirebaseFirestore/SkipFirebaseFirestore.swift) is a good example of this. And again, if you create a dual-platform library, please consider [contributing](https://skip.dev/docs/contributing/) it to the open-source Skip user community! --- # SkipFuseUI | Skip [Skip to content](https://skip.dev/docs/modules/skip-fuse-ui/#_top) SkipFuseUI ========== Bridging between SwiftUI and Jetpack Compose for [Skip Fuse](https://skip.dev/docs/modes/) apps. SkipFuseUI uses [SkipUI](https://skip.dev/docs/modules/skip-ui/) under the hood to adapt the SwiftUI APIs to Compose. See its documentation for information about SwiftUI-on-Android. --- # Update Guide | Skip [Skip to content](https://skip.dev/docs/update-guide/#_top) Update Guide ============ Skip 0.x to Skip 1.0 Migration ------------------------------ [Section titled “Skip 0.x to Skip 1.0 Migration”](https://skip.dev/docs/update-guide/#skip1_0migration) Both Skip and Swift Package Manager use [semantic versioning ↗](https://semver.org/) . While semantic versioning has [many benefits ↗](https://semver.org/#why-use-semantic-versioning) , it also means that the jump from Skip 0.x to Skip 1.x is considered a potentially “breaking change”, and therefore it requires manual upgrade steps. To migrate your Skip 0.x library or app to Skip 1.x, please do the following: 1. In your `Package.swift` file, update any dependencies on the following Skip packages from `from: 0.x.y` to `from: 1.0.0`: * `skip` * `skip-unit` * `skip-lib` * `skip-foundation` * `skip-model` * `skip-ui` For example, here is the updated `Package.swift` for our `HelloSkip` sample app: import PackageDescription ... let package = Package( name: "skipapp-hello", ... dependencies: [ .package(url: "https://source.skip.tools/skip.git", from: "1.0.0"), .package(url: "https://source.skip.tools/skip-ui.git", from: "1.0.0") ], ...) 2. Open your project in Xcode and run `File → Packages → Update to Latest Package Versions` 3. Delete the `.build/` directory and `Package.resolved` file from your project folder. Please reach out to [support](https://skip.dev/docs/help/) if you encounter any problems! Skip 1.0 to Skip 1.1 Migration ------------------------------ [Section titled “Skip 1.0 to Skip 1.1 Migration”](https://skip.dev/docs/update-guide/#skip_1_0_to_1_1_migration) ### Required Changes to Android/settings.gradle.kts [Section titled “Required Changes to Android/settings.gradle.kts”](https://skip.dev/docs/update-guide/#required-changes-to-androidsettingsgradlekts) The `commandLine` call in `Android/settings.gradle.kts` needs to invoke `xcrun swift build …`, rather than `swift build` as it had done previously. For a complete example, you can reference the build file for the `skip-hello` app: [https://source.skip.dev/skipapp-hello/blob/main/Android/settings.gradle.kts ↗](https://source.skip.dev/skipapp-hello/blob/main/Android/settings.gradle.kts) . // This gradle project is part of a conventional Skip app project.// It invokes the shared build skip plugin logic, which included as part of the skip-unit buildSrc// When built from Android Studio, it uses the BUILT_PRODUCTS_DIR folder to share the same build outputs as Xcode, otherwise it uses SwiftPM's .build/ folderpluginManagement { // local override of BUILT_PRODUCTS_DIR if (System.getenv("BUILT_PRODUCTS_DIR") == null) { //System.setProperty("BUILT_PRODUCTS_DIR", "${System.getProperty("user.home")}/Library/Developer/Xcode/DerivedData/MySkipProject-aqywrhrzhkbvfseiqgxuufbdwdft/Build/Products/Debug-iphonesimulator") } // the source for the plugin is linked as part of the SkipUnit transpilation val skipOutput = System.getenv("BUILT_PRODUCTS_DIR") ?: System.getProperty("BUILT_PRODUCTS_DIR") val outputExt = if (skipOutput != null) ".output" else "" // Xcode saves output in package-name.output; SPM has no suffix val skipOutputs: File = if (skipOutput != null) { // BUILT_PRODUCTS_DIR is set when building from Xcode, in which case we will use Xcode's DerivedData plugin output file(skipOutput).resolve("../../../SourcePackages/plugins/") } else { exec { // create transpiled Kotlin and generate Gradle projects from SwiftPM modules commandLine("sh", "-c", "xcrun swift build --triple arm64-apple-ios --sdk $(xcrun --sdk iphoneos --show-sdk-path)") workingDir = file("..") } // SPM output folder is a peer of the parent Package.swift rootDir.resolve("../.build/plugins/outputs/") } // load the Skip plugin (part of the skip-unit project), which handles configuring the Android project // because this path is a symlink, we need to use the canonical path or gradle will mis-interpret it as a different build source var pluginSource = skipOutputs.resolve("skip-unit${outputExt}/SkipUnit/skipstone/buildSrc/").canonicalFile if (!pluginSource.isDirectory) { // check new SwiftPM6 plugin "destination" folder for command-line builds pluginSource = skipOutputs.resolve("skip-unit${outputExt}/SkipUnit/destination/skipstone/buildSrc/").canonicalFile } if (!pluginSource.isDirectory) { throw GradleException("Missing expected Skip output folder: ${pluginSource}. Run `swift build` in the root folder to create, or specify Xcode environment BUILT_PRODUCTS_DIR.") } includeBuild(pluginSource.path) { name = "skip-plugins" }} plugins { id("skip-plugin") apply true} Kotlin 1.x to Kotlin 2 Migration -------------------------------- [Section titled “Kotlin 1.x to Kotlin 2 Migration”](https://skip.dev/docs/update-guide/#kotlin-1x-to-kotlin-2-migration) Kotlin 2 introduces many improvements to the Kotlin language, as well as integration of the Compose compiler into the Kotlin compiler itself[1](https://skip.dev/docs/update-guide/#user-content-fn-1) . Skip 0.9.2 adopts Kotlin 2 as the target language, which involves some changes to the Gradle project that Skip creates[2](https://skip.dev/docs/update-guide/#user-content-fn-2) . Internally, this involves changing some of the dependencies that Skip adds to the Android Gradle project. These changes are, for the most part, transparent to Skip framework projects. But Skip app projects that were created before Skip 0.9.2 will need to make a couple of changes in order to work with the new Compose compiler. To upgrade a Skip app project, open the `Android/app/build.gradle.kts` file in any text editor (e.g., Xcode, Android Studio, or TextEdit), and make the following changes: 1. Add `alias(libs.plugins.kotlin.compose)` to the `plugins { }` block 2. Remove the `kotlinCompilerExtensionVersion = libs.versions.kotlin.compose.compiler.extension.get()` from the `composeOptions { }` block The `skip-build-plugin`, which is included in all Skip app projects created with `skip init`, will provide helpful error messages if it detects that your project needs to be updated: ![Xcode error messages for Skip project that needs upgrading](https://assets.skip.dev/screens/kotlin2-migration-xcode.png){: style=“width: 100%;”} {: style=“text-align: center; margin: auto;“} ### Required Changes to Android/app/build.gradle.kts [Section titled “Required Changes to Android/app/build.gradle.kts”](https://skip.dev/docs/update-guide/#required-changes-to-androidappbuildgradlekts) A condensed comparison of the changes needed to be made to the `Android/app/build.gradle.kts` follows. For a complete example, you can reference the build file for the `skip-hello` app: [https://source.skip.dev/skipapp-hello/blob/main/Android/app/build.gradle.kts ↗](https://source.skip.dev/skipapp-hello/blob/main/Android/app/build.gradle.kts) . Before: plugins { alias(libs.plugins.kotlin.android) alias(libs.plugins.android.application) id("skip-build-plugin")} android { namespace = group as String compileSdk = libs.versions.android.sdk.compile.get().toInt() compileOptions { sourceCompatibility = JavaVersion.toVersion(libs.versions.jvm.get()) targetCompatibility = JavaVersion.toVersion(libs.versions.jvm.get()) } defaultConfig { minSdk = libs.versions.android.sdk.min.get().toInt() targetSdk = libs.versions.android.sdk.compile.get().toInt() } composeOptions { kotlinCompilerExtensionVersion = libs.versions.kotlin.compose.compiler.extension.get() }} After: plugins { alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) // this line added alias(libs.plugins.android.application) id("skip-build-plugin")} android { namespace = group as String compileSdk = libs.versions.android.sdk.compile.get().toInt() compileOptions { sourceCompatibility = JavaVersion.toVersion(libs.versions.jvm.get()) targetCompatibility = JavaVersion.toVersion(libs.versions.jvm.get()) } kotlinOptions { jvmTarget = libs.versions.jvm.get().toString() // this line added } defaultConfig { minSdk = libs.versions.android.sdk.min.get().toInt() targetSdk = libs.versions.android.sdk.compile.get().toInt() } composeOptions { // kotlinCompilerExtensionVersion = libs.versions.kotlin.compose.compiler.extension.get() // this line removed }} ### Troubleshooting [Section titled “Troubleshooting”](https://skip.dev/docs/update-guide/#troubleshooting) #### Error: Inconsistent JVM-target compatibility detected for tasks ‘compileDebugJavaWithJavac’ (17) and ‘compileDebugKotlin’ (22). [Section titled “Error: Inconsistent JVM-target compatibility detected for tasks ‘compileDebugJavaWithJavac’ (17) and ‘compileDebugKotlin’ (22).”](https://skip.dev/docs/update-guide/#error-inconsistent-jvm-target-compatibility-detected-for-tasks-compiledebugjavawithjavac-17-and-compiledebugkotlin-22) The same JVM target must be used across the entire Gradle project, which includes both your own app as well as any and all Skip dependencies. This was done automatically using the `compileOptions` block’s `sourceCompatibility` and `targetCompatibility` properties, but now it also needs to be added to the `kotlinOptions` block using the `jvmTarget` property. Edit your `build.gradle.kts` file like so: android { compileOptions { sourceCompatibility = JavaVersion.toVersion(libs.versions.jvm.get()) targetCompatibility = JavaVersion.toVersion(libs.versions.jvm.get()) } kotlinOptions { jvmTarget = libs.versions.jvm.get().toString() // this line added }} #### Error: Exception during IR lowering [Section titled “Error: Exception during IR lowering”](https://skip.dev/docs/update-guide/#error-exception-during-ir-lowering) This is the error that occurs when the Compose compiler plugin has not been added to the apps’s `Android/app/build.gradle.kts` file. Make sure you’ve followed the instructions in this migration guide to fix the error. A condensed representation of this error is as follows: e: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering> Task :app:compileDebugKotlin FAILEDFile being compiled: /opt/src/github/skiptools/skipapp-hello/Android/app/src/main/kotlin/hello/skip/Main.ktThe root cause java.lang.RuntimeException was thrown at: org.jetbrains.kotlin.backend.jvm.codegen.FunctionCodegen.generate(FunctionCodegen.kt:50) at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253) at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236) at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65) …Caused by: java.lang.RuntimeException: Exception while generating code for:FUN LOCAL_FUNCTION_FOR_LAMBDA name:PresentationRootView$lambda$1 visibility:private modality:FINAL <> (ctx:skip.ui.ComposeContext) returnType:kotlin.Unit? VALUE_PARAMETER name:ctx index:0 type:skip.ui.ComposeContext BLOCK_BODY VAR name:contentContext type:skip.ui.ComposeContext [val] Footnotes --------- [Section titled “Footnotes”](https://skip.dev/docs/update-guide/#footnote-label) 1. Jetpack Compose compiler moving to the Kotlin repository: [https://android-developers.googleblog.com/2024/04/jetpack-compose-compiler-moving-to-kotlin-repository.html ↗](https://android-developers.googleblog.com/2024/04/jetpack-compose-compiler-moving-to-kotlin-repository.html) [↩](https://skip.dev/docs/update-guide/#user-content-fnref-1) 2. Migrating a Jetpack Compose project: [https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compiler.html#migrating-a-jetpack-compose-project ↗](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compiler.html#migrating-a-jetpack-compose-project) [↩](https://skip.dev/docs/update-guide/#user-content-fnref-2) --- # SkipFuse | Skip [Skip to content](https://skip.dev/docs/modules/skip-fuse/#_top) SkipFuse ======== SkipFuse helps fuse the Swift and Android worlds for [Skip Fuse](https://skip.dev/docs/modes/#native) modules and apps. It is an umbrella framework that vends cross-platform functionality. For example, SkipFuse: * Vends OSLog APIs on Android so that your OSLog messages appear in Android’s Logcat logging. * Transparently integrates your Swift `@Observables` into Jetpack Compose, so that they can transparently power Compose UI. * Includes the `AnyDynamicObject` type for invoking Kotlin/Java API from compiled Swift with zero setup. Skip will be enhancing SkipFuse over time to integrate many additional Swift foundational APIs with Android. See the Skip [documentation](https://skip.dev/docs/) for details. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-fuse/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-fuse.git", from: "1.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipFuse", package: "skip-fuse") ]) ]) --- # SkipBridge | Skip [Skip to content](https://skip.dev/docs/modules/skip-bridge/#_top) SkipBridge ========== Bridging between Swift and Kotlin/Java for Skip Fuse apps. Documentation can be found at [https://skip.dev/docs/modes/](https://skip.dev/docs/bridging/) . --- # Project Structure | Skip [Skip to content](https://skip.dev/docs/project-types/#_top) Project Structure ================= Conventional Skip Projects -------------------------- [Section titled “Conventional Skip Projects”](https://skip.dev/docs/project-types/#conventional-skip-projects) A Skip app project is one that is created with `skip init` or `skip create`. At its essence, it is a standard [Swift Package Manager project ↗](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/introducingpackages) , with a top-level `Package.swift` and directories for `Sources/` and `Tests/`. Skip augments this structure by adding shared `Skip.env` metadata and `Darwin/` and `Android/` sub-folders that house the build systems for their respective platforms along with platform-specific metadata and deployment information. A Skip project can be built locally with `swift build` and tested with `swift test`. In addition, Skip-specific commands like `skip export` can be used to build the platform-specific binaries (`.ipa` for iOS and `.apk` for Android) for distribution through the various app stores. ### Samples [Section titled “Samples”](https://skip.dev/docs/project-types/#samples) There are four standard examples that can be perused that illustrate the structure of a conventional Skip project: 1. [HelloSkip](https://skip.dev/docs/samples/skipapp-hello/) : A basic example of a transpiled Skip Lite app project. 2. [HowdySkip](https://skip.dev/docs/samples/skipapp-howdy/) : A basic example of a compiled Skip Fuse app project. 3. [AhoySkipper](https://skip.dev/docs/samples/skipapp-ahoy/) : A multi-module Skip Fuse app project. 4. [HiyaSkip](https://skip.dev/docs/samples/skipapp-hiya/) : An example of a Skip app project that uses a compiled (Skip Fuse) UI module bridging to a transpiled (Skip Lite) model layer module. ### Filesystem Layout [Section titled “Filesystem Layout”](https://skip.dev/docs/project-types/#filesystem-layout) Consider an “Ahoy Skipper” Skip Fuse app with two modules and test cases that can be generated with the following `skip init` command (or the equivalent `skip create` interactive command): skip init --native-app --bridged --module-tests --show-tree --appid=ahoy.skipper ahoy-skipper AhoySkipper SkipperModel An abridged view of the filesystem layout of the app will look like this: * README.md * [Skip.env](https://skip.dev/docs/project-types/#Skip.env) App metadata (name, version, appid, etc.) * [Package.swift](https://skip.dev/docs/project-types/#Package.swift) Standard SwiftPM build file * [Project.xcworkspace](https://skip.dev/docs/project-types/#Project.xcworkspace) Xcode workspace container for Darwin project * DirectorySources * DirectoryAhoySkipper App module with SwiftUI source * [AhoySkipperApp.swift](https://skip.dev/docs/project-types/#App.swift) App entry point * [ContentView.swift](https://skip.dev/docs/project-types/#ContentView.swift) Top-level SwiftUI view * DirectoryResources * [Localizable.xcstrings](https://skip.dev/docs/project-types/#Localizable.xcstrings) Localized UI strings * [Module.xcassets](https://skip.dev/docs/project-types/#Module.xcassets) Images and other resources * DirectorySkip * [skip.yml](https://skip.dev/docs/project-types/#skip.yml) Skip module configuration * DirectorySkipperModel Model code used by `AhoySkipper` * [ViewModel.swift](https://skip.dev/docs/project-types/#ViewModel.swift) Observable model code * DirectoryResources * [Localizable.xcstrings](https://skip.dev/docs/project-types/#Localizable.xcstrings) Localized model strings * DirectorySkip * [skip.yml Skip](https://skip.dev/docs/project-types/#skip.yml) module configuration * DirectoryTests * DirectorySkipperModelTests * [SkipperModelTests.swift](https://skip.dev/docs/project-types/#SkipperModelTests.swift) XCUnit test cases * DirectoryResources * TestData.json Test data * DirectorySkip * [skip.yml](https://skip.dev/docs/project-types/#skip.yml) Skip module configuration * DirectoryAndroid * gradle.properties * [settings.gradle.kts](https://skip.dev/docs/project-types/#settings.gradle.kts) Top-level Gradle configuration * Directoryapp * [build.gradle.kts](https://skip.dev/docs/project-types/#build.gradle.kts) Gradle build file * [proguard-rules.pro](https://skip.dev/docs/project-types/#proguard-rules.pro) Proguard/R8 optimization configuration * Directorysrc * Directorymain * [AndroidManifest.xml](https://skip.dev/docs/project-types/#AndroidManifest.xml) Android app metadata * Directorykotlin * [Main.kt](https://skip.dev/docs/project-types/#Main.kt) Entry point for Android app * Directoryres * Directorymipmap-hdpi Android app icon * [ic\_launcher.png](https://skip.dev/docs/project-types/#Android/src/main/kotlin/res) * … * … * Directoryfastlane configuration for App Store * [Appfile](https://skip.dev/docs/project-types/#Android/fastlane) * Fastfile * Directorymetadata * Directoryandroid * Directoryen-US * title.txt * short\_description.txt * full\_description.txt * … * DirectoryDarwin * [AhoySkipper.xcconfig](https://skip.dev/docs/project-types/#ProjectName.xcconfig) Xcode build configuration * [AhoySkipper.xcodeproj](https://skip.dev/docs/project-types/#ProjectName.xcodeproj) Xcode project * [Info.plist](https://skip.dev/docs/project-types/#Info.plist) iOS app metadata * [Entitlements.plist](https://skip.dev/docs/project-types/#Entitlements.plist) iOS permissions * Directory[Assets.xcassets](https://skip.dev/docs/project-types/#Assets.xcassets) iOS app assets * DirectoryAppIcon.appiconset iOS app icon * [AppIcon.png](https://skip.dev/docs/project-types/#AppIcon.png) * … * DirectorySources * [Main.swift](https://skip.dev/docs/project-types/#Main.swift) Entry point for Darwin app * Directoryfastlane configuration for Play Store * [Appfile](https://skip.dev/docs/project-types/#Darwin/fastlane) * Deliverfile * Fastfile * Directorymetadata * app\_privacy\_details.json * rating.json * Directoryen-US * title.txt * subtitle.txt * description.txt * … #### `Package.swift` [Section titled “Package.swift”](https://skip.dev/docs/project-types/#Package.swift) This file is the manifest for the Swift Package Manager (SwiftPM). It defines the project’s name, products (libraries or executables), dependencies, and targets. In a Skip project, it also declares the `skipstone` plugin, which handles the translation of the iOS project into an equivalent Android project. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Package.swift ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Package.swift) * Reference: [Swift Package Manager - Package.swift Manifest ↗](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/introducingpackages) #### `Project.xcworkspace` [Section titled “Project.xcworkspace”](https://skip.dev/docs/project-types/#Project.xcworkspace) This file is an Xcode Workspace, a container used to group multiple Xcode projects and Swift packages together. In Skip, it allows you to manage the primary Darwin app project alongside the shared Swift modules in a single interface, ensuring that shared code changes are immediately reflected in the app build. The generated `Project.xcworkspace` will contain only the [`Darwin/ProjectName.xcodeproj`](https://skip.dev/docs/project-types/#ProjectName.xcodeproj) , but additional projects and frameworks can be added to it, as described in the [Contribution Guide](https://skip.dev/docs/contributing/#local-libraries) . * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Project.xcworkspace/contents.xcworkspacedata ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Project.xcworkspace/contents.xcworkspacedata) * Reference: [Xcode Help - Workspace Concepts ↗](https://developer.apple.com/documentation/xcode/managing-multiple-projects-and-their-dependencies) #### `Skip.env` [Section titled “Skip.env”](https://skip.dev/docs/project-types/#Skip.env) This is a shared configuration environment file for Skip projects. It contains key-value pairs for project-wide metadata, such as the `PRODUCT_NAME`, `MARKETING_VERSION`, and `PRODUCT_BUNDLE_IDENTIFIER`. These variables are used by both the Xcode build process and the Gradle build process to ensure consistency between the iOS and Android binaries. It is included by both the [`Darwin/ProjectName.xcconfig`](https://skip.dev/docs/project-types/#ProjectName.xcconfig) and [`Android/settings.gradle.kts`](https://skip.dev/docs/project-types/#settings.gradle.kts) files. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Skip.env ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Skip.env) * See also: [Configuration Metadata](https://skip.dev/docs/development-topics/#configuration-with-skipenv) #### `Sources//App.swift` [Section titled “Sources//App.swift”](https://skip.dev/docs/project-types/#App.swift) The shared app entry point, which launches and configures the app and holds the primary [`ContentView.swift`](https://skip.dev/docs/project-types/#ContentView.swift) and contains app lifecycle callbacks. This will be the common app logic that is invoked from the Darwin [Main.swift](https://skip.dev/docs/project-types/#Main.swift) and Android [Main.kt](https://skip.dev/docs/project-types/#Main.kt) . * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/AhoySkipper/AhoySkipperApp.swift ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/AhoySkipper/AhoySkipperApp.swift) #### `Sources//ContentView.swift` [Section titled “Sources//ContentView.swift”](https://skip.dev/docs/project-types/#ContentView.swift) This file is a SwiftUI View file representing the main user interface of the application. In a Skip project, the SwiftUI code in this file will be translated into Jetpack Compose for Android, allowing a single declarative UI definition to drive both platforms. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/AhoySkipper/ContentView.swift ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/AhoySkipper/ContentView.swift) * Reference: [SwiftUI Views ↗](https://developer.apple.com/documentation/swiftui/view-fundamentals) #### `Sources//Skip/skip.yml` [Section titled “Sources//Skip/skip.yml”](https://skip.dev/docs/project-types/#skip.yml) This is a YAML Configuration file specific to Skip. It controls module-level settings, such as [Skip mode](https://skip.dev/docs/modes/#configuration) , bridging configurations, and [Android-specific dependencies](https://skip.dev/docs/dependencies/#android) . * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/AhoySkipper/Skip/skip.yml ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/AhoySkipper/Skip/skip.yml) #### `Sources//Resources/Module.xcassets` [Section titled “Sources//Resources/Module.xcassets”](https://skip.dev/docs/project-types/#Module.xcassets) This is an Asset Catalog located within a specific Swift module. It holds resources like images, data, and localized strings that are scoped to that module. Skip ensures these assets are processed and made available to both the iOS bundle and the Android res directory. * Example: [https://github.com/skiptools/skipapp-ahoy/tree/main/Sources/AhoySkipper/Resources/Module.xcassets ↗](https://github.com/skiptools/skipapp-ahoy/tree/main/Sources/AhoySkipper/Resources/Module.xcassets) * Reference: [Apple Developer - Asset Catalogs ↗](https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs) #### `Sources//Resources/Localizable.xcstrings` [Section titled “Sources//Resources/Localizable.xcstrings”](https://skip.dev/docs/project-types/#Localizable.xcstrings) This is a String Catalog file used for managing translations. It is the modern format for Darwin localization. Skip converts these strings into Android asserts during the build process which will be used to provide translation parity on both platforms. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/AhoySkipper/Resources/Localizable.xcstrings ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/AhoySkipper/Resources/Localizable.xcstrings) * Reference: [Apple Developer - Localizing UI Strings ↗](https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog) #### `Sources//ViewModel.swift` [Section titled “Sources//ViewModel.swift”](https://skip.dev/docs/project-types/#ViewModel.swift) This file contains the business logic and state for the app, typically utilizing the `@Observable` macro. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/SkipperModel/ViewModel.swift ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Sources/SkipperModel/ViewModel.swift) #### `Tests//SkipperModelTests.swift` [Section titled “Tests//SkipperModelTests.swift”](https://skip.dev/docs/project-types/#SkipperModelTests.swift) This file contains test cases used to verify the logic of your modules. For a Skip Fuse project, these tests can be run directly with `skip android test`, and for a Skip Lite project, tests will be transpiled into Kotlin and can be run with `skip test`. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Tests/SkipperModelTests/SkipperModelTests.swift ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Tests/SkipperModelTests/SkipperModelTests.swift) #### `Darwin/ProjectName.xcconfig` [Section titled “Darwin/ProjectName.xcconfig”](https://skip.dev/docs/project-types/#ProjectName.xcconfig) This is an Xcode Build Configuration file. It provides a way to define build settings (such as compiler flags or search paths) in a plain-text format rather than within the opaque .xcodeproj file, making it easier to manage environment-specific settings. This configuration file automatically includes the root-level [`Skip.env`](https://skip.dev/docs/project-types/#Skip.env) file, so common properties can be shared between the Darwin and Android projects. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Darwin/AhoySkipper.xcconfig ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Darwin/AhoySkipper.xcconfig) * Reference: [Apple Developer - Configuration Settings File (xcconfig) format ↗](https://help.apple.com/xcode/mac/current/#/dev745c5c974) #### `Darwin/ProjectName.xcodeproj` [Section titled “Darwin/ProjectName.xcodeproj”](https://skip.dev/docs/project-types/#ProjectName.xcodeproj) This is the Xcode Project file, a bundle containing the configuration, build targets, and file references for the Darwin (iOS/macOS) version of the app. It is the primary entry point for developers focused on the Apple ecosystem. This project will automatically be included by the generated [`Project.xcworkspace`](https://skip.dev/docs/project-types/#Project.xcworkspace) . * Example: [https://github.com/skiptools/skipapp-ahoy/tree/main/Darwin/AhoySkipper.xcodeproj ↗](https://github.com/skiptools/skipapp-ahoy/tree/main/Darwin/AhoySkipper.xcodeproj) * Reference: [Apple Developer - Xcode Project Structure ↗](https://developer.apple.com/documentation/xcode/creating-an-xcode-project-for-an-app) #### `Darwin/Assets.xcassets` [Section titled “Darwin/Assets.xcassets”](https://skip.dev/docs/project-types/#Assets.xcassets) This is the Primary Asset Catalog for the Darwin application. It typically contains the application’s icon sets, launch images, and global color assets required for the iOS target. * Example: [https://github.com/skiptools/skipapp-ahoy/tree/main/Darwin/Assets.xcassets ↗](https://github.com/skiptools/skipapp-ahoy/tree/main/Darwin/Assets.xcassets) #### `Dawin/Assets.xcassets/AppIcon.appiconset/AppIcon.png` [Section titled “Dawin/Assets.xcassets/AppIcon.appiconset/AppIcon.png”](https://skip.dev/docs/project-types/#AppIcon.png) This is the Source Image file for the Darwin application icon. * Example: [https://github.com/skiptools/skipapp-ahoy/tree/main/Darwin/Assets.xcassets/AppIcon.appiconset ↗](https://github.com/skiptools/skipapp-ahoy/tree/main/Darwin/Assets.xcassets/AppIcon.appiconset) * Reference: [Apple Developer - App icons ↗](https://developer.apple.com/design/human-interface-guidelines/app-icons) #### `Darwin/Entitlements.plist` [Section titled “Darwin/Entitlements.plist”](https://skip.dev/docs/project-types/#Entitlements.plist) This is an XML Property List that defines the capabilities and permissions of the iOS app, such as Push Notifications or application permissions. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Darwin/Entitlements.plist ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Darwin/Entitlements.plist) * Reference: [Apple Developer - Entitlements ↗](https://developer.apple.com/documentation/bundleresources/entitlements) #### `Darwin/Info.plist` [Section titled “Darwin/Info.plist”](https://skip.dev/docs/project-types/#Info.plist) This file is the Information Property List for the iOS application. It contains essential configuration data, such as the bundle identifier, supported orientations, and required hardware capabilities. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Darwin/Info.plist ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Darwin/Info.plist) * Reference: [Apple Developer - Info.plist Keys ↗](https://developer.apple.com/documentation/bundleresources/information-property-list) #### `Darwin/Sources/Main.swift` [Section titled “Darwin/Sources/Main.swift”](https://skip.dev/docs/project-types/#Main.swift) This file serves as the Darwin Entry Point. It contains the @main attribute which initializes the SwiftUI App lifecycle for the iOS/macOS version of the project. The contents of this file will be quite minimal. Like its Kotlin [Main.kt](https://skip.dev/docs/project-types/#Main.kt) counterpart, it mostly calls into the shared [`App.swift`](https://skip.dev/docs/project-types/#App.swift) . * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Darwin/Main.swift ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Darwin/Main.swift) * Reference: [Apple Developer - App Structure ↗](https://developer.apple.com/documentation/swiftui/app) #### `Darwin/fastlane/` [Section titled “Darwin/fastlane/”](https://skip.dev/docs/project-types/#Darwin/fastlane) This directory contains Fastlane configuration files (`Deliverfile`, `Fastfile`, `Appfile`) used to automate the deployment process for the iOS app, specifically for uploading to the Apple App Store and TestFlight. * Example: [https://github.com/skiptools/skipapp-ahoy/tree/main/Darwin/fastlane ↗](https://github.com/skiptools/skipapp-ahoy/tree/main/Darwin/fastlane) * Reference: [Fastlane - Upload metadata and binary to App Store Connect ↗](https://docs.fastlane.tools/actions/upload_to_app_store/) #### `Android/app/src/main/kotlin/Main.kt` [Section titled “Android/app/src/main/kotlin/Main.kt”](https://skip.dev/docs/project-types/#Main.kt) This is the Android Entry Point. It contains the MainActivity class, which serves as the primary Activity for the Android app. The contents of this file will be quite minimal. Like its Swift [Main.swift](https://skip.dev/docs/project-types/#Main.swift) counterpart, it mostly calls into the shared [`App.swift`](https://skip.dev/docs/project-types/#App.swift) . * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Android/app/src/main/kotlin/Main.kt ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Android/app/src/main/kotlin/Main.kt) * Reference: [Android Developers - Activities ↗](https://developer.android.com/guide/components/activities/intro-activities) #### `Android/settings.gradle.kts` [Section titled “Android/settings.gradle.kts”](https://skip.dev/docs/project-types/#settings.gradle.kts) This is a Gradle Settings file written in Kotlin DSL. It defines the project-level settings and includes the sub-modules (like :app) that make up the Android build. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Android/settings.gradle.kts ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Android/settings.gradle.kts) * Reference: [Gradle - Settings File ↗](https://developer.android.com/build#settings-file) #### `Android/app/build.gradle.kts` [Section titled “Android/app/build.gradle.kts”](https://skip.dev/docs/project-types/#build.gradle.kts) This is the Module-level Gradle Build file. It defines the Android-specific build configuration, including SDK versions, signing configurations, and dependencies (including the transpiled Swift modules). * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Android/app/build.gradle.kts ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Android/app/build.gradle.kts) * Reference: [Android Developers - Configure Your Build ↗](https://developer.android.com/build#top-level) #### `Android/app/proguard-rules.pro` [Section titled “Android/app/proguard-rules.pro”](https://skip.dev/docs/project-types/#proguard-rules.pro) This file contains R8/ProGuard rules for code shrinking and optimization. It ensures that the Kotlin code and its dependencies are properly minified for production. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Android/app/proguard-rules.pro ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Android/app/proguard-rules.pro) * Reference: [Enable app optimization ↗](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) #### `Android/src/main/AndroidManifest.xml` [Section titled “Android/src/main/AndroidManifest.xml”](https://skip.dev/docs/project-types/#AndroidManifest.xml) This is the Android Manifest file. It describes essential information about the app to the Android build tools, the Android OS, and Google Play, including permissions and the entry point Activity. * Example: [https://github.com/skiptools/skipapp-ahoy/blob/main/Android/app/src/main/AndroidManifest.xml ↗](https://github.com/skiptools/skipapp-ahoy/blob/main/Android/app/src/main/AndroidManifest.xml) * Reference: [Android Developers - App Manifest Overview ↗](https://developer.android.com/guide/topics/manifest/manifest-intro) #### `Android/src/main/kotlin/res/` [Section titled “Android/src/main/kotlin/res/”](https://skip.dev/docs/project-types/#Android/src/main/kotlin/res) This is a folder that contains app icon for various density Android screens. * Example: [https://github.com/skiptools/skipapp-ahoy/tree/main/Android/app/src/main/res ↗](https://github.com/skiptools/skipapp-ahoy/tree/main/Android/app/src/main/res) * Reference: [Android Developers - App Icons ↗](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive) #### `Android/fastlane/` [Section titled “Android/fastlane/”](https://skip.dev/docs/project-types/#Android/fastlane) This directory contains Fastlane configuration files (`Appfile`, `Fastfile`) used to automate the deployment process for the Android app, specifically for uploading to the Google Play Store. * Example: [https://github.com/skiptools/skipapp-ahoy/tree/main/Android/fastlane ↗](https://github.com/skiptools/skipapp-ahoy/tree/main/Android/fastlane) * Reference: [Fastlane - Upload metadata, screenshots and binaries to Google Play ↗](https://docs.fastlane.tools/actions/upload_to_play_store/) Project Types ------------- [Section titled “Project Types”](https://skip.dev/docs/project-types/#project-types) The Skip [Getting Started](https://skip.dev/docs/gettingstarted/) guide demonstrates using `skip create` to generate a basic app project interactively, but there are a variety of additional project types. This document discussed the various different project types that can be created and built with Skip. ### Creating an App with `skip init` [Section titled “Creating an App with skip init”](https://skip.dev/docs/project-types/#app_development) The `skip init` command is an alternative to the interactive `skip create` command, which facilitates the initialization of an app with a single, non-interactive command. Run `skip init --help` or see the the [command line reference](https://skip.dev/docs/skip-cli/#init) for a complete listing of `skip init` options. ### Creating a Standard App [Section titled “Creating a Standard App”](https://skip.dev/docs/project-types/#creating-a-standard-app) Create a new dual-platform app project with the command: skip init --native-app --appid=bundle.id project-name AppName Pass the `--open-xcode` argument to immediately open the project in Xcode. For example: skip init --open-xcode --native-app --appid=bundle.id.HelloSkip hello-skip HelloSkip This will create a `hello-skip/` folder with a new SwiftPM package containing a single module named `HelloSkip`, along with folders named `Darwin` and `Android` and the shared `Skip.env` app configuration file. The `Darwin` folder will contain a `HelloSkip.xcodeproj` project with a `HelloSkip` target, which you can open in Xcode. ### Creating a Multi-Module App [Section titled “Creating a Multi-Module App”](https://skip.dev/docs/project-types/#creating-a-multi-module-app) Skip is designed to accommodate and encourage using multi-module projects. You can create a modularized project by specifying additional module names to `skip init` at the end of the chain. For example: skip init --native-app --appid=bundle.id.HelloSkip multi-project HelloSkip HelloModel HelloCore This command will create a SwiftPM project with three modules: `HelloSkip`, `HelloModel`, and `HelloCore`. The heuristics of such module creation is that the modules will all be dependent on their subsequent peer module, with the first module (`HelloSkip`) having an initial dependency on `SkipFuseUI` and the second module depending on `SkipFuse` and `SkipModel`. The `Package.swift` file can be manually edited to shuffle around dependencies, or to add new dependencies on additional frameworks. ### Creating Separate iOS and Android Apps [Section titled “Creating Separate iOS and Android Apps”](https://skip.dev/docs/project-types/#separate-apps) You might choose to share functionality using [dual-platform frameworks](https://skip.dev/docs/project-types/#framework_development) , but create separate iOS and Android apps. Some development teams, for example, would like to share common model and business logic layers, but write the UI separately for each platform. The [Travel Posters](https://skip.dev/docs/samples/skipapp-travelposters-native/) sample app provides an example of this pattern. You can read more about it in this [blog post](https://skip.dev/blog/shared-swift-model/) . It has the following top-level entries: * `travel-posters-model`: This SwiftPM package builds a [dual-platform framework](https://skip.dev/docs/project-types/#framework_development) containing a common model layer for the iOS and Android apps. Skip ensures that the `@Observable` types you write in Swift can power not only a SwiftUI interface, but a Compose interface as well. See the Development [documentation](https://skip.dev/docs/app-development/#observables) for details. * `iOS`: Directory containing the TravelPosters iOS app and Xcode project, which has `travel-posters-model` as a package dependency. * `Android`: Directory containing the Android version of the app. The `Android/lib` directory contains exported archives of `travel-posters-model` and the various Skip frameworks that it depends on. * `TravelPostersNative.xcworkspace`: A workspace that includes both the iOS app and the `travel-posters-model` package. Use `TravelPostersNative.xcworkspace` to iterate on the iOS app and/or shared model layer. To donate the latest `travel-posters-model` code to the Android app: skip export --project travel-posters-model -d Android/lib/debug/ --debugskip export --project travel-posters-model -d Android/lib/release/ --release There are many ways to automate this process, from simple scripting to git submodules to publishing Skip’s generated Android `travel-posters-model` output to a local Maven repository. Use whatever system fits your team’s workflow best. Additional notes: * You may need to “Sync Project with Gradle Files” in Android Studio after updating the exported libraries. * Using an exported library function which has transitive dependencies on additional Android libraries can cause a runtime error. You must ensure that all transitive dependencies are in your own app’s `build.gradle.kts`. * * * ### Creating a Dual-Platform Framework [Section titled “Creating a Dual-Platform Framework”](https://skip.dev/docs/project-types/#framework_development) [SkipFuse](https://skip.dev/docs/status/#skip_fuse) apps can depend on pure SwiftPM packages as well as pure Kotlin/Java Android libraries. See the chapter covering [dependencies](https://skip.dev/docs/dependencies) for details. This section describes how to create a Swift framework whose API can be used from both Swift and Kotlin. The most common use cases are powering [SkipLite](https://skip.dev/docs/status/#skip_fuse) transpiled apps or [separate iOS and Android](https://skip.dev/docs/project-types/#separate-apps) apps. Dual-platform Skip frameworks are pure SwiftPM packages that encapsulate common functionality. Frameworks are simpler than app projects, as they do not need `Darwin/` and `Android/` folders. ![Framework Development Screenshot](https://assets.skip.dev/framework-dev/framework-skip-init.png) A new framework project can be generated with: skip init --native-model lib-name ModuleName This will create a new `lib-name` folder containing a `Package.swift` with targets of `ModuleName` and `ModuleNameTests`. The generated package can be opened in Xcode, which you can use to build and run the unit tests. Or use `swift build` and `swift test` from the Terminal for headless testing as part of a continuous integration process. The `--native-model` option we passed to `skip init` will configure Skip to automatically bridge our model’s public API from compiled Swift to Android’s [ART ↗](https://source.android.com/docs/core/runtime) Java runtime. This is done through the [`skip.yml`](https://skip.dev/docs/modes/#configuration) configuration file included in every Skip module. See the documentation on [bridging](https://skip.dev/docs/modes/#bridging) and [`skip.yml`](https://skip.dev/docs/modes/#configuration) for details. ### Skip Framework Structure [Section titled “Skip Framework Structure”](https://skip.dev/docs/project-types/#framework_structure) The structure of a Skip framework is exactly the same as any other SwiftPM package: * Directorylib-name * Package.swift * README.md * DirectorySources * DirectoryModuleName * ModuleName.swift * DirectoryResources * Localizable.xcstrings * DirectorySkip * skip.yml * DirectoryTests * DirectoryModuleNameTests * ModuleNameTests.swift * XCSkipTests.swift * DirectoryResources * TestData.json * DirectorySkip * skip.yml Skip frameworks use a standard `Package.swift` file, with the exception of added dependencies on Skip libraries and use of the `skipstone` plugin for transpilation: // swift-tools-version: 5.9import PackageDescription let package = Package( name: "lib-name", defaultLocalization: "en", platforms: [.iOS(.v16), .macOS(.v13), .tvOS(.v16), .watchOS(.v9), .macCatalyst(.v16)], products: [ .library(name: "ModuleName", targets: ["ModuleName"]), ], dependencies: [ .package(url: "https://source.skip.tools/skip.git", from: "1.7.0"), .package(url: "https://source.skip.tools/skip-fuse.git", from: "1.0.0"), .package(url: "https://source.skip.tools/skip-model.git", from: "1.0.0"), ], targets: [ .target(name: "ModuleName", , dependencies: [ .product(name: "SkipFuse", package: "skip-fuse") .product(name: "SkipModel", package: "skip-model") ], plugins: [ .plugin(name: "skipstone", package: "skip") ]), .testTarget(name: "ModuleNameTests", dependencies: ["ModuleName"], plugins: [.plugin(name: "skipstone", package: "skip")]), ]) This configuration includes the dependencies you need to write common model code, including `@Observables` that will work with both SwiftUI and Compose user interfaces. You are free to edit `Package.swift` for your particular needs. * * * Migrating an Existing App to Skip --------------------------------- [Section titled “Migrating an Existing App to Skip”](https://skip.dev/docs/project-types/#existing_development) Migrating an existing app to Skip is not trivial. Most apps contain many iOS-only dependencies that make an Android port challenging. Additionally, when you use `skip create` or `skip init` to create a [new Skip app](https://skip.dev/docs/project-types/#app_development) , it handles all the messy details involved in making an app that can build for both iOS and Android. The process is complex enough that we do not recommend trying to migrate an existing Xcode project. Instead, choose one of two options to create an Android version of your existing app: 1. Use `skip create` or `skip init` to create a new Skip app, then add your existing app’s dependencies and code. 2. Keep your existing Xcode app, and create a separate Android app using Android Studio or your IDE of choice. Manage the apps separately, but share code by creating [dual-platform frameworks](https://skip.dev/docs/project-types/#framework_development) . Regardless of which option you choose, your first steps are the same: 1. Modularize your app into Swift Package Manager packages, if it isn’t already. 2. Starting with your “base” module and working your way up the stack, attempt to get each module building for Android. We recommend using [Skip Fuse](https://skip.dev/docs/status/#skip_fuse) where possible, as it offers greater compatibility with existing Swift code and dependencies. Porting an app to an entirely new platform isn’t easy, even with Skip. Remember that [we’re here to help](https://skip.dev/docs/help/) . --- # Auth0 | Skip [Skip to content](https://skip.dev/docs/modules/skip-auth0/#_top) Auth0 ===== This is a free Skip Swift/Kotlin library project containing the following modules: SkipAuth0 Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-auth0/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-auth0/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # SkipUnit | Skip [Skip to content](https://skip.dev/docs/modules/skip-unit/#_top) SkipUnit ======== Base Skip gradle plugin module and unit testing for [Skip ↗](https://skip.tools/) apps, adapting Swift XCUnit to transpiled Kotlin JUnit test cases. About ----- [Section titled “About”](https://skip.dev/docs/modules/skip-unit/#about) SkipUnit vends the `skip.unit` Kotlin package containing a Swift `XCTest` interface to the Java/Kotlin `JUnit` testing framework. Combined with the Skip [transpiler ↗](https://source.skip.tools/skip) , this provides automatic transpilation of XCUnit test cases as JUnit tests, which enables parity testing to identify and isolate any differences between your Swift code and your transpiled Skip Kotlin code. Dependencies ------------ [Section titled “Dependencies”](https://skip.dev/docs/modules/skip-unit/#dependencies) SkipUnit depends on the [skip ↗](https://source.skip.tools/skip) transpiler plugin and has no additional library dependencies. It is part of the core _SkipStack_ and is not intended to be imported directly. The module is transparently adopted through the translation of `import XCUnit` into `import skip.unit.*` by the Skip transpiler. Parity Testing -------------- [Section titled “Parity Testing”](https://skip.dev/docs/modules/skip-unit/#parity-testing) Parity testing is a central aspect of Skip development. It ensures that your Swift and Kotlin behave exactly the same. You do not need to import any Skip-specific libraries or APIs in order to perform parity testing. The standard modules of `XCTest` and `Foundation`, as well as your own library dependencies, will be sufficient for performing most test operations. Your tests **do** need to run against both the macOS and iOS platforms, because the Skip tests are only executed when running on the macOS destination. See [Running Tests from Xcode](https://skip.dev/docs/modules/skip-unit/#running-tests-from-xcode) . Testing Modes ------------- [Section titled “Testing Modes”](https://skip.dev/docs/modules/skip-unit/#testing-modes) When writing Swift code that doesn’t need access to device-specific services or other iOS-specific capabilities, the fastest development path is to build and run against macOS. Most common APIs behave the same between macOS and iOS, and running tests locally against the macOS version of an app enables quick unit testing cycles, both when developing with Xcode interactively, as well as running as part of a continuous integration (CI) process by running `swift test`. When tests need iOS-specific functionality, those tests can be gated inside `#if os(iOS)` blocks on order to enable them to be run only when targeting the iOS Simulator or device. Similarly, when running transpiled Kotlin/Gradle tests, the fastest development mode is to run the JUnit tests locally without launching an Android emulator or connecting to a device. While the Java and Kotlin APIs that underly `SkipFoundation` are sufficient for many testing needs, there are some Android-specific APIs that are needed. This compatibility is provided by the Robolectric framework, which provides a set of Android-compatible APIs for app testing. Keep in mind that _Robolectric is not Android_. The local Robolectric testing environment is similar, but not identical, to an actual Android emulator or device. Some Android APIs are missing from Robolectric, and it is possible that the compiled Java bytecode run locally differs from the Dalvik/ART byte code that is run in a true Android environment. If this becomes a problem, follow the instructions below to run the tests on an Android device or emulator instead. These six modes of testing can be summarized by the following table: | | Swift | Kotlin | Fidelity | Speed | | --- | --- | --- | --- | --- | | Local | macOS w/ Cocoa API | macOS w/ Robolectric API | lowest | highest | | Simula | iOS Simualtor | Android Emulator | good | slower | | Actual | iOS Device | Android Device | highest | slowest | By default, Skip uses the simulated Robolectric Android environment to run your transpiled tests. To run them against an Android device or emulator instead, set the device or emulator ID in the `ANDROID_SERIAL` environment variable. This can be done either in the Xcode scheme’s `Run` action arguments for the target, or as a standard environment variable when using the command line: > ANDROID_SERIAL=emulator-5554 swift test Writing Tests ------------- [Section titled “Writing Tests”](https://skip.dev/docs/modules/skip-unit/#writing-tests) A standard Skip test is just a plain XCTest: import XCTest final class MyUnitTests: XCTestCase { func testSomeLogic() throws { XCTAssertEqual(1 + 2, 3, "basic test") }} **Note**: The Skip transpiler currently does not have access to the internal API of the module being tested. If you take advantage of Swift’s `@testable imports` to exercise internal API, the transpiler will not be able to perform its usual type inference when translating your test code. This just means that you might have to be more explicit about types and to fully-qualify values (e.g. `MyType.value` instead of just `.value`) when unit testing internal API. Running Tests ------------- [Section titled “Running Tests”](https://skip.dev/docs/modules/skip-unit/#running-tests) The transpiled unit tests are intended to be run as part of the standard Xcode and Swift Package Manager testing process. This is done by adding one additional test class to the project’s `Tests/ModuleNameTests/` folder named `XCSkipTests.swift`. This additional test class is added automatically when a library is created with the `skip init` command. When adopting Skip into an existing process, add the test case manually: #if os(macOS) // Skip transpiled tests only run on macOS targetsimport SkipTest /// This test case will run the transpiled tests for the Skip module.@available(macOS 13, *)final class XCSkipTests: XCTestCase, XCGradleHarness { public func testSkipModule() async throws { try await runGradleTests(device: .none) // set device ID to run in Android emulator vs. Robolectric }}#endif ### Running Tests from Xcode [Section titled “Running Tests from Xcode”](https://skip.dev/docs/modules/skip-unit/#running-tests-from-xcode) Once the `XCSkipTests.swift` file has been added to a project, the transpiled test cases will automatically run whenever testing against the **macOS** run destination. As such, you need to ensure that your Swift code compiles and runs the same on macOS and iOS. This is a pre-requisite for Skip’s parity testing, which runs the XCUnit test cases on macOS against the transpiled Kotlin tests in the Android testing environment. While many of the Foundation and SwiftUI APIs are identical on macOS and iOS, you may occasionally have to work around minor differences. The transpiled unit tests are run by forking the `gradle test` process on the macOS host machine against the output folder of the skip transpiler plugin. The JUnit test output XML files are then parsed, and a report summarizing the test results is presented. ### Running Tests from the CLI/CI [Section titled “Running Tests from the CLI/CI”](https://skip.dev/docs/modules/skip-unit/#running-tests-from-the-clici) The `swift test` command on macOS will automatically perform test transpliation. This can be used for headless testing locally as well as on a continuous integration (CI) server. Note that running test cases will also initiate a Gradle build, which has the side-effect of Gradle downloading all the library dependencies for the modules. When tests depend on frameworks like `SkipUI`, which depends on many Jetpack Compose libraries, the dependencies can amount to over 1 gigabyte in the `~/.gradle/` folder. This may lead to a slow initial run of the tests and a perception that the tests may be hanging or excessively slow. Subsequent runs will use the cached dependencies, and will thus run much more quickly. ### Running Tests from Android Studio [Section titled “Running Tests from Android Studio”](https://skip.dev/docs/modules/skip-unit/#running-tests-from-android-studio) Once your module and tests have been transpiled, you can run the JUnit tests directly from Android Studio. To open the tests for a module created with `skip init`, navigate to your module’s `Skip/build/.output/Tests/skipstone` folder. Control-click the `settings.gradle.kts` file and select ‘Open with External Editor’ from the resulting context menu. Running in Android Studio allows you to bypass the iOS tests and to run - and to debug - individual Android tests. This can be helpful when tracking down Android-specific failures. ### Test Failures [Section titled “Test Failures”](https://skip.dev/docs/modules/skip-unit/#test-failures) Test failures differ in the XCTest and JUnit worlds. When an `XCTAssert*` failure occurs in Swift, the test failure is noted, but the test continues to run. When the adapted `assert*` failure occurs in Kotlin, that failure is signalled by an exception, which halts the execution of that test method. This distinction can be noted in the differing number of test failures that occur when mulitple `XCTAssert*` failures occur. The same applies to `XCTFail`, but not to `XCTSkip`, which is the supported and recommended way to prevent tests from running in one or the other environment. Implementation Notes -------------------- [Section titled “Implementation Notes”](https://skip.dev/docs/modules/skip-unit/#implementation-notes) The adaptation from Swift XCUnit to Kotlin JUnit test cases is quite simple. For example: fun XCTAssertEqual(a: Any?, b: Any?): Unit = org.junit.Assert.assertEquals(b, a) ### Transpiled Kotlin Test Case [Section titled “Transpiled Kotlin Test Case”](https://skip.dev/docs/modules/skip-unit/#transpiled-kotlin-test-case) While you may never need to interact with it directly, the transpiled Kotlin for the example test case above looks like this: package app.module.name import skip.lib.* import skip.unit.* internal class MyUnitTests: XCTestCase { @Test internal fun testSomeLogic() = XCTAssertEqual(1 + 2, 3, "basic test")} --- # Contributing | Skip [Skip to content](https://skip.dev/docs/contributing/#_top) Contributing ============ Skip welcomes community contributions in all forms. There are many ways to contribute: * Help improve the docs. Every documentation page contains an “Edit page” link at the bottom which takes you to the [github.com/skiptools/skip.dev/ ↗](https://github.com/skiptools/skip.dev/) documentation site where you can fork a page, make improvements, and submit it as a pull request. * Improve the Skip [Core Frameworks](https://skip.dev/docs/modules/) , Skip’s open source re-implementations of fundamental Swift frameworks like Foundation for [Skip Lite](https://skip.dev/docs/status/#skip_fuse) . Each library’s README includes background information such as its current status, any important implementation strategies employed, etc. Please review the README before contributing. When you are ready to submit an update, create a standard GitHub [pull request ↗](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) . Skip’s development team will attempt to address PRs as quickly as possible. * Contribute to [SkipUI](https://skip.dev/docs/modules/skip-ui/) and [SkipFuseUI](https://skip.dev/docs/modules/skip-fuse-ui/) to help expand and improve SwiftUI support on Android. As with other Skip modules, please review the README, and submit a standard pull request. `SkipFuse` and `SkipFuseUI` libraries have [special considerations](https://skip.dev/docs/contributing/#skipui-and-skipfuseui) for contribution. * Submit pull requests to third-party Swift libraries so that they can build for Android and show up as Android compatible on the [Swift Package Index ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) . See the [porting guide](https://skip.dev/docs/porting/) for helpful tips. Local Skip Libraries -------------------- [Section titled “Local Skip Libraries”](https://skip.dev/docs/contributing/#local-libraries) If you’d like to make local changes to Skip’s libraries while working on an app - such as by improving [SkipUI](https://skip.dev/docs/modules/skip-ui/) or [SkipFuseUI](https://skip.dev/docs/modules/skip-fuse-ui/) and exercising your changes with its [ShowcaseFuse app](https://skip.dev/docs/samples/skipapp-showcase-fuse/) - you will want to configure Xcode to use local library versions rather than the current Skip distributions. This is easily accomplished with an Xcode Workspace. 1. Clone the [repositories ↗](https://github.com/skiptools) you’d like to work on. We find it most convenient to make the clone directory a peer of your app directory. > lshello-skip/ skip-ui/ skip-fuse-ui/ 2. Use Xcode to create a new Workspace in the same directory. 3. Add your app’s `.xcodeproj` file to the Workspace. 4. Add your local module directories to the Workspace. Adding these local packages will override the distributions specified in `Package.swift`, so that changes to the local packages will get used in your app build. 5. Use the Workspace to iterate on your app and the libraries in tandem. You are now set up to work on Skip libraries! This is a great way to fix any issues or add any missing functionality that is affecting your app, while potentially helping the entire Skip community. ### Working in Android Studio [Section titled “Working in Android Studio”](https://skip.dev/docs/contributing/#working-in-android-studio) Launching your app from Android Studio will **not** use your Workspace’s local libraries by default. If you’d like to work in Android Studio, edit your app’s `Android/settings.gradle.kts` file to point Android Studio at Xcode’s build, as described in [Cross-Platform Topics](https://skip.dev/docs/platformcustomization/#android-studio-setup) . ### Android Studio Tips [Section titled “Android Studio Tips”](https://skip.dev/docs/contributing/#android-studio-tips) If you’re working on a [transpiled library](https://skip.dev/docs/modes/#transpiled) that involves heavy use of Kotlin API, you might find it faster to open your module in Android Studio and iterate there _even if you want the final code to be in Swift_. The idea is to prototype in Android Studio using Kotlin, then back-port the solution to your Swift codebase. We have found this particularly useful when implementing SwiftUI features on top of Compose. Android Studio provides robust autocompletion, inline documentation, and automatic `imports` of the myriad of Compose packages. It is also extremely fast to build and run, which helps a lot when you’re experimenting. Our general strategy when implementing a major [SkipUI module](https://skip.dev/docs/modules/skip-ui/) feature is: 1. Make sure you’re set up for [local library development](https://skip.dev/docs/contributing/#local-libraries) . Don’t forget to point Android Studio at your local libraries. 2. Stub out the feature in Swift and Xcode. 3. Build your app (in the case of SkipUI work, we use the [Showcase app](https://skip.dev/docs/samples/skipapp-showcase/) ) so that your Swift stubs are transpiled. 4. [Open the app](https://skip.dev/docs/platformcustomization/#android-studio) in Android Studio. 5. Iterate on the implementation in Android Studio using pure Kotlin/Compose. Yes, this involves editing the transpiled file containing your stubs, and you may even have to override the file’s read-only flag to do so. **Take care not to run an Xcode build** during this phase, because it will overwrite the file with its own transpilation. 6. Once you have settled on your Android implementation, use it to fill in your Swift stubs. You can do this by separating it into an included [Kotlin file](https://skip.dev/docs/platformcustomization/#kotlin-files) whose API you call from Swift, or by back-porting the Kotlin you wrote into inline Swift. Again, we only recommend this process when your implementation is going to require heavy use of Kotlin API and a healthy amount of iteration and experimentation. In that case, the speed of using Android’s native IDE to figure out what works more than makes up for the extra few minutes it takes to clean up the final solution. SkipUI and SkipFuseUI --------------------- [Section titled “SkipUI and SkipFuseUI”](https://skip.dev/docs/contributing/#skipui-and-skipfuseui) Most contributions to the libraries, whether they are [Core Frameworks](https://skip.dev/docs/modules/) or [Integration Frameworks](https://skip.dev/docs/contributing/#integration-frameworks) , will be isolated to submitting pull requests for a single package and repository. SkipUI, however, is different: due to the complexity of the SwiftUI implementation, Skip’s support for it is split across two separate repositories: 1. [SkipUI](https://skip.dev/docs/modules/skip-ui/) for the transpiled Skip Lite mapping of the SwiftUI concepts to their Jetpack Compose equivalents 2. [SkipFuseUI](https://skip.dev/docs/modules/skip-fuse-ui/) for the additional accommodations that are needed to use `SKipUI` in a native Skip Fuse app. Some improvements to `SkipUI`, such as fixing bugs with Compose implementations, can be done in just the [`skip-ui` ↗](https://github.com/skiptools/skip-ui) repository. But if the improvement involves additional API that is being made available or any other changes to the `SwiftUI` surface, and PRs will need to be paired with a PR against the [`skip-fuse-ui` ↗](https://github.com/skiptools/skip-fuse-ui) repository. In addition, when adding new features or otherwise changing behavior, it is always good to test it out in the Showcase apps, both the [`skipapp-showcase` ↗](https://github.com/skiptools/skipapp-showcase) Skip Lite app and the equivalent [`skipapp-showcase-fuse` ↗](https://github.com/skiptools/skipapp-showcase-fuse) app. This both serves as a way to test out your new feature, ensure that it doesn’t break anything else, and provide a demonstration for other developers of how your improvement works. So a sample workflow making a contribution would look like this: 1. Fork SkipUI: [https://github.com/skiptools/skip-ui/fork ↗](https://github.com/skiptools/skip-ui/fork) 2. Fork SkipFuseUI: [https://github.com/skiptools/skip-fuse-ui/fork ↗](https://github.com/skiptools/skip-fuse-ui/fork) 3. Fork Showcase Lite: [https://github.com/skiptools/skipapp-showcase/fork ↗](https://github.com/skiptools/skipapp-showcase/fork) 4. Fork Showcase Fuse: [https://github.com/skiptools/skipapp-showcase-fuse/fork ↗](https://github.com/skiptools/skipapp-showcase-fuse/fork) 5. Clone each of the forked repositories to a local folder 6. Create an Xcode workspace that incorporates both the `skip-ui` and `skip-fuse-ui` packages as well as the `skipapp-showcase/Darwin/Showcase.xcodeproj` and `skipapp-showcase-fuse/Darwin/ShowcaseFuse.xcodeproj` projects 7. Run each of the `ShowcaseLite App` and `ShowcaseFuse App` targets to ensure they work 8. Start implementing changes to `SkipUI`, testing them out by launching the `ShowcaseLite App` target 9. When you are happy with the changes, begin implementing the equivalent functionality in the `SkipFuseUI` module, and test those out by launching the `ShowcaseFuse App` target 10. When everything is working, push your changes to your local forks and submit 4 separate PRs to each of the upstream repositories. Integration Frameworks ---------------------- [Section titled “Integration Frameworks”](https://skip.dev/docs/contributing/#integration-frameworks) Integration frameworks are the lifeblood of Skip: they are the optional modules that provide a single API to equivalent functionality on Android and Darwin platforms alike. A subset of the existing integration frameworks is available at the [modules index](https://skip.dev/docs/modules/) , but many more exist under the [Skip GitHub organization repositories ↗](https://github.com/orgs/skiptools/repositories) . Many of these frameworks have been contributed by users over the years. There are two different strategies for implementing integration frameworks: “Pass-through” and “Bespoke”. ### Pass-through API [Section titled “Pass-through API”](https://skip.dev/docs/contributing/#pass-through-api) When the goal is to mimic a pre-existing Darwin (iOS or macOS) API surface on Android, the Android API can be crafted to match the Darwin API. Some examples of pass-through APIs are: * [SkipAV](https://skip.dev/docs/modules/skip-av/) : provides an Android equivalent of a subset of the Darwin [AVKit ↗](https://developer.apple.com/documentation/avkit) and [AVFoundation ↗](https://developer.apple.com/documentation/avfoundation) frameworks, backed by [Jetpack Media3 ↗](https://developer.android.com/media/media3) libraries. * [SkipBluetooth](https://skip.dev/docs/modules/skip-bluetooth/) : mimics Darwin’s built-in [CoreBluetooth ↗](https://developer.apple.com/documentation/corebluetooth) framework using Android’s built-in [Bluetooth ↗](https://developer.android.com/develop/connectivity/bluetooth) libraries. * [SkipScript](https://skip.dev/docs/modules/skip-script/) : mimics Darwin’s [JavaScriptCore ↗](https://developer.apple.com/documentation/javascriptcore) framework on Android, based on a third-party [JSC build ↗](https://github.com/react-native-community/jsc-android-buildscripts) . * [SkipFirebase](https://skip.dev/docs/modules/skip-firebase/) : Provides API parity between the Firebase’s [Darwin ↗](https://firebase.google.com/docs/ios/learn-more) and [Android ↗](https://firebase.google.com/docs/android/learn-more) APIs. The pass-through strategy has these advantages: 1. The API is already designed for you. You just need to “fill in the blanks” for Android for the subset of functionality that you want to expose. 2. When mimicking a pre-existing Darwin API surface, often you _only_ need to implement the Android side (typically within an `#if SKIP` block). The iOS/macOS API will be used as-is, and your Android implementation just needs to be crafted to work the same as the Darwin side. ### Bespoke API [Section titled “Bespoke API”](https://skip.dev/docs/contributing/#bespoke-api) Unlike the pass-through strategy, a bespoke API is a whole new API surface that adapts separately to underlying Darwin and Android APIs. Some examples of bespoke APIs are: * [SkipDevice](https://skip.dev/docs/modules/skip-device/) : Integration with device sensors such as Location, Network Reachability, and Accelerometer. * [SkipKeychain](https://skip.dev/docs/modules/skip-keychain/) : Abstraction of Darwin [Keychain Services ↗](https://developer.apple.com/documentation/security/keychain-services) and Android [Keystore ↗](https://developer.android.com/privacy-and-security/keystore) . * [SkipMarketplace](https://skip.dev/docs/modules/skip-marketplace/) : Utilities around app purchases, updates and reviews. Some advantages of the bespoke API strategy are: 1. While a pass-through API will constrain the design to an (oftentimes legacy) pre-existing Darwin API, designing a custom API to abstract functionality on Darwin and Android platforms enables using modern conventions such as `async`/`await` and `AsyncStream`. 2. A custom API can build just the subset of supported API, rather than having to provide shims for unsupported parts of an API on Android that throw errors indicating that they are unsupported. 3. A bespoke API can cut across multiple independent dependencies rather than being narrowly restricted to just the framework that it is designed to mimic. ### Implementation Tips [Section titled “Implementation Tips”](https://skip.dev/docs/contributing/#implementation-tips) Regardless of whether an integration framework is pass-through or bespoke, there are some useful strategies to designing and implementing them. 1. **Minimize Novelty**: An integration framework is usually intended to provide an abstraction between equivalent functionality or features on Android and Darwin. It is generally recommended that implementations be as minimal as possible, and delegate the important logic to the underlying implementation, which might either be a built-in feature of the platform or an external library dependency. 2. **Seek Prior Art**: Many common integration needs have been already implemented in other cross-platform development tools. Searching on Flutter’s [pub.dev ↗](https://pub.dev/) or React Native’s [expo.dev ↗](https://docs.expo.dev/versions/latest/) can lead to existing free and open-source implementations of the necessary Swift (or Objective-C) and Kotlin (or Java) that demonstrates how the integration might be accomplished. These can either be adapted for Skip, or serve as inspiration for how a new API might be designed and implemented. ### Contribution Process for Integration Frameworks [Section titled “Contribution Process for Integration Frameworks”](https://skip.dev/docs/contributing/#contribution-process-for-integration-frameworks) There is no obligation to contribute your framework to the Skip organization, but doing so helps the community and opens up the framework to being documented on the skip.dev modules page and helps encourage community contributions and ongoing maintenance. To suggest a new idea for an integration framework, post a message on the [ideas forum ↗](https://github.com/orgs/skiptools/discussions/categories/ideas) with a description of the new features and functionality it will enable. Typically, we will then create a new Skip repository with a blank Skip project, which you can then fork, iterate on, and then improve by submitting a pull request. Native Android Libraries ------------------------ [Section titled “Native Android Libraries”](https://skip.dev/docs/contributing/#community-libraries) Skip Fuse apps can utilize pure Swift libraries with them needing the be “Skip aware”. This necessitates that the package will build for Android as well as Darwin platforms. The [Swift Package Index ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) tracks thousands of Swift packages that compile natively for Android. Submit your own Linux and Android-supporting packages to the Swift Package Index to automatically include and document them. Skip users can utilize [transpiled packages](https://skip.dev/docs/modes/#transpiled) in addition to native ones. Transpiled packages are useful both to full Skip Lite apps, and - when [bridging](https://skip.dev/docs/modes/#bridging) is applied - to Skip Fuse apps that want to interface with Kotlin libraries. If you’ve created a [transpiled library](https://skip.dev/docs/dependencies/#implementation) that you think might be useful to other Skip users, please consider opening it up to the Skip community. --- # SkipModel | Skip [Skip to content](https://skip.dev/docs/modules/skip-model/#_top) SkipModel ========= Model object observation for Skip Lite transpiled Swift. Parts of this package are also used by [Skip Fuse](https://skip.dev/docs/status/#skip_fuse) to bridge `@Observables` to Compose. See what API is included [here](https://skip.dev/docs/modules/skip-model/#api-support) . Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-model/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-model.git", from: "1.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipModel", package: "skip-model") ]) ]) About ----- [Section titled “About”](https://skip.dev/docs/modules/skip-model/#about) SkipModel vends the `skip.model` Kotlin package. This package contains `Observable` and `ObservableObject` interfaces, representing the two core protocols that SwiftUI uses to observe changes to model objects. It also includes limited `Publisher` support. Dependencies ------------ [Section titled “Dependencies”](https://skip.dev/docs/modules/skip-model/#dependencies) SkipLib depends on the [skip ↗](https://source.skip.dev/skip) transpiler plugin and the [SkipFoundation](https://skip.dev/docs/modules/skip-foundation) package. SkipModel is part of the core _SkipStack_ and is not intended to be imported directly. The transpiler includes `import skip.model.*` in generated Kotlin for any Swift source that imports the `Combine`, `Observation`, or `SwiftUI` frameworks. Status ------ [Section titled “Status”](https://skip.dev/docs/modules/skip-model/#status) From the `Observation` package, SkipModel supports the `@Observable` and `@ObservationIgnored` macros. From `Combine`, SkipModel supports the `ObservableObject` protocol, the `@Published` property wrapper, and limited `Publisher` functionality. See [API support](https://skip.dev/docs/modules/skip-model/#api-support) below. Much of Skip’s model support is implemented directly in the Skip transpiler. The `Observable` and `ObservableObject` marker protocols are are sufficient for the Skip transpiler to recognize your observable types. When generating their corresponding Kotlin classes, the transpiler then adds the necessary code so that their state can be tracked by the Compose runtime. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-model/#contributing) We welcome contributions to SkipModel. The Skip product [documentation](https://skip.dev/docs/contributing/) includes helpful instructions and tips on local Skip library development. When submitting code, please include unit tests in your [PR ↗](https://source.skip.dev/skip-model/pulls) . Model Objects ------------- [Section titled “Model Objects”](https://skip.dev/docs/modules/skip-model/#model-objects) Like Skip itself, SkipModel objects are dual-platform! Not only do your `@Observable` and `ObservableObject` properties participate in SwiftUI state tracking, but they are tracked by Compose as well. The Skip transpiler backs your observable properties with `MutableState` values in Kotlin, so Compose automatically tracks reads and writes and [performs recomposition as needed ↗](https://developer.android.com/jetpack/compose/state) . This means that you can write shared model-layer Swift code using observable objects, and use it to power both SwiftUI (whether iOS-only or dual-platform with Skip) as well as pure Android Compose UI code. For example, the following model class: @Observable class TapCounter { var tapCount = 0} could power a Compose UI: val tapCounter = TapCounter()...TapIt(counter = tapCounter)...@Composable fun TapIt(counter: TapCounter) { Button(onClick = { counter.tapCount += 1 }) { Text("Tap Count: ${counter.tapCount}") }} API Support ----------- [Section titled “API Support”](https://skip.dev/docs/modules/skip-model/#api-support) The following table summarizes SkipModel’s API support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within `#if !SKIP` blocks - you can use any Swift API you want. Additionally: * In all Combine publishes and related API, the `Failure` type must be `Never`: throwing errors in Combine chains is not supported. * In Skip, Combine is **not** automatically imported when you `import Foundation`. Make sure to `import Combine` or `import SwiftUI` explicitly. Support levels: * ✅ – Full * 🟢 – High * 🟡 – Medium * 🟠 – Low | Support | API | | --- | --- | | 🟢 | `AnyCancellable`

* See `Cancellable` | | 🟠 | `AnyPublisher`

* `init(_ publisher: Publisher)`
* See `Publisher` | | 🟢 | `Cancellable`

* The `store(in:)` function only supports a `Set` | | 🟠 | `ConnectablePublisher`

* `func connect()`
* `func autoconnect()`
* See `Publisher` | | ✅ | `func NotificationCenter.publisher(for: Notification.Name, object: Any? = nil): Publisher` | | 🟢 | `@Observable`

* Skip does not support calls to the generated `access(keyPath:)` and `withMutation(keyPath:_:)` functions | | 🟢 | `ObservableObject`

* If you declare your own `objectWillChange` publisher, it must be of type `ObservableObjectPublisher` | | 🟠 | `ObservableObjectPublisher`

* `func send()`
* See `Publisher` | | ✅ | `@ObservationIgnored` | | 🟠 | `PassthroughSubject`

* `func send(value: Output)`
* See `Publisher` | | ✅ | `@Published` | | 🟠 | `Publisher`

* `func assign(to: KeyPath, on: Root) -> AnyCancellable`
* `func sink(receiveValue: (Output) -> Unit) -> AnyCancellable`
* `func combineLatest(_ with: Publisher) -> Publisher`
* `func combineLatest3(_ with0: Publisher, _ with1: Publisher) -> Publisher`
* `func combineLatest4(_ with0: Publisher, _ with1: Publisher, _ with2: Publisher) -> Publisher`
* `func debounce(for: Double, scheduler: Scheduler) -> Publisher`
* `func dropFirst(count: Int = 1) -> Publisher`
* `func filter(isIncluded: (Output) -> Boolean) -> Publisher`
* `func map(transform: (Output) -> T) -> Publisher`
* `func receive(on: Scheduler): Publisher`
* `func eraseToAnyPublisher(): AnyPublisher` | | ✅ | `func Timer.publish(every: TimeInterval, tolerance: TimeInterval? = nil, on runLoop: RunLoop, in mode: RunLoop.Mode, options: RunLoop.SchedulerOptions? = nil) -> ConnectablePublisher` | --- # SkipLib | Skip [Skip to content](https://skip.dev/docs/modules/skip-lib/#_top) SkipLib ======= Swift standard library for [Skip Lite](https://skip.dev/docs/status#skip_lite) transpiled Swift. See what API is currently implemented [here](https://skip.dev/docs/modules/skip-lib/#swift-standard-library-support) . About ----- [Section titled “About”](https://skip.dev/docs/modules/skip-lib/#about) SkipLib vends the `skip.lib` Kotlin package. It serves two purposes: 1. SkipLib is a reimplementation of the Swift standard library for Kotlin on Android. Its goal is to mirror as much of the Swift standard library as possible, allowing Skip developers to use Swift standard library API with confidence. 2. SkipLib contains custom Kotlin API that the Skip transpiler takes advantage of when translating your Swift source to the equivalent Kotlin code. For example, the Kotlin language does not have tuples. Instead, SkipLib’s `Tuple.kt` defines bespoke Kotlin `Tuple` classes. When the transpiler translates Swift code that references tuples, it uses these `Tuple` classes in the Kotlin it generates. Dependencies ------------ [Section titled “Dependencies”](https://skip.dev/docs/modules/skip-lib/#dependencies) SkipLib depends on the [skip ↗](https://source.skip.dev/skip) transpiler plugin and has no additional library dependencies. It is part of the core _Skip Core Frameworks_ and is not intended to be imported directly. The module is transparently adopted through the automatic addition of `import skip.lib.*` to transpiled files by the Skip transpiler. Status ------ [Section titled “Status”](https://skip.dev/docs/modules/skip-lib/#status) * SkipLib’s Swift symbol files (see [Implementation Strategy](https://skip.dev/docs/modules/skip-lib/#implementation-strategy) ) are nominally complete. They should declare all Swift standard library API. This is difficult to validate, however, so if you find anything missing, please [report it ↗](https://source.skip.dev/skip-lib/issues) to us. * Unimplemented API is appropriately marked with `@available(*, unavailable)` annotations. Skip will generate an error when you attempt to use an unimplemented API. * In particular, a significant portion of the [collections](https://skip.dev/docs/modules/skip-lib/#collections) API is not yet implemented. * Unit testing is not comprehensive. See [Swift Standard Library Support](https://skip.dev/docs/modules/skip-lib/#swift-standard-library-support) . Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-lib/#contributing) We welcome contributions to SkipLib. The Skip product [documentation](https://skip.dev/docs/contributing/) includes helpful instructions and tips on local Skip library development. The most pressing need is to reduce the amount of unimplemented API. To help fill in unimplemented API in SkipLib: 1. Find unimplemented API. Unimplemented API should be marked with `@available(*, unavailable)` in the Swift symbol files. 2. Write an appropriate Kotlin implementation. See [Implementation Strategy](https://skip.dev/docs/modules/skip-lib/#implementation-strategy) below. For [collections](https://skip.dev/docs/modules/skip-lib/#collections) API, make sure your implementation is duplicated for `String` as well. 3. Write unit tests. 4. [Submit a PR. ↗](https://source.skip.dev/skip-lib/pulls) Other forms of contributions such as test cases, comments, and documentation are also welcome! Implementation Strategy ----------------------- [Section titled “Implementation Strategy”](https://skip.dev/docs/modules/skip-lib/#implementation-strategy) Apart from the Skip transpiler itself, SkipLib implements the lowest levels of the Swift language. Its implementation strategy, therefore, differs from other Skip libraries. Most Skip libraries _call_ Kotlin API, but are _written_ in Swift, relying on the Skip transpiler for translation to Kotlin. Most of SkipLib, however, is written in pure Kotlin. Consider SkipLib’s implementation of Swift’s `Array`. SkipLib divides its `Array` support into two files: 1. `Sources/SkipLib/Array.swift` acts as a Swift header file, declaring the `Array` type’s Swift API but stubbing out the implementation. The `// SKIP SYMBOLFILE` comment at the top of the file marks it as such. Read more about special Skip comments in the Skip product [documentation](https://skip.dev/docs/platformcustomization/#skip-comments) . 2. `Sources/SkipLib/Skip/Array.kt` contains the actual `Array` implementation in Kotlin. This pattern is used for most Swift types throughout SkipLib. Meanwhile, SwiftLib implementations of constructs built directly into the Swift language - e.g. tuples or `inout` parameters - only have a Kotlin file, with no corresponding Swift symbol file. Swift Standard Library Support ------------------------------ [Section titled “Swift Standard Library Support”](https://skip.dev/docs/modules/skip-lib/#swift-standard-library-support) The following table summarizes SkipLib’s Swift Standard Library API support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within `#if !SKIP` blocks - you can use any API you want. Support levels: * ✅ – Full * 🟢 – High * 🟡 - Medium * 🟠 – Low | Support | API | | --- | --- | | 🟢 | `Actor`

* Non-private mutable properties are not supported | | ✅ | `Any` | | ✅ | `AnyActor` | | ✅ | `AnyHashable` | | ✅ | `AnyObject` | | 🟢 | `Array`

* `init()`
* `init(repeating: Element, count: Int)`
* `init(_ sequence: any Sequence)`
* See `Collection` for collection API support | | ✅ | `assert` | | ✅ | `assertionFailure` | | ✅ | `AsyncSequence` | | ✅ | `AsyncStream`

* When invoking the `init(unfolding:)` constructor, use a labeled argument rather than a trailing closure | | 🟢 | `Bool`

* `static func random() -> Bool`
* `static func random(using gen: inout RandomNumberGenerator) -> Bool` | | ✅ | `CaseIterable` | | ✅ | `CGAffineTransform` | | ✅ | `CGFloat` | | ✅ | `CGPoint` | | ✅ | `CGRect` | | ✅ | `CGSize` | | 🟢 | `Character`

* `init(_: Character)`
* `init(_: String)`
* `var isNewline: Bool`
* `var isWhitespace: Bool`
* `var isUppercase: Bool`
* `var isLowercase: Bool`
* `func lowercased() -> String`
* `func uppercased() -> String` | | 🟢 | `Codable`

* See [Codable](https://skip.dev/docs/modules/skip-lib/#codable) | | 🟡 | `Collection`

* Note: This list represents the combined supported API of Swift's many collection types: `Sequence`, `Collection`, `BidirectionalCollection`, etc
* `func allSatisfy(_ predicate: (Element) throws -> Bool) rethrows -> Bool`
* `mutating func append(_ newElement: Element)`
* `mutating func append(contentsOf newElements: any Sequence)`
* `func contains(_ element: Element) -> Bool`
* `func contains(where predicate: (Element) throws -> Bool) rethrows -> Bool`
* `func distance(from start: Int, to end: Int) -> Int`
* `func drop(while predicate: (Element) throws -> Bool) rethrows -> [Element]`
* `func dropFirst(_ k: Int = 1) -> [Element]`
* `func dropLast(_ k: Int = 1) -> [Element]`
* `func elementsEqual(_ other: any Sequence) -> Bool`
* `func elementsEqual(_ other: any Sequence, by areEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool`
* `func enumerated() -> any Sequence<(offset: Int, element: Element)>`
* `var endIndex: Int`
* `func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]`
* `func first(where predicate: (Element) throws -> Bool) rethrows -> Element?`
* `func firstIndex(of element: Element) -> Int?`
* `func firstIndex(where predicate: (Element) throws -> Bool) rethrows -> Int?`
* `var first: Element?`
* `func flatMap(_ transform: (Element) throws -> any Sequence) rethrows -> [RE]`
* `func formIndex(_ i: inout Int, offsetBy distance: Int)`
* `func formIndex(after i: inout Int)`
* `func index(_ i: Int, offsetBy distance: Int) -> Int`
* `func index(after i: Int) -> Int`
* `var indices: any Sequence`
* `var isEmpty: Bool`
* `func joined() -> [RE] where Element: Sequence`
* `func joined(separator: any Sequence) -> [RE] where Element: Sequence`
* `func joined(separator: String) -> String`
* `func makeIterator() -> any IteratorProtocol`
* `func map(_ transform: (Element) throws -> RE) rethrows -> [RE]`
* `func max() -> Element?`
* `func max(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Element?`
* `func min() -> Element?`
* `func min(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Element?`
* `var underestimatedCount: Int`
* `func prefix(_ maxLength: Int) -> [Element]`
* `func prefix(through end: Int) -> [Element]`
* `func prefix(upTo end: Int) -> [Element]`
* `mutating func popFirst() -> Element?`
* `mutating func popLast() -> Element?`
* `func randomElement() -> Element?`
* `func randomElement(using generator: inout any RandomNumberGenerator) -> Element?`
* `func reduce(_ initialResult: R, _ nextPartialResult: (_ partialResult: R, Element) throws -> R) rethrows -> R`
* `func reduce(into initialResult: R, _ updateAccumulatingResult: (_ partialResult: inout R, Element) throws -> Void) rethrows -> R`
* `func remove(at i: Int) -> Element`
* `mutating func removeAll(keepingCapacity keepCapacity: Bool = false)`
* `mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows`
* `mutating func removeFirst() -> Element`
* `mutating func removeFirst(_ k: Int)`
* `mutating func removeLast() -> Element`
* `mutating func removeLast(_ k: Int)`
* `mutating func reverse()`
* `func reversed() -> [Element]`
* `mutating func shuffle()`
* `mutating func shuffle(using generator: inout T)`
* `mutating func sort()`
* `mutating func sort(by areIncreasingOrder: (Element, Element) throws -> Bool) rethrows`
* `mutating func swapAt(_ i: Int, _ j: Int)`
* `subscript(bounds: Range) -> any Collection`
* `subscript(position: Int) -> Element`
* `func starts(with possiblePrefix: Any) -> Bool`
* `func starts(with possiblePrefix: Any, by areEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool`
* `func suffix(from start: Int) -> [Element]`
* `func suffix(_ maxLength: Int) -> [Element]`
* `func sorted() -> [Element]`
* `func sorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]`
* `var startIndex: Int`
* `var count: Int`
* `func count(where: (E) throws -> Bool) rethrows -> Int`
* `func withContiguousStorageIfAvailable(_ body: (Any) throws -> R) rethrows -> R?`
* `func forEach(_ body: (Element) throws -> Void) rethrows`
* `func drop(while predicate: (Element) throws -> Bool) rethrows -> [Element]`
* `func dropFirst(_ k: Int = 1) -> [Element]`
* `func dropLast(_ k: Int = 1) -> [Element]`
* `func enumerated() -> any Sequence<(offset: Int, element: Element)>`
* `func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]`
* `func first(where predicate: (Element) throws -> Bool) rethrows -> Element?`
* `func map(_ transform: (Element) throws -> RE) rethrows -> [RE]`
* `func max(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Element?`
* `func min(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Element?`
* `func reduce(_ initialResult: R, _ nextPartialResult: (_ partialResult: R, Element) throws -> R) rethrows -> R`
* `func reduce(into initialResult: R, _ updateAccumulatingResult: (_ partialResult: inout R, Element) throws -> Void) rethrows -> R`
* `func reversed() -> [Element]`
* `func shuffled() -> [Element]`
* `func shuffled(using generator: inout T) -> [Element]`
* `func flatMap(_ transform: (Element) throws -> any Sequence) rethrows -> [RE]`
* `func compactMap(_ transform: (Element) throws -> RE?) rethrows -> [RE]`
* `func sorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]`
* `func joined() -> [RE] where Element: Sequence`
* `func joined(separator: any Sequence) -> [RE] where Element: Sequence`
* `func joined(separator: String) -> String`
* `func starts(with possiblePrefix: Any) -> Bool`
* `func contains(_ element: Element) -> Bool`
* `func min() -> Element?`
* `func max() -> Element?`
* `func sorted() -> [Element]`
* `var startIndex: Int`
* `var endIndex: Int`
* `var indices: any Sequence`
* `func index(_ i: Int, offsetBy distance: Int) -> Int`
* `func distance(from start: Int, to end: Int) -> Int`
* `func index(after i: Int) -> Int`
* `func formIndex(after i: inout Int)`
* `func formIndex(_ i: inout Int, offsetBy distance: Int)`
* `func randomElement() -> Element?`
* `func randomElement(using generator: inout any RandomNumberGenerator) -> Element?`
* `mutating func popFirst() -> Element?`
* `var first: Element?`
* `func prefix(upTo end: Int) -> [Element]`
* `func suffix(from start: Int) -> [Element]`
* `func prefix(through end: Int) -> [Element]`
* `mutating func removeFirst() -> Element`
* `mutating func removeFirst(_ k: Int)`
* `func firstIndex(of element: Element) -> Int?`
* `func firstIndex(where predicate: (Element) throws -> Bool) rethrows -> Int?`
* `mutating func shuffle()`
* `mutating func shuffle(using generator: inout T)`
* `mutating func sort()`
* `mutating func sort(by areIncreasingOrder: (Element, Element) throws -> Bool) rethrows`
* `mutating func reverse()`
* `mutating func swapAt(_ i: Int, _ j: Int)`
* `mutating func append(_ newElement: Element)`
* `mutating func append(contentsOf newElements: any Sequence)`
* `mutating func insert(_ newElement: Element, at i: Int)`
* `mutating func insert(contentsOf newElements: any Sequence, at i: Int)`
* `mutating func remove(at i: Int) -> Element`
* `mutating func removeAll(keepingCapacity keepCapacity: Bool = false)`
* `mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows`
* `mutating func popLast() -> Element?`
* `mutating func removeLast() -> Element`
* `mutating func removeLast(_ k: Int)`
* `subscript(bounds: Range) -> any Collection`
* `subscript(position: Int) -> Element` | | ✅ | `Comparable` | | ✅ | `CustomDebugStringConvertible` | | ✅ | `CustomStringConvertible` | | 🟢 | `Decodable`

* See [Codable](https://skip.dev/docs/modules/skip-lib/#codable) | | 🟢 | `Dictionary`

* `init()`
* `init(minimumCapacity: Int)`
* `init(uniqueKeysWithValues keysAndValues: any Sequence<(Key, Value)>)`
* `func filter(_ isIncluded: ((Key, Value)) throws -> Bool) rethrows -> Dictionary`
* `subscript(key: Key) -> Value?`
* `subscript(key: Key, default defaultValue: Value) -> Value`
* `func mapValues(_ transform: (Value) throws -> T) rethrows -> Dictionary`
* `func compactMapValues(_ transform: (Value) throws -> T?) rethrows -> Dictionary`
* `mutating func updateValue(_ value: Value, forKey key: Key) -> Value?`
* `mutating func removeValue(forKey key: Key) -> Value?`
* `var keys: any Collection)`
* `var values: any Collection`
* `mutating func removeAll(keepingCapacity keepCapacity: Bool = false)`
* See `Collection` for collection API support | | ✅ | `DiscardingTaskGroup` | | 🟢 | `Double`

* `static var nan: Double`
* `static var infinity: Double`
* `static var pi: Double`
* `var isNan: Bool`
* `var isFinite: Bool`
* `var isInfinite: Bool`
* `static func random(in range: Range) -> Double`
* `func rounded() -> Double`
* `func rounded(_ rule: FloatingPointRoundingRule) -> Double` | | 🟢 | `Encodable`

* See \[Codable\](#codable) | | ✅ | `Equatable` | | ✅ | `Error` | | ✅ | `fatalError` | | 🟢 | `Float`

* `static var nan: Float`
* `static var infinity: Float`
* `static var pi: Float`
* `var isNan: Bool`
* `var isFinite: Bool`
* `var isInfinite: Bool`
* `static func random(in range: Range) -> Float`
* `func rounded() -> Float`
* `func rounded(_ rule: FloatingPointRoundingRule) -> Float` | | ✅ | `Hashable` | | ✅ | `Hasher` | | ✅ | `Identifiable` | | 🟢 | `Int8`

* `static var min: Int8`
* `static var max: Int8`
* `static func random(in range: Range) -> Int8`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> Int8` | | 🟢 | `Int16`

* `static var min: Int16`
* `static var max: Int16`
* `static func random(in range: Range) -> Int16`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> Int16` | | 🟢 | `Int32`

* `static var min: Int32`
* `static var max: Int32`
* `static func random(in range: Range) -> Int32`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> Int32` | | 🟢 | `Int`

* Kotlin `Ints` are 32 bit
* `static var min: Int`
* `static var max: Int`
* `static func random(in range: Range) -> Int`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> Int` | | 🟢 | `Int64`

* `static var min: Int64`
* `static var max: Int64`
* `static func random(in range: Range) -> Int64`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> Int64` | | 🟢 | `@MainActor` | | 🟢 | `MainActor`

* `static func run(body: () throws -> T) async -> T` | | 🟢 | `math.h`

* `var M_E: Double`
* `var M_LOG2E: Double`
* `var M_LOG10E: Double`
* `var M_LN2: Double`
* `var M_LN10: Double`
* `var M_PI: Double`
* `func acosf(_ x: Float) -> Float`
* `func acos(_ x: Double) -> Double`
* `func acosl(_ x: Double) -> Double`
* `func asinf(_ x: Float) -> Float`
* `func asin(_ x: Double) -> Double`
* `func asinl(_ x: Double) -> Double`
* `func atanf(_ x: Float) -> Float`
* `func atan(_ x: Double) -> Double`
* `func atanl(_ x: Double) -> Double`
* `func atan2f(_ x: Float, _ y: Float) -> Float`
* `func atan2(_ x: Double, _ y: Double) -> Double`
* `func atan2l(_ x: Double, _ y: Double) -> Double`
* `func cosf(_ x: Float) -> Float`
* `func cos(_ x: Double) -> Double`
* `func cosl(_ x: Double) -> Double`
* `func sinf(_ x: Float) -> Float`
* `func sin(_ x: Double) -> Double`
* `func sinl(_ x: Double) -> Double`
* `func tanf(_ x: Float) -> Float`
* `func tan(_ x: Double) -> Double`
* `func tanl(_ x: Double) -> Double`
* `func acoshf(_ x: Float) -> Float`
* `func acosh(_ x: Double) -> Double`
* `func acoshl(_ x: Double) -> Double`
* `func asinhf(_ x: Float) -> Float`
* `func asinh(_ x: Double) -> Double`
* `func asinhl(_ x: Double) -> Double`
* `func atanhf(_ x: Float) -> Float`
* `func atanh(_ x: Double) -> Double`
* `func atanhl(_ x: Double) -> Double`
* `func coshf(_ x: Float) -> Float`
* `func cosh(_ x: Double) -> Double`
* `func coshl(_ x: Double) -> Double`
* `func sinhf(_ x: Float) -> Float`
* `func sinh(_ x: Double) -> Double`
* `func sinhl(_ x: Double) -> Double`
* `func tanhf(_ x: Float) -> Float`
* `func tanh(_ x: Double) -> Double`
* `func tanhl(_ x: Double) -> Double`
* `func expf(_ x: Float) -> Float`
* `func exp(_ x: Double) -> Double`
* `func expl(_ x: Double) -> Double`
* `func exp2f(_ x: Float) -> Float`
* `func exp2(_ x: Double) -> Double`
* `func exp2l(_ x: Double) -> Double`
* `func expm1f(_ x: Float) -> Float`
* `func expm1(_ x: Double) -> Double`
* `func expm1l(_ x: Double) -> Double`
* `func logf(_ x: Float) -> Float`
* `func log(_ x: Double) -> Double`
* `func logl(_ x: Double) -> Double`
* `func log10f(_ x: Float) -> Float`
* `func log10(_ x: Double) -> Double`
* `func log10l(_ x: Double) -> Double`
* `func log2f(_ x: Float) -> Float`
* `func log2(_ x: Double) -> Double`
* `func log2l(_ x: Double) -> Double`
* `func log1pf(_ x: Float) -> Float`
* `func log1p(_ x: Double) -> Double`
* `func log1pl(_ x: Double) -> Double`
* `func logbf(_ x: Float) -> Float`
* `func logb(_ x: Double) -> Double`
* `func logbl(_ x: Double) -> Double`
* `func abs(_ x: Double) -> Double`
* `func abs(_ x: Int) -> Int`
* `func abs(_ x: Int64) -> Int64`
* `func fabsf(_ x: Float) -> Float`
* `func fabs(_ x: Double) -> Double`
* `func fabsl(_ x: Double) -> Double`
* `func cbrtf(_ x: Float) -> Float`
* `func cbrt(_ x: Double) -> Double`
* `func cbrtl(_ x: Double) -> Double`
* `func hypotf(_ x: Float, _ y: Float) -> Float`
* `func hypot(_ x: Double, _ y: Double) -> Double`
* `func hypotl(_ x: Double, _ y: Double) -> Double`
* `func powf(_ x: Float, _ y: Float) -> Float`
* `func pow(_ x: Double, _ y: Double) -> Double`
* `func powl(_ x: Double, _ y: Double) -> Double`
* `func sqrtf(_ x: Float) -> Float`
* `func sqrt(_ x: Double) -> Double`
* `func sqrtl(_ x: Double) -> Double`
* `func ceilf(_ x: Float) -> Float`
* `func ceil(_ x: Double) -> Double`
* `func ceill(_ x: Double) -> Double`
* `func floorf(_ x: Float) -> Float`
* `func floor(_ x: Double) -> Double`
* `func floorl(_ x: Double) -> Double`
* `func roundf(_ x: Float) -> Float`
* `func round(_ x: Double) -> Double`
* `func roundl(_ x: Double) -> Double`
* `func fmodf(_ x: Float, _ y: Float) -> Float`
* `func fmod(_ x: Double, _ y: Double) -> Double`
* `func fmodl(_ x: Double, _ y: Double) -> Double`
* `func remainderf(_ x: Float, _ y: Float) -> Float`
* `func remainder(_ x: Double, _ y: Double) -> Double`
* `func remainderl(_ x: Double, _ y: Double) -> Double`
* `func fmaxf(_ x: Float, _ y: Float) -> Float`
* `func fmax(_ x: Double, _ y: Double) -> Double`
* `func fmaxl(_ x: Double, _ y: Double) -> Double`
* `func fminf(_ x: Float, _ y: Float) -> Float`
* `func fmin(_ x: Double, _ y: Double) -> Double`
* `func fminl(_ x: Double, _ y: Double) -> Double` | | ✅ | `max(_:_:)` | | ✅ | `min(_:_:)` | | ✅ | `ObjectIdentifier` | | ✅ | `OptionSet` | | ✅ | `precondition` | | ✅ | `preconditionFailure` | | ✅ | `RandomNumberGenerator` | | 🟠 | `Range`

* Only `Range` is generally supported
* `var lowerBound: Bound`
* `var upperBound: Bound`
* `func contains(_ element: Bound) -> Bool`
* `var isEmpty: Bool`
* `func map(_ transform: (Bound) throws -> RE) rethrows -> [RE]` | | ✅ | `RawRepresentable` | | 🟠 | `Regex`

* `init(_ string: String)`
* `func matches(_ string: String) -> [Match]`
* `func replace(_ string: String, with replacement: String) -> String` | | 🟠 | `Regex.Match`

* `var count: Int`
* `subscript(index: Int) -> MatchGroup` | | 🟠 | `Regex.MatchGroup`

* `var substring: Substring?` | | ✅ | `Result` | | 🟡 | `swap(_:_:)`

* Does not support swapping values in arrays and other data structures | | 🟢 | `Set`

* `init()`
* `init(_ sequence: any Sequence)`
* See `Collection`
* See `SetAlgebra` | | 🟢 | `SetAlgebra`

* `func contains(_ element: Element) -> Bool`
* `func union(_ other: Self) -> Self`
* `func intersection(_ other: Self) -> Self`
* `func symmetricDifference(_ other: Self) -> Self`
* `mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element)`
* `mutating func remove(_ member: Element) -> Element?`
* `mutating func update(with newMember: Element) -> Element?`
* `mutating func formUnion(_ other: Self)`
* `mutating func formIntersection(_ other: Self)`
* `mutating func formSymmetricDifference(_ other: Self)`
* `func subtracting(_ other: Self) -> Self`
* `func isSubset(of other: Self) -> Bool`
* `func isDisjoint(with other: Self) -> Bool`
* `func isSuperset(of other: Self) -> Bool`
* `var isEmpty: Bool`
* `mutating func subtract(_ other: Self)`
* `func isStrictSubset(of other: Self) -> Bool`
* `func isStrictSuperset(of other: Self) -> Bool` | | 🟢 | `String`

* Kotlin strings are \*\*not\*\* mutable
* `init(data: Data, encoding: StringEncoding)`
* `init(bytes: [UInt8], encoding: StringEncoding)`
* `init(contentsOf: URL)`
* `var capitalized: String`
* `var deletingLastPathComponent: String`
* `func replacingOccurrences(of search: String, with replacement: String) -> String`
* `func components(separatedBy separator: String) -> [String]`
* `func trimmingCharacters(in set: CharacterSet) -> String`
* `var utf8Data: Data`
* `func data(using: StringEncoding, allowLossyConversion: Bool = true) -> Data?`
* `var utf8: [UInt8]`
* `var utf16: [UInt8]`
* `var unicodeScalars: [UInt8]`
* See `Collection`
* See `SkipFoundation` for additional string API from `Foundation` | | ✅ | `strlen` | | ✅ | `strncmp` | | 🟢 | `Substring`

* See `String` | | ✅ | `SystemRandomNumberGenerator` | | 🟡 | `Task`

* `init(priority: TaskPriority? = nil, operation: @escaping () async throws -> Success)`
* `static func detached(priority: TaskPriority? = nil, operation: @escaping () async -> Success) -> Task`
* `var value: Success`
* `func cancel()`
* `static func yield() async`
* `var isCancelled: Bool`
* `static var isCancelled: Bool`
* `static func checkCancellation() throws`
* `static func sleep(nanoseconds duration: UInt64) async throws`
* `static var min: UInt8` | | ✅ | `TaskGroup` | | ✅ | `ThrowingDiscardingTaskGroup` | | ✅ | `ThrowingTaskGroup` | | ✅ | `type(of:)` | | 🟢 | `UInt8`

* `static var min: UInt8`
* `static var max: UInt8`
* `static func random(in range: Range) -> UInt8`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> UInt8` | | 🟢 | `UInt16`

* `static var min: UInt16`
* `static var max: UInt16`
* `static func random(in range: Range) -> UInt16`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> UInt16` | | 🟢 | `UInt32`

* `static var min: UInt32`
* `static var max: UInt32`
* `static func random(in range: Range) -> UInt32`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> UInt32` | | 🟢 | `UInt`

* Kotlin `UInts` are 32 bit
* `static var min: UInt`
* `static var max: UInt`
* `static func random(in range: Range) -> UInt`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> UInt` | | 🟢 | `UInt64`

* `static var min: UInt64`
* `static var max: UInt64`
* `static func random(in range: Range) -> UInt64`
* `static func random(in range: Range, using gen: inout RandomNumberGenerator) -> UInt64` | | ✅ | `withDiscardingTaskGroup` | | ✅ | `withTaskCancellationHandler` | | ✅ | `withThrowingTaskGroup` | | ✅ | `withThrowingDiscardingTaskGroup` | | ✅ | `withThrowingTaskGroup` | Topics ------ [Section titled “Topics”](https://skip.dev/docs/modules/skip-lib/#topics) ### Collections [Section titled “Collections”](https://skip.dev/docs/modules/skip-lib/#collections) Collections are perhaps the most complex part of the Swift standard library, and of SkipLib. Swift’s comprehensive collection protocols allow `Array`, `Set`, `Dictionary`, `String`, and other types to all share a common set of API, including iteration, `map`, `reduce`, and much more. Corresponding Kotlin types - `List`, `Set`, `Map`, `String`, etc - do not share a similarly rich API set. As a result, SkipLib must duplicate collection protocol implementations in both `Collections.kt` and `String.kt`, and must duplicate `SetAlgebra` implementations in both `Set.kt` and `OptionSet.kt`. See the explanatory comments in `Collections.kt` for more information on the design of SkipLib’s internal collections support. #### Converting between Skip and Kotlin collections [Section titled “Converting between Skip and Kotlin collections”](https://skip.dev/docs/modules/skip-lib/#converting-between-skip-and-kotlin-collections) It is important to note that Skip’s `skip.lib.Array` is not the same as a Java/Kotlin `kotlin.Array`, so if you need to pass a Skip array into a Java API that expects a Java-style Array, you need to convert it like so: #if SKIPlet skipArray: skip.lib.Array = ["ABC", "DEF"]let kotlinList: kotlin.collections.List = skipArray.toList()let kotlinArray: kotlin.Array = kotlinList.toTypedArray()java.util.Arrays.sort(kotlinArray) // same as a Java array#endif ### Codable [Section titled “Codable”](https://skip.dev/docs/modules/skip-lib/#codable) Skip is able to synthesize default `Codable` conformance for the Android versions of your Swift types. The Android versions will encode and decode exactly like their Swift source types. Skip also supports your custom `CodingKeys` as well as your custom `encode(to:)` and `init(from:)` functions for encoding and decoding. There are, however, a few restrictions: * Skip cannot synthesize `Codable` conformance for enums that are not `RawRepresentable`. You must implement the required protocol functions yourself. * If you implement your own `encode` function or `init(from:)` decoding constructor and you use `CodingKeys`, you must declare your own `CodingKeys` enum. You cannot rely on the synthesized enum. * `Array`, `Set`, and `Dictionary` are fully supported, but nesting of these types is limited. So for example Skip can encode and decode `Array` and `Dictionary`, but not `Array>`. Two forms of container nesting **are** currently supported: arrays-of-arrays - e.g. `Array>` - and dictionaries-of-array-values - e.g. `Dictionary>`. In practice, other nesting patters are rare. * When calling `decode`, **you must supply a concrete type literal to decode**. This applies to both top-level `Decoders` like `JSONDecoder` as well as containers like `KeyedDecodingContainer`. The following will work: let object = try decoder.decode(MyType.self, from: jsonData) But these examples will not work: let type = MyType.selflet object = try decoder.decode(type, from: jsonData) // T is a generic typelet object = try decoder.decode(T.self, from: jsonData) It is common for developers to take advantage of `Decodable`\-typed generic functions to be able to decode arbitrary types, so this last limitation is the most onerous. You must consider it when writing your decoding code, and it often requires refactoring existing decoding code being ported to Skip. One mechanism to ease this restriction and allow you to decode unknown generic types is to write `inline` decoding functions that take advantage of Kotlin’s _reified types_. Inline functions, however, come with their own limitations and tradeoffs. You can read more about this topic in the [Kotlin language documentation ↗](https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters) . Skip automatically converts any Swift function with the `@inline(__always)` attribute into a Kotlin inline function with reified generics. For example, a function like the following **will work** with Skip, so long as you call it with a concrete `Response` type or with a generic `Response` type from another `inline` function: @inline(__always) public func send(request: R) async throws -> Response { let data = try await download(request: request) return try jsonDecoder.decode(Response.self, from: data)} It transpiles to code like: inline suspend fun send(request: R): Response where R: Request, Response: Decodable { val data = download(request = request) return jsonDecoder.decode(Response::class, from = data)} --- # RevenueCat | Skip [Skip to content](https://skip.dev/docs/modules/skip-revenue/#_top) RevenueCat ========== This is a free Skip Swift/Kotlin library project containing the following modules: SkipRevenueUI SkipRevenue Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-revenue/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-revenue/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Porting Swift Packages to Android | Skip [Skip to content](https://skip.dev/docs/porting/#_top) Porting Swift Packages to Android ================================= The [Swift Package Index ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) site tracks thousands of Swift packages already building for Android. Porting your own packages or packages you depend on is often a straightforward process. This guide covers what our experience porting Swift packages has taught us, and how you can apply this knowledge to turn your parochial Apple-only Swift package into a universal multi-platform package that can build for not just iOS and macOS, but also for Android. Package Prerequisites --------------------- [Section titled “Package Prerequisites”](https://skip.dev/docs/porting/#package-prerequisites) What sorts of Swift packages are good candidates for Android? The best litmus test is whether the package offers _general-purpose_ functionality, as opposed to having an integral dependency on iOS-specific frameworks. Some examples of good candidates are: * Business logic * Algorithms and generic data structures * Networking utilities * Online web service and API clients * Data persistence * Parsers and formatters On the flip side, examples of packages that would be challenging to bring to Android might be: * Custom UIKit components * HealthKit, CarPlay, Siri integration libraries * Other Apple-specific \*Kit library integrations Now, just because a Swift package is designed to work with an Apple-specific framework doesn’t mean that it is impossible to port to Android. It just means that it would be a signifiant amount of work and involve creating a bridge to the equivalent Kotlin or Java framework. This is by all means possible, but the subject of this guide is how to bring _naturally portable_ Swift packages to Android. Setup and Building ------------------ [Section titled “Setup and Building”](https://skip.dev/docs/porting/#setup-and-building) Say you have a [conventional Swift package ↗](https://developer.apple.com/documentation/xcode/swift-packages) that contains a `Package.swift` file at the root and has the usual `Sources/` and (hopefully) `Tests/` folders that contain the individual targets and source files. Does running `swift build` and `swift test` in the package directory work from the Terminal? If so, then you already have a cross-platform package that works on multiple platforms: iOS and macOS! That, by itself, is a good sign that your package might be suitable for Android. Many frameworks that are available on iOS are not present on macOS, so either your package doesn’t use too many iOS frameworks, or it is smart enough to only reference them conditionally (more on that below). But how do we build and test for Android? First, [install Skip and the native Android SDK](https://skip.dev/docs/gettingstarted) . Then try to build your Swift package with the Android toolchain. The very abbreviated quick start looks like: $ brew install skiptools/skip/skip …skip was successfully installed! $ skip android sdk install [✓] Install Swift Android SDK (2.4s) $ cd MySwiftPackage/$ skip android build Building for debugging...[0/2] Write sources…[4/4] Emitting module DemoPackageBuild complete! (1.85s) If you see “Build complete!” then congratulations! Your package already builds for Android, and you can move on to the [Testing](https://skip.dev/docs/porting/#testing) section. But if you encounter errors from the build command, you will need to _port_ your package over to Android. Read on… Porting your Swift Package -------------------------- [Section titled “Porting your Swift Package”](https://skip.dev/docs/porting/#porting-your-swift-package) Wikipedia defines [porting ↗](https://en.wikipedia.org/wiki/Porting) as the “process of adapting software for the purpose of achieving some form of execution in a computing environment that is different from the one that a given program (meant for such execution) was originally designed for”. In other words, you made your Swift package with iOS in mind, and now you want it to work on Android. The following sections will go over some of the most common issues you may hit when first trying to build your package on this new platform. ### Conditionally Importing and Using Platform-Specific Modules [Section titled “Conditionally Importing and Using Platform-Specific Modules”](https://skip.dev/docs/porting/#conditionally-importing-and-using-platform-specific-modules) Suppose your Swift package defines an `Event` protocol with a simple default implementation: protocol Event { var dateRange: Range { get } var isConfirmed: Bool { get }} struct SimpleEvent : Hashable, Codable { let start, end: Date let confirmed} extension SimpleEvent : Event { var dateRange: Range { self.start.. { self.startDate.. { get } var isConfirmed: Bool { get }} struct SimpleEvent : Hashable, Codable { let start, end: Date let confirmed} extension SimpleEvent : Event { var dateRange: Range { self.start.. { self.startDate.. Item { let (data, response) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(Item.self, from: data)} You may be surprised to see this fail to compile for Android: $ skip android build Building for debugging...Fetcher.swift:9:49: error: type 'URLSession' (aka 'AnyObject') has no member 'shared' 7 | 8 | func fetch(_ url: URL) async throws -> Item { 9 | let (data, response) = try await URLSession.shared.data(from: url) | `- error: type 'URLSession' (aka 'AnyObject') has no member 'shared'10 | return try JSONDecoder().decode(Item.self, from: data)11 | } This somewhat confusing error message just means that it can’t find the `URLSession` type, because it isn’t present in the `Foundation` module on Android. On Darwin platforms (macOS, iOS, and other Apple operating systems), the `Foundation` module is an umbrella for a wide variety of functionality. But on other platforms, such as Android and Linux, `Foundation` is broken up into multiple separate sub-components: * `FoundationEssentials`: All the basic Foundation types: `Date`, `Calendar`, `URL`, `IndexSet`, etc. * `FoundationInternationalization`: `DateFormatter`, `NumberFormatter`, and other localization utilities * `FoundationNetworking`: `URLSession`, `URLCache`, and other networking utilities * `FoundationXML`: `XMLParser` The solution to this is simple: add a conditional import of `FoundationNetworking` to any file that uses any networking functionality, like so: import Foundation#if canImport(FoundationNetworking)import FoundationNetworking#endif struct Item: Decodable { let id: Int let name: String} func fetch(_ url: URL) async throws -> Item { let (data, response) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(Item.self, from: data)} This will include the required `FoundationNetworking` module for platforms like Android and Linux that need it, but quietly ignore it on Darwin platforms like iOS and macOS where the networking types are included with the monolithic `Foundation` framework. ### Importing the Android Module [Section titled “Importing the Android Module”](https://skip.dev/docs/porting/#importing-the-android-module) Swift has excellent integration with C, and many useful functions come in from the system’s C library, which is called `Darwin` on macOS and iOS. Take the simple example of calculating the hypotenuse of a triangle, which uses some math functions brought in from the standard C library: import Darwin func hypotenuse(a: Double, b: Double) -> Double { return sqrt(pow(a, 2) + pow(b, 2))} If you try to build this for Android, you will hit the error: 1 | import Darwin | `- error: no such module 'Darwin' This is because the `Darwin` module doesn’t exist for Android. It is instead simply called `Android`. Again, we solve this with our handy conditional `canImport`: #if canImport(Darwin)import Darwin#elseif canImport(Android)import Android#else#error("Unknown platform")#endif func hypotenuse(a: Double, b: Double) -> Double { return sqrt(pow(a, 2) + pow(b, 2))} In this case, we import either `Darwin` on iOS and macOS, or `Android` on Android. Both of these will provide access to the system’s standard C library. ### Low-level C Issues [Section titled “Low-level C Issues”](https://skip.dev/docs/porting/#low-level-c-issues) Simple C functions (like `pow` and `sqrt`) will generally be surfaced in exactly the same way on Darwin and Android platforms. But the definition of some functions and data structures in the Android C library can sometimes differ in subtle ways. For example, the following code uses the `FILE` type and `fopen` and `fwrite` C functions on Darwin platforms: import Darwin let fd: UnsafeMutablePointer = fopen("file.txt", "w")var buffer: [UInt8] = [1, 2, 3]let count: Int = buffer.withUnsafeBufferPointer { ptr in fwrite(ptr.baseAddress, MemoryLayout.stride, ptr.count, fd)} This will fail to build for Android: $ skip android build FileWrite.swift:15:30: error: cannot find type 'FILE' in scope13 | #endif14 |15 | let fd: UnsafeMutablePointer = fopen("file.txt", "w") | `- error: cannot find type 'FILE' in scope16 | var buffer: [UInt8] = [1, 2, 3]17 | let count: Int = buffer.withUnsafeBufferPointer { ptr in FileWrite.swift:18:16: error: value of optional type 'UnsafePointer?' must be unwrapped to a value of type 'UnsafePointer'16 | var buffer: [UInt8] = [1, 2, 3]17 | let count: Int = buffer.withUnsafeBufferPointer { ptr in18 | fwrite(ptr.baseAddress, MemoryLayout.stride, ptr.count, fd) | |- error: value of optional type 'UnsafePointer?' must be unwrapped to a value of type 'UnsafePointer' | |- note: coalesce using '??' to provide a default when the optional value contains 'nil' | `- note: force-unwrap using '!' to abort execution if the optional value contains 'nil'19 | }20 | There are two separate issue here: * `FILE` doesn’t exist on Android, so `UnsafeMutablePointer` must be replaced with `OpaquePointer` * Functions like `fwrite` that take a file pointer will not accept an optional, and so must be force-unwrapped from their pointer’s `rawValue` The following conditional typealias will handle the first issue, and simply force-unwrapping the pointer’s address (which should be valid on all platforms) addresses the second: #if canImport(Darwin)import Darwin#elseif canImport(Android)import Android#else#error("Unknown platform")#endif #if os(Android)typealias Descriptor = OpaquePointer#elsetypealias Descriptor = UnsafeMutablePointer#endif let fd: Descriptor = fopen("file.txt", "w")var buffer: [UInt8] = [1, 2, 3]let count: Int = buffer.withUnsafeBufferPointer { ptr in fwrite(ptr.baseAddress!, MemoryLayout.stride, ptr.count, fd)} Unless you are developing very low-level code that interfaces with the platform’s C library, you will rarely encounter these sorts of issues. But when you do, it is good to know that the solutions tend the be fairly simple. The most difficult part is often just deciphering the compilation failure message. Testing ------- [Section titled “Testing”](https://skip.dev/docs/porting/#testing) So now your package builds for Android with the command: `skip android build`. Amazing! But you are only halfway there: you need to make sure your code not only builds for Android, but that it actually works. Hopefully, your Swift package includes test cases in the `Test/` folder, and running the tests locally on your macOS machine with `swift test` works. For example, with the [`swift-algorithms` ↗](https://github.com/apple/swift-algorithms) package: $ swift testBuilding for debugging...[78/78] Linking swift-algorithmsPackageTestsBuild complete! (12.67s) Test Suite 'All tests' started at 2025-01-21 19:25:03.841.Test Suite 'swift-algorithmsPackageTests.xctest' started at 2025-01-21 19:25:03.842.Test Suite 'AdjacentPairsTests' started at 2025-01-21 19:25:03.842.Test Case '-[SwiftAlgorithmsTests.AdjacentPairsTests testEmptySequence]' started.Test Case '-[SwiftAlgorithmsTests.AdjacentPairsTests testEmptySequence]' passed (0.002 seconds).Test Case '-[SwiftAlgorithmsTests.AdjacentPairsTests testIndexTraversals]' started.Test Case '-[SwiftAlgorithmsTests.AdjacentPairsTests testIndexTraversals]' passed (0.002 seconds).…Test Suite 'All tests' passed at 2025-01-21 19:25:05.718. Executed 212 tests, with 0 failures (0 unexpected) in 1.870 (1.876) seconds In order to run your tests on Android, you will need to either plug in an Android device (with [USB debugging ↗](https://developer.android.com/studio/debug/dev-options#Enable-debugging) enabled), or else configure and launch an Android emulator, either from the command line or [Android Studio ↗](https://developer.android.com/studio/run/managing-avds) ). Once you have your Android development target setup, you can run your package’s test cases with the `skip android test` command, which will compile the test cases, bundle them up (along with any associated resources), copy them to the Android device or emulator, and then execute the test cases remotely. For example, for the `swift-algorithms` package: % skip android test[0/1] Planning buildBuilding for debugging...…[83/84] Linking swift-algorithmsPackageTests.xctest Build complete! (11.68s) [✓] Check Swift Package (0.87s)[✓] Connecting to Android (0.18s)[✓] Copying test files (0.88s) Test Suite 'All tests' started at 2025-01-21 21:02:09.086Test Suite 'swift-algorithms-1C77777B-CEC3-4075-8853-E77CECFCF30B.xctest' started at 2025-01-21 21:02:09.105Test Suite 'AdjacentPairsTests' started at 2025-01-21 21:02:09.105Test Case 'AdjacentPairsTests.testEmptySequence' started at 2025-01-21 21:02:09.105Test Case 'AdjacentPairsTests.testEmptySequence' passed (0.014 seconds)Test Case 'AdjacentPairsTests.testIndexTraversals' started at 2025-01-21 21:02:09.120Test Case 'AdjacentPairsTests.testIndexTraversals' passed (0.004 seconds)…Test Suite 'All tests' passed at 2025-01-21 21:02:21.697 Executed 212 tests, with 0 failures (0 unexpected) in 12.579 (12.579) seconds If there are any test failures, this is where you will delve into the details of your test case, isolate the problem, and apply fixes. There are many reasons why tests may fail, such as assumptions about the filesystem layout. These will need to be examined and resolved on a case-by-case basis. Once all your tests pass, you’ve successfully brought your Swift package to Android! Bonus: Continuous Integration ----------------------------- [Section titled “Bonus: Continuous Integration”](https://skip.dev/docs/porting/#bonus-continuous-integration) Once you have your package building and your tests passing, you will want to ensure that they _continue_ to pass. Maintaining a package that supports multiple platforms can be more challenging than just a single platform, because often when a new feature is implemented or a bug is fixed, the change will only be tested on the platform the developer is currently working with. For example, if you are working on the iOS side of your application and make a bug fix in one of your packages, you may only test the changes on that one platform, but it may inadvertently break something on another platform. This is where continuous integration (CI) can be useful. If you use GitHub as your package’s source code management system, you can utilize [GitHub Actions ↗](https://github.com/features/actions) to automatically build and test your package on multiple platforms whenever you push to the repository or, for example, whenever a pull request is created from a branch or fork. In order to facilitate Android CI, we provide the [swift-android-action ↗](https://github.com/marketplace/actions/swift-android-action) , which enables you to build and test your package against Android in a single line of configuration. The following example of a `.github/workflows/ci.yml` script will build and test your package on each of macOS, iOS, Linux, and Android whenever a commit is pushed or a PR is created: name: swift package cion: push: branches: - '*' workflow_dispatch: pull_request: branches: - '*'jobs: linux-android: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: "Test Swift Package on Linux" run: swift test - name: "Test Swift Package on Android" uses: skiptools/swift-android-action@v2 macos-ios: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: "Test Swift Package on macOS" run: swift test - name: "Test Swift Package on iOS" run: xcodebuild test -sdk "iphonesimulator" -destination "platform=iOS Simulator,name=iPhone 15" -scheme "$(xcodebuild -list -json | jq -r '.workspace.schemes[-1]')" You can see this workflow in play in many of the packages on GitHub that support Android, such as Skip’s own [swift-sqlcipher ↗](https://github.com/skiptools/swift-sqlcipher/actions) package. In this way, you can be assured that once you have done the hard work of getting your package working with Android, it continues to work on **all** your supported platforms. Conclusion ---------- [Section titled “Conclusion”](https://skip.dev/docs/porting/#conclusion) Expanding your Swift packages to support platforms beyond iOS may at first seem daunting, but using the advice from this guide, you can follow a few simple steps that will put you on the right track: 1. Setup Skip and the Swift Android SDK 2. Try to build your package with `skip android build` 3. Identify build errors and resolve them with conditional imports and by accommodating platform differences 4. Set up an Android emulator or device for testing 5. Test your package with `skip android test` 6. Identify test failures and resolve them on a case-by-case basis This is the sequence we have used to add Android support to dozens of popular Swift packages, such as [GraphQL ↗](https://github.com/GraphQLSwift/GraphQL/pull/161) , [CryptoSwift ↗](https://github.com/krzyzanowskim/CryptoSwift/pull/1065) , and [PromiseKit ↗](https://github.com/mxcl/PromiseKit/pull/1352) . With thousands of Swift packages currently building for Android, we feel the platform has achieved enough critical mass to make Swift an attractive language for many parts of your apps on both major mobile platforms: iOS **and** Android. And if you have a popular GitHub package that builds for Android, expect it to show up on the [Swift Package Index ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) in the near future! --- # Lottie | Skip [Skip to content](https://skip.dev/docs/modules/skip-motion/#_top) Lottie ====== This is a Skip Swift/Kotlin library project that provides the ability to play Lottie animations in dual-platform Skip apps for iOS and Android. Your browser does not support the video tag. On the Kotlin side, SkipMotion uses the [lottie-ios ↗](https://github.com/airbnb/lottie-ios) package, and on Android it uses the [lottie-android ↗](https://github.com/airbnb/lottie-android) . Lottie is a mobile library for Android and iOS that parses [Adobe After Effects ↗](http://www.adobe.com/products/aftereffects.html) animations exported as json with [Bodymovin ↗](https://github.com/airbnb/lottie-web) and renders them natively, enabling designers and developers to create and ship beautiful and seamless animations in their apps. Example: import Foundationimport SwiftUIimport SkipMotion struct ContentView: View { var body: some View { VStack(alignment: .center) { Text("Skip Motion Animation") .font(.largeTitle) MotionView(lottie: lottieData) } }} let lottieData = """{"mn":"ADBE Vector Shape - Group"}""".data(using: String.Encoding.utf8)! Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-motion/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-motion.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipMotion", package: "skip-motion") ]) ]) Status ------ [Section titled “Status”](https://skip.dev/docs/modules/skip-motion/#status) SkipMotion is in a very early stage, and lack any playback controls or other customization of the animations. You are encouraged to contribute to the project or file an [issue ↗](https://source.skip.dev/skip-motion/issues) with needs and requests. Size ---- [Section titled “Size”](https://skip.dev/docs/modules/skip-motion/#size) Adding `SkipMotion` as a dependency will add around 1MB to the size of the release `.ipa` and `.apk` artifacts for your project. Example ------- [Section titled “Example”](https://skip.dev/docs/modules/skip-motion/#example) See the [LottieDemo ↗](https://source.skip.dev/skipapp-lottiedemo/releases) project for an example of using `SkipMotion` in a Skip App project. Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-motion/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-motion/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-motion/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes --- # Keychain | Skip [Skip to content](https://skip.dev/docs/modules/skip-keychain/#_top) Keychain ======== This is a Skip Swift/Kotlin library project providing a simple unified API to secure key/value storage. It uses the Keychain on Darwin platforms and `EncyptedSharedPreferences` on Android. Your browser does not support the video tag. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-keychain/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-keychain.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipKeychain", package: "skip-keychain") ]) ]) Usage ----- [Section titled “Usage”](https://skip.dev/docs/modules/skip-keychain/#usage) import SkipKeychain let keychain = Keychain.shared try keychain.set("value", forKey: "key")assert(keychain.string(forKey: "key") == "value") try keychain.removeValue(forKey: "key")assert(keychain.string(forKey: "key") == nil) Backups ------- [Section titled “Backups”](https://skip.dev/docs/modules/skip-keychain/#backups) Google recommends excluding encrypted shared preference files from backups to prevent restoring an encrypted file whose encryption key is lost. If your app targets devices running Android 11 or lower, follow the instructions [here ↗](https://developer.android.com/identity/data/autobackup#include-exclude-android-11) to create backup rules for your app and reference them from your `AndroidManifest.xml`. Your rules file should exclude SkipKeychain’s shared preferences, like so: For newer devices, follow [these instructions ↗](https://developer.android.com/identity/data/autobackup#include-exclude-android-12) in addition to the steps above for older devices. Your additional rules file should contain the fllowing: Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-keychain/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-keychain/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-keychain/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes --- # NFC | Skip [Skip to content](https://skip.dev/docs/modules/skip-nfc/#_top) NFC === This package provides rudimentary support for Android and iOS NFC interaction for Skip apps. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-nfc/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-nfc.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipNFC", package: "skip-nfc") ]) ]) Usage ----- [Section titled “Usage”](https://skip.dev/docs/modules/skip-nfc/#usage) let nfcAdapter: NFCAdapter = NFCAdapter()nfcAdapter.startScanning { message in print("received message: \(message)")}nfcAdapter.stopScanning() Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-nfc/#setup-1) ### Android [Section titled “Android”](https://skip.dev/docs/modules/skip-nfc/#android) * Add [android.permission.NFC ↗](https://developer.android.com/reference/android/Manifest.permission.html#NFC) to your `AndroidManifest.xml`. ### iOS [Section titled “iOS”](https://skip.dev/docs/modules/skip-nfc/#ios) * Add [Near Field Communication Tag Reader Session Formats Entitlements ↗](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_nfc_readersession_formats) to your entitlements. * Add [NFCReaderUsageDescription ↗](https://developer.apple.com/documentation/bundleresources/information_property_list/nfcreaderusagedescription) to your `Darwin/Info.plist`. * Add [com.apple.developer.nfc.readersession.iso7816.select-identifiers ↗](https://developer.apple.com/documentation/bundleresources/information_property_list/select-identifiers) to your `Darwin/Info.plist` as needed. For example: com.apple.developer.nfc.readersession.formats NDEFNSNFCReaderUsageDescriptionThis app requires access to NFC to read and write data to NFC tags. Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-nfc/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-nfc/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Deployment | Skip [Skip to content](https://skip.dev/docs/deployment/#_top) Deployment ========== Skip app projects will build and launch the Android version of the app by starting the gradle process as part of the `Run Scripts` phase of the Xcode build process. This mechanism enables developers to rapidly iterate on an app by running both the iOS and Android versions side-by-side in the iOS Simulator and Android emulator. Similarly, Skip framework testing will run the transpiled Kotlin test cases by running `gradle test` on the translated modules. Exporting Build Artifacts ------------------------- [Section titled “Exporting Build Artifacts”](https://skip.dev/docs/deployment/#export) Running the `skip export` command from the Terminal in the project’s directory will build your Skip framework or app, and export all the binary artifacts to the folder specified with the `-d` argument. This includes `.aar` library archives for each of the targets in the current project, as well as all the dependent archives. These binary artifacts can be used in any Kotlin or Java build process or IDE. For example, a Kotlin development team can manually add them to their Android Studio project in order to work with a shared data model that is created using Skip. ![Framework Export Screenshot](https://assets.skip.dev/framework-dev/framework-skip-export.png) For app projects, the `skip export` command will additionally export `.apk` and `.aab` archives for the application, which can be installed directly on an Android device or uploaded to an Android app store, such as the Google Play Store. ![Framework Export Screenshot](https://assets.skip.dev/deployment/skip-export-app.png) Signing the Exported Android Artifacts -------------------------------------- [Section titled “Signing the Exported Android Artifacts”](https://skip.dev/docs/deployment/#export-signing) By default, the exported `.build/skip-export/AppName-debug.apk` file is signed with a single-use temporary signature, which means that you can install it on an Android emulator or simulator but future versions of the app cannot be upgraded without first manually removing the existing app. The release APK at `.build/skip-export/AppName.apk`, on the other hand, is not signed at all by default. Signing the APK is something that you can manage manually however you want (such as by integrating signing into your app’s CI workflow), but the default `Android/app/build.gradle.kts` build file will look for the presence of `keystore.properties` and `keystore.jks` files in the `Android/app/` folder. For complete information on creating and managing Android signing keys, refer to the [Sign your app ↗](https://developer.android.com/studio/publish/app-signing) documentation. But to get started quickly, you can create the `Android/app/keystore.jks` file by running this command from the root of your project and following the prompts: keytool -genkeypair -v -keystore Android/app/keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias app Remember the passwords that you set for the keystore and the key itself, and then create a new `Android/app/keystore.properties` file with the contents: keyAlias=appstoreFile=keystore.jksstorePassword=PASSWORDkeyPassword=PASSWORD Once these files are present, running `skip export` will automatically sign the release .apk file, which will enable you to distribute and run release builds of your app directly, without going through the Google Play Store. The `apksigner` command-line tool (located in `~/Library/Android/sdk/build-tools/*/apksigner`) can be used to verify the signature for the .apk file. Export Options -------------- [Section titled “Export Options”](https://skip.dev/docs/deployment/#export-options) For detailed information on the command, see the [`skip export` CLI reference](https://skip.dev/docs/skip-cli/#export) . * * * Running Gradle Manually ----------------------- [Section titled “Running Gradle Manually”](https://skip.dev/docs/deployment/#gradle) In addition to `skip export`, a Skip app project contains a gradle project in the `Android/` folder that can be run directly using the `gradle` command. This is useful for manually executing gradle tasks, such as launching the app on an emulator or configured device with the `launchDebug` task: skipapp-showcase % gradle -p Android launchDebug To release an Android app, you may want to perform the build yourself, either manually or as part of a continuous integration (CI) process. To build a release version of your app from the Terminal, you can run: gradle -p Android/ assemble This will run the Gradle project specified in the `Android/gradle.settings.kts` file ([Showcase source code ↗](https://github.com/skiptools/skipapp-showcase/blob/main/Android/settings.gradle.kts) ), which will invoke the Swift Package Manager build to for Android. The resulting build artifacts can then be found in the `.build/Android/` folder, like so: skiptools/skipapp-showcase % ls -lah .build/Android/app/outputs/apk/*/*.apk73M .build/Android/app/outputs/apk/debug/app-debug.apk11M .build/Android/app/outputs/apk/release/app-release.apk If you need an Android Bundle for submitting to an app storefront, you can build it with: skiptools/skipapp-showcase % gradle -p Android/ bundleskiptools/skipapp-showcase % ls -lah .build/Android/app/outputs/bundle/*/* 19M .build/Android/app/outputs/bundle/debug/app-debug.aab5.6M .build/Android/app/outputs/bundle/release/app-release.aab See the [Gradle project reference](https://skip.dev/docs/gradle/) for more information on the structure of a Skip gradle project. More information on using the gradle CLI for building Android apps can be found at [https://developer.android.com/build/building-cmdline ↗](https://developer.android.com/build/building-cmdline) . Fastlane Integration -------------------- [Section titled “Fastlane Integration”](https://skip.dev/docs/deployment/#fastlane) Fastlane is a tool that automates the release of an app to the Apple App Store and the Google Play Store. It is an alternative to the manual steps required to create and upload a release of an app through the web site for each of the stores. The fastlane command-line tool can be installed using Homebrew from the Terminal: brew install fastlane Full documentation for fastlane can be found at [https://docs.fastlane.tools ↗](https://docs.fastlane.tools/) . When you initialize a new Skip app with the following command: skip init --fastlane --appid=app.bundle.id package-name AppName The `Darwin/` and `Android/` folders will each contain a template for the fastlane project, which holds the metadata files that can be edited to fill in information like the app’s title, description, content ratings, and screenshots. These files can be localized for each language that your app supports. The structure of the metadata for each platform is different. For iOS and other Apple platforms, the `Darwin/` folder will look like this: * DirectoryDarwin/fastlane * AppStore.xcconfig * Appfile * Deliverfile * Fastfile * README.md * apikey.json\* * Directorymetadata * Directoryen-US * description.txt * keywords.txt * release\_notes.txt * subtitle.txt * Directoryscreenshots * Directoryen-US * 0\_APP\_IPHONE\_55\_0.png * 0\_APP\_IPHONE\_65\_0.png * 1\_APP\_IPHONE\_65\_1.png * 2\_APP\_IPHONE\_65\_2.png * 3\_APP\_IPHONE\_65\_3.png * 4\_APP\_IPHONE\_65\_4.png * 5\_APP\_IPHONE\_65\_5.png The `Android/` folder will look like: * DirectoryAndroid/fastlane * Appfile * Fastfile * README.md * apikey.json\* * Directorymetadata * Directoryandroid * Directoryen-US * full\_description.txt * Directoryimages * featureGraphic.png * icon.png * DirectoryphoneScreenshots * 1\_en-US.png * 2\_en-US.png * 3\_en-US.png * 4\_en-US.png * short\_description.txt * title.txt * video.txt The fastlane `Fastfile`, `Appfile`, and `Deliverfile` for each platform can be configured to automatically submit the release for review, or to release it to the storefront’s testing service. See the [fastlane documentation ↗](https://docs.fastlane.tools/) for further details. ### App Signing and Storefront Authentication [Section titled “App Signing and Storefront Authentication”](https://skip.dev/docs/deployment/#fastlane_signing_auth) An `apikey.json` file in each of the `fastlane/` folders (not created by `skip init`) must be downloaded for each of the App Store Connect service and the Google Play Console service. These contain the authentication information necessary to upload releases to the respective storefronts. For the Apple App Store, follow the instructions for [using fastlane API Key JSON file ↗](https://docs.fastlane.tools/app-store-connect-api/#using-fastlane-api-key-json-file) . For the Google Play Store, follow the instructions for [upload to Play Store setup ↗](https://docs.fastlane.tools/actions/upload_to_play_store/#setup) . In addition to the API keys, you must also follow each platform’s app signing procedures. For Darwin platforms, this will mean downloading and installing the certificate and provisioning profile for the registered app id from [https://developer.apple.com ↗](https://developer.apple.com/) . For Android, the `Android/app/` folder should contain a `keystore.jks` and `keystore.properties` file, which is used by the default `Android/app/build.gradle.kts` for signing the release artifacts when they are present. Once you are ready to create a new release of your app, you can run the `fastlane release` command in each of the `Android/` and `Darwin/` folders to build the app (translating it first using Skip, in the case of Android), assemble it into an `.apk` or `.ipa`, and update the file to the appropriate storefront. ### Fastlane Example (Darwin) [Section titled “Fastlane Example (Darwin)”](https://skip.dev/docs/deployment/#fastlane_sample_darwin) A sample session for releasing an iOS app will look like: zap skipapp-showcase/Darwin % fastlane release[✔] 🚀[10:59:50]: ------------------------------[10:59:50]: --- Step: default_platform ---[10:59:50]: ------------------------------[10:59:50]: Driving the lane 'release' 🚀[10:59:50]: -------------------------------------[10:59:50]: --- Step: Switch to assemble lane ---[10:59:50]: -------------------------------------[10:59:50]: Cruising over to lane 'assemble' 🚖[10:59:50]: -----------------------[10:59:50]: --- Step: build_app ---[10:59:50]: -----------------------[10:59:50]: Resolving Swift Package Manager dependencies...[10:59:50]: $ xcodebuild -resolvePackageDependencies -scheme Showcase -project ./Showcase.xcodeproj -derivedDataPath ../.build/Darwin/DerivedData -xcconfig fastlane/AppStore.xcconfig[10:59:51]: ▸ Command line invocation:[10:59:51]: ▸ /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -resolvePackageDependencies -scheme Showcase -project ./Showcase.xcodeproj -derivedDataPath ../.build/Darwin/DerivedData -xcconfig fastlane/AppStore.xcconfig[10:59:51]: ▸ Build settings from configuration file 'fastlane/AppStore.xcconfig':[10:59:51]: ▸ Resolve Package Graph202 collapsed lines[10:59:53]: ▸ dependency 'skip-ui' is not used by any targetdependency 'skip-av' is not used by any target[10:59:53]: ▸ Resolved source packages:[10:59:53]: ▸ skipapp-showcase: /srcroot/skipapp-showcase[10:59:53]: ▸ skip-model: https://source.skip.tools/skip-model.git @ 0.7.0[10:59:53]: ▸ skip-av: https://source.skip.tools/skip-av.git @ 0.0.2[10:59:53]: ▸ skip-unit: https://source.skip.tools/skip-unit.git @ 0.7.2[10:59:53]: ▸ skip: https://source.skip.tools/skip.git @ 0.8.46[10:59:53]: ▸ skip-ui: https://source.skip.tools/skip-ui.git @ 0.9.6[10:59:53]: ▸ skip-foundation: https://source.skip.tools/skip-foundation.git @ 0.6.11[10:59:53]: ▸ skip-lib: https://source.skip.tools/skip-lib.git @ 0.7.13[10:59:53]: ▸ resolved source packages: skipapp-showcase, skip-model, skip-av, skip-unit, skip, skip-ui, skip-foundation, skip-lib[10:59:53]: $ xcodebuild -showBuildSettings -scheme Showcase -project ./Showcase.xcodeproj -derivedDataPath ../.build/Darwin/DerivedData -xcconfig fastlane/AppStore.xcconfig 2>&1 +-----------------------------------------------------------------------------+| Summary for gym 2.220.0 |+--------------------------------------+--------------------------------------+| sdk | iphoneos || xcconfig | fastlane/AppStore.xcconfig || derived_data_path | ../.build/Darwin/DerivedData || output_directory | ../.build/fastlane/Darwin || skip_archive | false || skip_codesigning | false || project | ./Showcase.xcodeproj || scheme | Showcase || clean | false || output_name | Showcase || silent | false || skip_package_ipa | false || skip_package_pkg | false || result_bundle | false || buildlog_path | ~/Library/Logs/gym || destination | generic/platform=iOS || xcodebuild_formatter | xcpretty || build_timing_summary | false || skip_profile_detection | false || xcodebuild_command | xcodebuild || skip_package_dependencies_resolutio | false || n | || disable_package_automatic_updates | false || use_system_scm | false || xcode_path | /Applications/Xcode.app |+--------------------------------------+--------------------------------------+ [10:59:56]: $ set -o pipefail && xcodebuild -scheme Showcase -project ./Showcase.xcodeproj -derivedDataPath ../.build/Darwin/DerivedData -xcconfig fastlane/AppStore.xcconfig -sdk 'iphoneos' -destination 'generic/platform=iOS' -archivePath ~/Library/Developer/Xcode/Archives/App.xcarchive -skipPackagePluginValidation -skipMacroValidation archive | tee ~/Library/Logs/gym/Showcase-Showcase.log | xcpretty[11:00:40]: ▸ Touching skipapp-showcase_Showcase.bundle (in target 'skipapp-showcase_Showcase' from project 'skipapp-showcase')[11:00:40]: ▸ Linking Showcase.o[11:00:40]: ▸ Processing empty-ShowcaseApp.plist[11:00:40]: ▸ Linking ShowcaseApp[11:00:40]: ▸ Generating 'ShowcaseApp.framework.dSYM'[11:00:40]: ▸ Touching ShowcaseApp.framework (in target 'ShowcaseApp' from project 'skipapp-showcase')[11:00:40]: ▸ Copying /srcroot/skipapp-showcase/.build/Darwin/DerivedData/Build/Intermediates.noindex/ArchiveIntermediates/Showcase/InstallationBuildProductsLocation/Applications/Showcase.app/protest_guerrilla.ttf[11:00:40]: ▸ Linking Showcase[11:00:41]: ▸ Running script 'Run skip gradle'[11:00:41]: ▸ Processing Info.plist[11:00:41]: ▸ Touching Showcase.app (in target 'Showcase' from project 'Showcase')[11:00:41]: ▸ Archive Succeeded[11:00:41]: Generated plist file with the following values:[11:00:41]: ▸ -----------------------------------------[11:00:41]: ▸[11:00:41]: ▸ "method": "app-store"[11:00:41]: ▸[11:00:41]: ▸ -----------------------------------------[11:00:41]: $ /usr/bin/xcrun /opt/homebrew/Cellar/fastlane/2.220.0/libexec/gems/fastlane-2.220.0/gym/lib/assets/wrap_xcodebuild/xcbuild-safe.sh -exportArchive -exportOptionsPlist '/var/folders/zl/wkdjv4s1271fbm6w0plzknkh0000gn/T/gym_config20240612-40159-xgzrb9.plist' -archivePath ~/Library/Developer/Xcode/Archives/2024-06-12/Showcase\ 2024-06-12\ 10.59.56.xcarchive -exportPath '/var/folders/zl/wkdjv4s1271fbm6w0plzknkh0000gn/T/gym_output20240612-40159-udedtq' -skipPackagePluginValidation -skipMacroValidation[11:00:55]: Successfully exported and signed the ipa file:[11:00:55]: /srcroot/skipapp-showcase/.build/fastlane/Darwin/Showcase.ipa[11:00:55]: Cruising back to lane 'release' 🚘[11:00:56]: ---------------------------------[11:00:56]: --- Step: upload_to_app_store ---[11:00:56]: ---------------------------------[11:00:56]: Successfully loaded '/srcroot/skipapp-showcase/Darwin/fastlane/Deliverfile' 📄 +--------------------------------------------------------------------------+| Detected Values from './fastlane/Deliverfile' |+-----------------------------------+--------------------------------------+| copyright | 2024 || force | true || automatic_release | true || skip_screenshots | false || precheck_include_in_app_purchases | false || submit_for_review | true |+-----------------------------------+--------------------------------------+ [11:00:56]: Creating authorization token for App Store Connect API +-----------------------------------------------------------------------------+| deliver 2.220.0 Summary |+--------------------------------------+--------------------------------------+| api_key_path | fastlane/apikey.json || app_rating_config_path | fastlane/metadata/rating.json || screenshots_path | ./fastlane/screenshots || metadata_path | ./fastlane/metadata || app_version | 1.0.3 || username | appfair@appfair.org || app_identifier | org.appfair.app.Showcase || platform | ios || edit_live | false || use_live_version | false || skip_binary_upload | false || skip_screenshots | false || skip_metadata | false || skip_app_version_update | false || force | true || overwrite_screenshots | false || screenshot_processing_timeout | 3600 || sync_screenshots | false || submit_for_review | true || verify_only | false || reject_if_possible | false || version_check_wait_retry_limit | 7 || automatic_release | true || phased_release | false || reset_ratings | false || run_precheck_before_submit | true || precheck_default_rule_level | warn || copyright | 2024 || ignore_language_directory_validation | false || precheck_include_in_app_purchases | false |+--------------------------------------+--------------------------------------+ [11:00:56]: Making sure the latest version on App Store Connect matches '1.0.3'...[11:00:59]: Successfully set the version to '1.0.3'[11:00:59]: Loading './fastlane/metadata/en-US/description.txt'...[11:00:59]: Loading './fastlane/metadata/en-US/keywords.txt'...[11:00:59]: Loading './fastlane/metadata/en-US/release_notes.txt'...[11:00:59]: Loading './fastlane/metadata/en-US/support_url.txt'...[11:00:59]: Loading './fastlane/metadata/en-US/subtitle.txt'...[11:00:59]: Loading './fastlane/metadata/en-US/privacy_url.txt'...[11:01:02]: No changes to localized App Info detected. Skipping upload.[11:01:02]: Will begin uploading metadata for '1.0.3' on App Store Connect[11:01:02]: Uploading metadata to App Store Connect for version[11:01:06]: Uploading metadata to App Store Connect for localized version 'en-US'[11:01:08]: Setting the app's age rating...[11:01:09]: Will begin uploading snapshots for '1.0.3' on App Store Connect[11:01:09]: Starting with the upload of screenshots...[✔] Waiting for all the screenshots to finish being processed...[11:01:09]: Successfully uploaded all screenshots[✔] Sorting screenshots uploaded...[11:01:10]: Successfully uploaded screenshots to App Store Connect[11:01:10]: Uploading binary to App Store Connect[11:01:10]: Going to upload updated app to App Store Connect[11:01:10]: This might take a few minutes. Please don't interrupt the script.[11:01:47]: --------------------------------------------------------------------[11:01:47]: Successfully uploaded package to App Store Connect. It might take a few minutes until it's visible online.[11:01:47]: --------------------------------------------------------------------[11:01:47]: Finished the upload to App Store Connect[11:01:47]: Running precheck before submitting to review, if you'd like to disable this check you can set run_precheck_before_submit to false[11:01:47]: Making sure we pass precheck 👮‍♀️ 👮 before we submit 🛫 +-----------------------------------------------------+| Summary for precheck 2.220.0 |+--------------------------+--------------------------+| default_rule_level | warn || include_in_app_purchases | false || app_identifier | org.appfair.app.Showcase || api_key_path | fastlane/apikey.json || username | appfair@appfair.org || platform | ios || use_live | false |+--------------------------+--------------------------+ [11:01:47]: Creating authorization token for App Store Connect API[11:01:47]: Checking app for precheck rule violations[11:01:51]: ✅ Passed: No negative  sentiment[11:01:51]: ✅ Passed: No placeholder text[11:01:51]: ✅ Passed: No mentioning  competitors[11:01:51]: ✅ Passed: No future functionality promises[11:01:51]: ✅ Passed: No words indicating test content[11:01:51]: ✅ Passed: No curse words[11:01:51]: ✅ Passed: No words indicating your IAP is free[11:01:51]: ✅ Passed: Incorrect, or missing copyright date[11:01:51]: ✅ Passed: No broken urls[11:01:51]: precheck 👮‍♀️ 👮 finished without detecting any potential problems 🛫[11:01:51]: Selecting the latest build...[11:01:51]: Waiting for processing on... app_id: 6474885022, app_version: 1.0.3, build_version: 103, platform: IOS[11:01:52]: Read more information on why this build isn't showing up yet - https://github.com/fastlane/fastlane/issues/14997[11:01:52]: Waiting for the build to show up in the build list - this may take a few minutes (check your email for processing issues if this continues)[11:02:07]: Waiting for the build to show up in the build list - this may take a few minutes (check your email for processing issues if this continues)[11:02:22]: Waiting for the build to show up in the build list - this may take a few minutes (check your email for processing issues if this continues)[11:02:37]: Waiting for the build to show up in the build list - this may take a few minutes (check your email for processing issues if this continues)[11:02:54]: Waiting for App Store Connect to finish processing the new build (1.0.3 - 103) for IOS[11:03:10]: Waiting for App Store Connect to finish processing the new build (1.0.3 - 103) for IOS[11:03:26]: Waiting for App Store Connect to finish processing the new build (1.0.3 - 103) for IOS[11:03:42]: Successfully finished processing the build 1.0.3 - 103 for IOS[11:03:42]: Selecting build 1.0.3 (103)...[11:03:44]: Successfully selected build[11:03:45]: Successfully updated IDFA declarations on App Store Connect[11:03:46]: Successfully updated contents rights declaration on App Store Connect[11:04:01]: Successfully submitted the app for review! +------------------------------------------+| fastlane summary |+------+---------------------+-------------+| Step | Action | Time (in s) |+------+---------------------+-------------+| 1 | default_platform | 0 || 2 | Switch to assemble | 0 || | lane | || 3 | build_app | 65 || 4 | upload_to_app_store | 185 |+------+---------------------+-------------+ [11:04:01]: fastlane.tools finished successfully 🎉 ### Fastlane Example (Android) [Section titled “Fastlane Example (Android)”](https://skip.dev/docs/deployment/#fastlane_sample_android) A sample session for Android will look like: zap skipapp-showcase/Android % fastlane release[✔] 🚀[15:29:48]: ------------------------------[15:29:48]: --- Step: default_platform ---[15:29:48]: ------------------------------[15:29:48]: ------------------------------[15:29:48]: --- Step: default_platform ---[15:29:48]: ------------------------------[15:29:48]: Driving the lane 'release' 🚀[15:29:48]: -------------------------------------[15:29:48]: --- Step: Switch to assemble lane ---[15:29:48]: -------------------------------------[15:29:48]: Cruising over to lane 'assemble' 🚖[15:29:48]: ---------------------------[15:29:48]: --- Step: bundleRelease ---[15:29:48]: ---------------------------[15:29:48]: $ /opt/homebrew/bin/gradle bundleRelease -p .[15:29:53]: ▸ [0/1] Planning build[15:29:53]: ▸ [1/1] Compiling plugin Create SkipLink107 collapsed lines[15:29:53]: ▸ [2/2] Compiling plugin skipstone[15:29:53]: ▸ Building for debugging...[15:29:53]: ▸ [2/8] Write sources[15:29:53]: ▸ [3/13] Write swift-version--58304C5D6DBC2206.txt[15:29:53]: ▸ note: Skip 0.8.46: transpile plugin to: /srcroot/skipapp-showcase/.build/plugins/outputs/skip-unit/SkipUnit/skipstone/SkipUnit/src/main at 15:29:53…[15:30:13]: ▸ > Task :app:processReleaseManifest[15:30:13]: ▸ > Task :app:mergeReleaseArtProfile UP-TO-DATE[15:30:13]: ▸ > Task :app:processApplicationManifestReleaseForBundle[15:30:13]: ▸ > Task :app:mergeReleaseAssets UP-TO-DATE[15:30:13]: ▸ > Task :app:mergeReleaseNativeLibs NO-SOURCE[15:30:13]: ▸ > Task :app:stripReleaseDebugSymbols NO-SOURCE[15:30:13]: ▸ > Task :app:extractReleaseNativeSymbolTables NO-SOURCE[15:30:13]: ▸ > Task :app:processReleaseManifestForPackage[15:30:13]: ▸ > Task :app:processReleaseResources[15:30:14]: ▸ > Task :app:bundleReleaseResources[15:30:14]: ▸ > Task :skipstone:Showcase:compileReleaseKotlin[15:30:14]: ▸ > Task :skipstone:Showcase:compileReleaseJavaWithJavac NO-SOURCE[15:30:14]: ▸ > Task :skipstone:Showcase:mergeReleaseGeneratedProguardFiles UP-TO-DATE[15:30:14]: ▸ > Task :skipstone:Showcase:exportReleaseConsumerProguardFiles UP-TO-DATE[15:30:14]: ▸ > Task :skipstone:Showcase:processReleaseJavaRes UP-TO-DATE[15:30:14]: ▸ > Task :skipstone:Showcase:bundleLibRuntimeToJarRelease[15:30:14]: ▸ > Task :skipstone:Showcase:bundleLibCompileToJarRelease[15:30:15]: ▸ > Task :app:compileReleaseKotlin[15:30:15]: ▸ > Task :app:compileReleaseJavaWithJavac[15:30:15]: ▸ > Task :app:mergeReleaseGeneratedProguardFiles UP-TO-DATE[15:30:15]: ▸ > Task :app:processReleaseJavaRes UP-TO-DATE[15:30:15]: ▸ > Task :app:mergeReleaseJavaResource UP-TO-DATE[15:30:15]: ▸ > Task :app:expandReleaseArtProfileWildcards[15:30:45]: ▸ > Task :app:minifyReleaseWithR8[15:31:05]: ▸ > Task :app:buildReleasePreBundle[15:31:06]: ▸ > Task :app:compileReleaseArtProfile[15:31:06]: ▸ > Task :app:packageReleaseBundle[15:31:08]: ▸ > Task :app:shrinkBundleReleaseResources[15:31:10]: ▸ > Task :app:signReleaseBundle[15:31:10]: ▸ > Task :app:produceReleaseBundleIdeListingFile[15:31:10]: ▸ > Task :app:createReleaseBundleListingFileRedirect UP-TO-DATE[15:31:10]: ▸ > Task :app:bundleRelease[15:31:10]: ▸ BUILD SUCCESSFUL in 1m 21s[15:31:10]: ▸ 169 actionable tasks: 30 executed, 139 up-to-date[15:31:10]: Cruising back to lane 'release' 🚘[15:31:10]: ----------------------------------[15:31:10]: --- Step: upload_to_play_store ---[15:31:10]: ---------------------------------- +---------------------------------------------------------------------------+| Summary for supply 2.220.0 |+------------------------------------+--------------------------------------+| aab | ../.build/Android/app/outputs/bundl || | e/release/app-release.aab || package_name | org.appfair.app.Showcase || release_status | completed || track | production || metadata_path | ./fastlane/metadata/android || json_key | fastlane/apikey.json || skip_upload_apk | false || skip_upload_aab | false || skip_upload_metadata | false || skip_upload_changelogs | false || skip_upload_images | false || skip_upload_screenshots | false || sync_image_upload | false || track_promote_release_status | completed || validate_only | false || check_superseded_tracks | false || timeout | 300 || deactivate_on_promote | true || changes_not_sent_for_review | false || rescue_changes_not_sent_for_review | true || ack_bundle_installation_warning | false |+------------------------------------+--------------------------------------+ [15:31:11]: Preparing aab at path '../.build/Android/app/outputs/bundle/release/app-release.aab' for upload...[15:31:28]: Updating track 'production'...[15:31:29]: Preparing uploads for language 'en-US'...[15:31:30]: ⬆️ Uploading image file ./fastlane/metadata/android/en-US/images/featureGraphic.png...[15:31:32]: ⬆️ Uploading image file ./fastlane/metadata/android/en-US/images/icon.png...[15:31:34]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png...[15:31:37]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png...[15:31:40]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png...[15:31:42]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png...[15:31:45]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/sevenInchScreenshots/1_en-US.png...[15:31:48]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/sevenInchScreenshots/2_en-US.png...[15:31:50]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/sevenInchScreenshots/3_en-US.png...[15:31:53]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/sevenInchScreenshots/4_en-US.png...[15:31:56]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png...[15:31:58]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/tenInchScreenshots/2_en-US.png...[15:32:01]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/tenInchScreenshots/3_en-US.png...[15:32:04]: ⬆️ Uploading screenshot ./fastlane/metadata/android/en-US/images/tenInchScreenshots/4_en-US.png...[15:32:07]: Uploaded all items for language 'en-US'... (37.582534 secs)[15:32:07]: Uploading all changes to Google Play...[15:32:10]: Successfully finished the upload to Google Play +-------------------------------------------+| fastlane summary |+------+----------------------+-------------+| Step | Action | Time (in s) |+------+----------------------+-------------+| 1 | default_platform | 0 || 2 | default_platform | 0 || 3 | Switch to assemble | 0 || | lane | || 4 | bundleRelease | 81 || 5 | upload_to_play_store | 59 |+------+----------------------+-------------+ [15:32:10]: fastlane.tools finished successfully 🎉 --- # JavaScriptCore | Skip [Skip to content](https://skip.dev/docs/modules/skip-script/#_top) JavaScriptCore ============== SkipScript provides a unified interface to the JavaScriptCore script engine on both iOS (using the platform-provided JavaScriptCore libraries) and on Android (using the bundled libjsc.so library). SkipScript enables a single scripting language (JavaScript) to be embedded in a dual-platform Skip app and provide the exact same behavior on both platforms. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-script/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-script.git", from: "1.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipScript", package: "skip-script") ]) ]) as a dependency and a Swift source file imports the `JavaScriptCore` framework. In this case, a subset of the the Objective-C JavaScriptAPI is mimicked on the Kotlin side, passing the calls through to the underlying C interface to the JavaScriptCore API using JNA and [SkipFFI](https://skip.dev/docs/modules/skip-ffi) . An example of evaluating some JavaScript: import SkipScript let ctx = try JSContext()let num = ctx.evaluateScript("1 + 2.3")assert(num.toDouble() == 3.3) **NOTE**: JIT compilation is blocked on iOS without a special entitlement, which can drastically impact the performance of JavaScriptCore on iOS compared to either macOS or Android (where JIT is not blocked). Implementation -------------- [Section titled “Implementation”](https://skip.dev/docs/modules/skip-script/#implementation) On iOS and other Darwin platforms, the built-in `JavaScriptCore` libraries will be used. Android, on the other hand, does not ship JSC as part of the operating system, and so the dependency on the Android side will utilize the `org.webkit:android-jsc` package to bundle a native build of JavaScriptCore with the app itself. This will increase the total Android bundle size by between 5-10Mb. Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-script/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-script/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Bluetooth | Skip [Skip to content](https://skip.dev/docs/modules/skip-bluetooth/#_top) Bluetooth ========= This is a Skip Lite Swift/Kotlin library project that provides API parity to `CoreBluetooth` for Android. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-bluetooth/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-bluetooth.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipBluetooth", package: "skip-bluetooth") ]) ]) Implementation Instructions --------------------------- [Section titled “Implementation Instructions”](https://skip.dev/docs/modules/skip-bluetooth/#implementation-instructions) `SkipBluetooth` aims to provide API parity to `CoreBluetooth`, but in a few cases, this requires using `// SKIP DECLARE:` in your implementation. There are delegate methods which have the same argument type signature (despite having differently-named parameters) in `CoreBluetooth`, and are therefore recognized as the same function to `gradle` since Kotlin doesn’t differentiate between function calls based on parameter names. One such collision example is: func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)func centralManager(central: CBCentralManager, DidUpdateANCSAuthorizationFor peripheral: CBPeripheral) In order to implement these functions in your `CBCentralManagerDelegate` implementation, you should write the function call as you would do in Swift, but put the corresponding `// SKIP DECLARE: ...` line above that function which corresponds to the Kotlin-compliant API call. Here is an example: /* Assuming this function is inside of a class conforming to `CBCentralManagerDelegate` */ // SKIP DECLARE: override fun centralManagerDidConnect(central: CBCentralManager, didConnect: CBPeripheral)func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { /* Your implementation here */} Here is a list of all such currently-available functions and their corresponding calls: **CBCentralManagerDelegate** | CoreBluetooth | SkipBluetooth | | --- | --- | | `func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)` | `// SKIP DECLARE: override fun centralManagerDidConnect(central: CBCentralManager, didConnect: CBPeripheral)` | | `func centralManager(_ central: CBCentralManager, didUpdateANCSAuthorizationFor peripheral: CBPeripheral)` | `// SKIP DECLARE: override fun centralManagerDidUpdateANCSAuthorizationFor(central: CBCentralManager, didUpdateANCSAuthorizationFor: CBPeripheral)` | | `func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: (any Error)?)` | `// SKIP DECLARE: override fun centralManagerDidDisconnectPeripheral(central: CBCentralManager, peripheral: CBPeripheral, error: (any Error)?)` | **CBPeripheralManagerDelegate** | CoreBluetooth | SkipBluetooth | | --- | --- | | `func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic)` | `// SKIP DECLARE: override fun peripheralManagerDidSubscribeTo(peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo: CBCharacteristic)` | | `func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic)` | `// SKIP DECLARE: override fun peripheralManagerDidUnsubscribeFrom(peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom: CBCharacteristic)` | **CBPeripheralDelegate** | CoreBluetooth | SkipBluetooth | | --- | --- | | `func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: (any Error)?)` | `// SKIP DECLARE: override fun peripheralDidUpdateValueFor(peripheral: CBPeripheral, didUpdateValueFor: CBCharacteristic, error: Error?)` | | `func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: (any Error)?)` | `// SKIP DECLARE: override fun peripheralDidWriteValueFor(peripheral: CBPeripheral, didWriteValueFor: CBCharacteristic, error: Error?)` | | `func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: (any Error)?)` | `// SKIP DECLARE: override fun peripheralDidUpdateNotificationStateFor(peripheral: CBPeripheral, didUpdateNotificationStateFor: CBCharacteristic, error: Error?)` | | `func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: (any Error)?)` | `// SKIP DECLARE: override fun peripheralDidDiscoverCharacteristicsFor(peripheral: CBPeripheral, didDiscoverCharacteristicsFor: CBService, error: Error?)` | ### Asking for Permissions [Section titled “Asking for Permissions”](https://skip.dev/docs/modules/skip-bluetooth/#asking-for-permissions) Bluetooth requires permissions for both IOS and Kotlin, so you must add the following to your Info.plist file: * [NSBluetoothAlwaysUsageDescription ↗](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothalwaysusagedescription) and these to your AndroidManifest.xml Before using any Bluetooth API’s, you must request user permissions. One way to do this is to use the `PermissionManager` API in [SkipKit](https://skip.dev/docs/modules/skip-kit#permissionmanager) : import SkipKit func performBluetoothOperation() async { if await PermissionManager.requestPermission("android.permission.BLUETOOTH_SCAN") == true { if await PermissionManager.requestPermission("android.permission.BLUETOOTH_CONNECT") == true { // perform bluetooth operation… } }} An example of manually requesting a permission without using `SkipKit` is as follows. In this scenario, note that you must request user permissions **in the body of the view or function** which will use Bluetooth. import SwiftUI #if SKIPimport SkipBluetoothimport androidx.compose.runtime.Composableimport androidx.compose.runtime.SideEffectimport androidx.activity.compose.rememberLauncherForActivityResultimport androidx.activity.result.contract.ActivityResultContracts#endif struct ContentView: View { var body: some View {#if SKIP askForBluetoothPermissions()#endif }} /// Prompts the user to allow bluetooth permissions@Composablepublic func askForBluetoothPermissions() { let requestPermissionLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestMultiplePermissions()) { perms in // Handle permissions here if you'd like } let permissions: kotlin.Array = kotlin.arrayOf( Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT ) // Skip can't implicitly convert between kotlin.Array and // skip.lib.Array hence the cast SideEffect { requestPermissionLauncher.launch(permissions) }}#endif This will request Bluetooth permissions as soon as the view appears. Subsequent loads of this view will not show the prompt again—you will have to request the user to enable Bluetooth in settings if they denied permission previously. Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-bluetooth/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-bluetooth/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-bluetooth/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes --- # AuthenticationServices | Skip [Skip to content](https://skip.dev/docs/modules/skip-authentication-services/#_top) AuthenticationServices ====================== This module provides a compatibility API corresponding to Apple’s [AuthenticationServices ↗](https://developer.apple.com/documentation/AuthenticationServices) framework. Currently, the framework provides the ability to launch a WebAuthenticationSession, which your app can use to authenticate a user using a web site. In the future, this framework will provide the ability to Sign In with Apple. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-authentication-services/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-authentication-services.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipAuthenticationServices", package: "skip-authentication-services") ]) ]) ### Setup fallback support for old versions of Chrome [Section titled “Setup fallback support for old versions of Chrome”](https://skip.dev/docs/modules/skip-authentication-services/#setup-fallback-support-for-old-versions-of-chrome) Our implementation of `WebAuthenticationSession` is built on top of Google’s [Auth Tab ↗](https://developer.chrome.com/docs/android/custom-tabs/guide-auth-tab) , which is designed to work in Chrome browser versions 137 and later. Chrome 137 was released in May 2025; not all of your users may have updated to it, especially users on older versions of Android. In that case, the code includes a “fallback” to launch a [Custom Tab ↗](https://developer.android.com/develop/ui/views/layout/webapps/overview-of-android-custom-tabs) . To make that fallback work, you’ll need to add an `` to your `AndroidManifest.xml` file, like this: Web Authentication Session -------------------------- [Section titled “Web Authentication Session”](https://skip.dev/docs/modules/skip-authentication-services/#web-authentication-session) Your app can login using a web site you control using [`WebAuthenticationSession` ↗](https://developer.apple.com/documentation/authenticationservices/webauthenticationsession) . We support both the deprecated legacy iOS 16.4 API, [`authenticate(using:callbackURLScheme:preferredBrowserSession:)` ↗](https://developer.apple.com/documentation/authenticationservices/webauthenticationsession/authenticate%28using:callbackurlscheme:preferredbrowsersession:%29) , as well as the iOS 17.4 API, [`authenticate(using:callback:preferredBrowserSession:additionalHeaderFields:)` ↗](https://developer.apple.com/documentation/authenticationservices/webauthenticationsession/authenticate%28using:callback:preferredbrowsersession:additionalheaderfields:%29) . First, you’ll need to have a login page on a web site you control. When the user finishes logging in to the web site, your web site will need to redirect the user to a custom URL scheme. In other words, instead of sending the user to an URL starting with `https://` (the `https` is the “scheme” of the URL), you’ll redirect the user to an URL starting with a scheme that you make up, e.g. `mycustomscheme://auth`. (Tip: Custom URL schemes can include dots, so you could use your app’s bundle ID or Android package name, e.g. `com.example.myapp://auth`) Your web site should pass an authentication token in a query parameter to the redirect URL, e.g. `com.example.myapp://auth?login_token=12345abcdef`. When the user signs in, `WebAuthenticationSession` will dismiss the login screen and return the URL containing the token. If you need to store the token securely, consider storing it in the user’s keychain with the [skip-keychain](https://skip.dev/docs/modules/skip-keychain) library. #if os(Android)import SkipAuthenticationServices#elseimport AuthenticationServices#endif import SwiftUI struct ContentView: View { @Environment(\.webAuthenticationSession) var webAuthenticationSession: WebAuthenticationSession var body: some View { Button("Sign In") { Task { do { let urlWithToken: URL if #available(iOS 17.4, *) { urlWithToken = try await webAuthenticationSession.authenticate( using: URL(string: "https://example.com/login/")!, callback: .customScheme("mycustomscheme"), preferredBrowserSession: .ephemeral, additionalHeaderFields: [:] ) } else { urlWithToken = try await webAuthenticationSession.authenticate( using: URL(string: "https://example.com/login/")!, callbackURLScheme: "mycustomscheme", preferredBrowserSession: .ephemeral ) } let queryItems = URLComponents(url: urlWithToken, resolvingAgainstBaseURL: false)?.queryItems let token = queryItems?.filter({ $0.name == "login_token" }).first?.value // Here, you can store the token (perhaps in the skip-keychain) and do something with it } catch { if let error = error as? ASWebAuthenticationSessionError, error.code == ASWebAuthenticationSessionError.canceledLogin { print("user canceled login") } else { print("error: \(error)") } } } } }} Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-authentication-services/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-authentication-services/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Zip | Skip [Skip to content](https://skip.dev/docs/modules/skip-zip/#_top) Zip === SkipZip is a Skip framework that provides support for creating and extracting zip archives. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-zip/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-zip.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipZip", package: "skip-zip") ]) ]) Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-zip/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-zip/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Sentry | Skip [Skip to content](https://skip.dev/docs/modules/skip-sentry/#_top) Sentry ====== This is a Skip Swift/Kotlin library project that abstracts the [Sentry iOS ↗](https://docs.sentry.io/platforms/apple/guides/ios/usage/) and [Sentry Android ↗](https://docs.sentry.io/platforms/android/usage/) SDKs. It is currently in a primitive state and needs contributions to flesh out the capabilities. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-sentry/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-sentry.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipSentry", package: "skip-sentry") ]) ]) Configuration ------------- [Section titled “Configuration”](https://skip.dev/docs/modules/skip-sentry/#configuration) ### Android [Section titled “Android”](https://skip.dev/docs/modules/skip-sentry/#android) Follow the guide at [https://docs.sentry.io/platforms/android/configuration/ ↗](https://docs.sentry.io/platforms/android/configuration/) . ### iOS [Section titled “iOS”](https://skip.dev/docs/modules/skip-sentry/#ios) Follow the guide at [https://docs.sentry.io/platforms/apple/configuration/ ↗](https://docs.sentry.io/platforms/apple/configuration/) . Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-sentry/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-sentry/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Showcase (Fuse) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-showcase-fuse/#_top) Showcase (Fuse) =============== This is a Skip Fuse dual-platform app project. It builds a native app for both iOS and Android. [![Download on the Google Play Store](https://appfair.org/assets/badges/google-play-store.svg)](https://play.google.com/store/apps/details?id=org.appfair.app.Showcase) [![Download on the Apple App Store](https://appfair.org/assets/badges/apple-app-store.svg)](https://apps.apple.com/us/app/skip-showcase/id6474885022) iPhone Screenshots ------------------ [Section titled “iPhone Screenshots”](https://skip.dev/docs/samples/skipapp-showcase-fuse/#iphone-screenshots) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Darwin/fastlane/screenshots/en-US/1_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Darwin/fastlane/screenshots/en-US/2_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Darwin/fastlane/screenshots/en-US/3_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Darwin/fastlane/screenshots/en-US/4_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Darwin/fastlane/screenshots/en-US/5_en-US.png) Android Screenshots ------------------- [Section titled “Android Screenshots”](https://skip.dev/docs/samples/skipapp-showcase-fuse/#android-screenshots) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase-fuse/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png) Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-showcase-fuse/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-showcase-fuse/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-showcase-fuse/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the ShowcaseFuseApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # PostHog | Skip [Skip to content](https://skip.dev/docs/modules/skip-posthog/#_top) PostHog ======= A Skip interface for integrating PostHog analytics into your cross-platform iOS and Android applications. About PostHog ------------- [Section titled “About PostHog”](https://skip.dev/docs/modules/skip-posthog/#about-posthog) [PostHog ↗](https://posthog.com/) is an open-source product analytics platform that helps you understand user behavior, track events, conduct A/B tests, and analyze feature usage. It provides powerful analytics capabilities including event tracking, user identification, feature flags, session recording, and more—all while giving you full control over your data. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-posthog/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-posthog.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipPostHog", package: "skip-posthog") ]) ]) API Compatibility ----------------- [Section titled “API Compatibility”](https://skip.dev/docs/modules/skip-posthog/#api-compatibility) SkipPostHog provides the same API surface as the [PostHog iOS SDK ↗](https://posthog.com/docs/libraries/ios) , ensuring a familiar development experience. All methods behave identically on both iOS and Android platforms, allowing you to write once and deploy everywhere. On Android, the API calls are forwarded to their equivalents in the [PostHog Android SDK ↗](https://posthog.com/docs/libraries/android) . Configuration ------------- [Section titled “Configuration”](https://skip.dev/docs/modules/skip-posthog/#configuration) Initialize PostHog when your app starts, typically in your app’s initialization code: import SkipPostHog PostHogSDK.shared.setup( PostHogConfig(apiKey: "your-api-key")) For self-hosted PostHog instances, specify your custom host: PostHogSDK.shared.setup( PostHogConfig(apiKey: "your-api-key", host: "https://your-posthog-instance.com")) Usage Examples -------------- [Section titled “Usage Examples”](https://skip.dev/docs/modules/skip-posthog/#usage-examples) ### Tracking Events [Section titled “Tracking Events”](https://skip.dev/docs/modules/skip-posthog/#tracking-events) Capture events to understand what users are doing in your app: // Simple eventPostHogSDK.shared.capture("button_clicked") // Event with propertiesPostHogSDK.shared.capture( "purchase_completed", properties: [ "product_id": "abc123", "price": 29.99, "currency": "USD" ]) ### Identifying Users [Section titled “Identifying Users”](https://skip.dev/docs/modules/skip-posthog/#identifying-users) Associate events with specific users: // Identify a userPostHogSDK.shared.identify("user_12345") // Identify with user propertiesPostHogSDK.shared.identify( "user_12345", userProperties: [ "email": "user@example.com", "plan": "premium", "signup_date": "2024-01-15" ]) ### Setting User Properties [Section titled “Setting User Properties”](https://skip.dev/docs/modules/skip-posthog/#setting-user-properties) Update properties for the current user: PostHogSDK.shared.identify( PostHogSDK.shared.getDistinctId(), userProperties: [ "theme_preference": "dark", "notifications_enabled": true ]) ### Screen Tracking [Section titled “Screen Tracking”](https://skip.dev/docs/modules/skip-posthog/#screen-tracking) Track screen views to understand navigation patterns: PostHogSDK.shared.screen("Home Screen") // With additional propertiesPostHogSDK.shared.screen( "Product Details", properties: [ "product_id": "xyz789", "category": "Electronics" ]) ### Aliasing Users [Section titled “Aliasing Users”](https://skip.dev/docs/modules/skip-posthog/#aliasing-users) Link anonymous users with identified users: // When a user signs up or logs inPostHogSDK.shared.alias("user_12345") ### Feature Flags [Section titled “Feature Flags”](https://skip.dev/docs/modules/skip-posthog/#feature-flags) Check feature flags to control feature rollouts: // Check if a feature is enabledif PostHogSDK.shared.isFeatureEnabled("new_checkout_flow") { // Show new checkout flow} else { // Show original checkout flow} // Get feature flag payloadif let payload = PostHogSDK.shared.getFeatureFlag("experiment_variant") as? String { switch payload { case "control": // Show control variant case "test": // Show test variant default: // Default behavior }} ### Resetting User Data [Section titled “Resetting User Data”](https://skip.dev/docs/modules/skip-posthog/#resetting-user-data) Clear user data when logging out: PostHogSDK.shared.reset() ### Advanced Configuration [Section titled “Advanced Configuration”](https://skip.dev/docs/modules/skip-posthog/#advanced-configuration) Configure additional options during setup: let config = PostHogConfig( apiKey: "your-api-key", host: "https://app.posthog.com") // Customize capture settingsconfig.captureScreenViews = trueconfig.captureApplicationLifecycleEvents = trueconfig.flushAt = 20config.flushIntervalSeconds = 30 PostHogSDK.shared.setup(config) ### Getting the Distinct ID [Section titled “Getting the Distinct ID”](https://skip.dev/docs/modules/skip-posthog/#getting-the-distinct-id) Retrieve the current user’s distinct ID: let distinctId = PostHogSDK.shared.getDistinctId()print("Current user ID: \(distinctId)") Privacy Considerations ---------------------- [Section titled “Privacy Considerations”](https://skip.dev/docs/modules/skip-posthog/#privacy-considerations) PostHog respects user privacy. Make sure to: * Comply with applicable privacy laws (GDPR, CCPA, etc.) * Provide clear privacy policies to your users * Only track necessary data * Use `PostHogSDK.shared.optOut()` to allow users to opt out of tracking // Allow users to opt outPostHogSDK.shared.optOut() // Re-enable trackingPostHogSDK.shared.optIn() Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-posthog/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-posthog/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # XML | Skip [Skip to content](https://skip.dev/docs/modules/skip-xml/#_top) XML === SkipXML is a Skip framework that provides an XML Document parser. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-xml/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-xml.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipXML", package: "skip-xml") ]) ]) Usage ----- [Section titled “Usage”](https://skip.dev/docs/modules/skip-xml/#usage) On Swift/iOS it is implemented using `Foundation.XMLParser` and `Foundation.XMLParserDelegate`, and on the Kotlin/Android side it uses `org.xml.sax.XMLReader` and `org.xml.sax.helpers.DefaultHandler`. Documents are parsed into in-memory `XMLNode` structures as follows: import SkipXML func parseXML(data: Data) throws -> String { let node = try XMLNode.parse(data: data, options: [.processNamespaces]) return node.xmlString() // returns the XML document as a string} Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-xml/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-xml/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # AVKit | Skip [Skip to content](https://skip.dev/docs/modules/skip-av/#_top) AVKit ===== Audio/Video functionality for Skip Lite apps. See what API is included [here](https://skip.dev/docs/modules/skip-av/#api-support) . About ----- [Section titled “About”](https://skip.dev/docs/modules/skip-av/#about) The SkipAV framework provides a small subset of the `AVKit` and `AVFoundation` frameworks as well as a `SwiftUI.VideoPlayer` component for Android based on the `androidx.media3` package’s ExoPlayer. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-av/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-av.git", from: "1.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipAV", package: "skip-av") ]) ]) Dependencies ------------ [Section titled “Dependencies”](https://skip.dev/docs/modules/skip-av/#dependencies) SkipAV depends on the [skip ↗](https://source.skip.dev/skip) transpiler plugin and the [SkipUI](https://skip.dev/docs/modules/skip-ui) package. SkipAV is part of the core _SkipStack_ and is not intended to be imported directly. The transpiler includes `import skip.av.*` in generated Kotlin for any Swift source that imports the `AVKit` or `AVFoundation` frameworks. Example ------- [Section titled “Example”](https://skip.dev/docs/modules/skip-av/#example) import SwiftUIimport AVKit struct PlayerView: View { @State var player = AVPlayer(playerItem: AVPlayerItem(url: URL(string: "https://skip.dev/assets/introduction.mov")!)) @State var isPlaying: Bool = false var body: some View { VStack { Button { isPlaying ? player.pause() : player.play() isPlaying = !isPlaying player.seek(to: .zero) } label: { Image(systemName: isPlaying ? "stop" : "play") .padding() } VideoPlayer(player: player) } }} Event Handling -------------- [Section titled “Event Handling”](https://skip.dev/docs/modules/skip-av/#event-handling) Some `AVPlayerItem` notifications, such as `.didPlayToEndTimeNotification` and `.timeJumpedNotification`, can be handled through the `NotificationCenter`. For example: VideoPlayer(player: player).onReceive(NotificationCenter.default.publisher(for: AVPlayerItem.didPlayToEndTimeNotification)) { event in logger.info("didPlayToEndTimeNotification: \(event)")}.onReceive(NotificationCenter.default.publisher(for: AVPlayerItem.timeJumpedNotification)) { event in logger.info("timeJumpedNotification: \(event)")} AVAudioRecorder --------------- [Section titled “AVAudioRecorder”](https://skip.dev/docs/modules/skip-av/#avaudiorecorder) This framework also supports the ‘AVFoundation.AVAudioRecorder’ and AVFoundation.AVAudioPlayer’ APIs via Android’s MediaRecorder and MediaPlayer. These APIs can be used for audio recording and playback. import SwiftUIimport AVFoundation struct AudioPlayground: View { @State var isRecording: Bool = false @State var errorMessage: String? = nil @State var audioRecorder: AVAudioRecorder? @State var audioPlayer: AVAudioPlayer? var body: some View { #if SKIP let context = androidx.compose.ui.platform.LocalContext.current #endif return VStack { Button(isRecording ? "Stop Recording" : "Start Recording") { self.isRecording ? self.stopRecording() : self.startRecording() } Button("Play Recording") { try? self.playRecording() } if let errorMessage { Text(errorMessage) .foregroundColor(.red) } } .padding() #if SKIP .onAppear { requestAudioRecordingPermission(context: context) } #endif } var captureURL: URL { get { #if SKIP let context = ProcessInfo.processInfo.androidContext let file = java.io.File(context.filesDir, "recording.m4a") return URL(fileURLWithPath: file.absolutePath) #else return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask) .first!.appendingPathComponent("recording.m4a") #endif } } func startRecording() { do { #if !SKIP setupAudioSession() #endif self.audioRecorder = try AVAudioRecorder(url: captureURL, settings: [AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 12000, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]) } catch { print(error.localizedDescription) } audioRecorder?.record() isRecording = true } func stopRecording() { isRecording = false audioRecorder?.stop() } func playRecording() throws { do { guard FileManager.default.fileExists(atPath: captureURL.path) else { errorMessage = "Recording file does not exist." return } audioPlayer = try AVAudioPlayer(contentsOf: captureURL) audioPlayer?.play() errorMessage = "" } catch { logger.error("Could not play audio: \(error.localizedDescription)") errorMessage = "Could not play audio: \(error.localizedDescription)" } } #if SKIP func requestAudioRecordingPermission(context: android.content.Context) { guard let activity = context as? android.app.Activity else { return } // You must also list these permissions in your Manifest.xml let permissions = listOf(android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) androidx.core.app.ActivityCompat.requestPermissions(activity, permissions.toTypedArray(), 1) } #else func setupAudioSession() { let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playAndRecord, mode: .default) try session.setActive(true) } catch { errorMessage = "Failed to setup audio session: \(error.localizedDescription)" } } #endif} API Support ----------- [Section titled “API Support”](https://skip.dev/docs/modules/skip-av/#api-support) The following table summarizes SkipAV’s API support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within `#if !SKIP` blocks - you can use any Swift API you want. Additionally: Support levels: * ✅ – Full * 🟢 – High * 🟡 – Medium * 🟠 – Low | Support | API | | --- | --- | | 🟢 | `AVAudioPlayer`

* `init(contentsOf url: URL) throws`
* `init(data: Data) throws`
* `func prepareToPlay() -> Bool`
* `func play()`
* `func pause()`
* `func stop()`
* `var isPlaying: Bool`
* `var duration: TimeInterval`
* `var numberOfLoops: Int`
* `var volume: Double`
* `var rate: Double`
* `var currentTime: TimeInterval`
* `var url: URL?`
* `var data: Data?` | | 🟢 | `AVAudioRecorder`

* `init(url: URL, settings: [String: Any]) throws`
* `func prepareToRecord() -> Bool`
* `func record()`
* `func pause()`
* `func stop()`
* `func deleteRecording() -> Bool`
* `var isRecording: Bool`
* `var url: URL`
* `var settings: [String: Any]`
* `var currentTime: TimeInterval`
* `func peakPower(forChannel channelNumber: Int) -> Float`
* `func averagePower(forChannel channelNumber: Int) -> Double` | | 🟠 | `AVPlayer`

* `init()`
* `init(playerItem: AVPlayerItem?)`
* `init(url: URL)`
* `func play()`
* `func pause()`
* `func seek(to time: CMTime)` | | 🟠 | `AVPlayerItem`

* `init(url: URL)` | | 🟡 | `VideoPlayer`

* `init(player: AVPlayer?)` | Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-av/#contributing) We welcome contributions to SkipAV. The Skip product [documentation](https://skip.dev/docs/contributing/) includes helpful instructions and tips on local Skip library development. --- # Device Hardware | Skip [Skip to content](https://skip.dev/docs/modules/skip-device/#_top) Device Hardware =============== The SkipDevice module is a dual-platform Skip framework that provides access to network reachability, location, and device sensor data. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-device/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-device.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipDevice", package: "skip-device") ]) ]) Network Reachability -------------------- [Section titled “Network Reachability”](https://skip.dev/docs/modules/skip-device/#network-reachability) You can check whether the device is currenly able to access the network with: let isReachable: Bool = networkReachability.isNetworkReachable ### Network Reachability Permissions [Section titled “Network Reachability Permissions”](https://skip.dev/docs/modules/skip-device/#network-reachability-permissions) In order to access the device’s photos or media library, you will need to declare the permissions in the app’s metadata. On Android, the `app/src/main/AndroidManifest.xml` file will need to be edited to include: Location -------- [Section titled “Location”](https://skip.dev/docs/modules/skip-device/#location) You can request a single current device location with: let provider = LocationProvider()let location: LocationEvent = try await provider.fetchCurrentLocation()logger.log("latitude: \(location.latitude) longitude: \(location.longitude) altitude: \(location.altitude)") You can also subscribe to a stream of location updates like so: import SwiftUIimport SkipKit // for PermissionManagerimport SkipDevice struct LocationView : View { @State var event: LocationEvent? var body: some View { VStack { if let event = event { Text("latitude: \(event.latitude)") Text("longitude: \(event.longitude)") Text("altitude: \(event.altitude)") Text("course: \(event.course)") Text("speed: \(event.speed)") } } .font(Font.body.monospaced()) .task { // SkipKit provided PermissionManager, which creates a user-interface to request individual permissions if await PermissionManager.requestLocationPermission(precise: true, always: false).isAuthorized == false { logger.warning("permission refused for ACCESS_FINE_LOCATION") return } let provider = LocationProvider() // must retain reference provider.updateInterval = 1.0 do { for try await event in provider.monitor() { self.event = event // if cancelled { break } } } catch { logger.error("error updating location: \(error)") } provider.stop() } }} ### Location Permissions [Section titled “Location Permissions”](https://skip.dev/docs/modules/skip-device/#location-permissions) In order to access the device’s location, you will need to declare the permissions in the app’s metadata. On Android, the `app/src/main/AndroidManifest.xml` file will need to be edited to include one of the following permissions: On iOS, you will need to add the `NSLocationWhenInUseUsageDescription` key to your `Darwin/AppName.xcconfig` file: INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This app uses your location to …" Motion ------ [Section titled “Motion”](https://skip.dev/docs/modules/skip-device/#motion) ### Motion Permissions [Section titled “Motion Permissions”](https://skip.dev/docs/modules/skip-device/#motion-permissions) On iOS, you will need to add the `NSMotionUsageDescription` key to your `Darwin/AppName.xcconfig` file: INFOPLIST_KEY_NSMotionUsageDescription = "This app uses your motion information to …" Accelerometer ------------- [Section titled “Accelerometer”](https://skip.dev/docs/modules/skip-device/#accelerometer) The `AccelerometerProvider` type provides an `AsyncThrowingStream` of device accelerometer changes. It can be used in a View like this: import SwiftUIimport SkipDevice struct AccelerometerView : View { @State var event: AccelerometerEvent? var body: some View { VStack { if let event = event { Text("x: \(event.x)") Text("y: \(event.y)") Text("z: \(event.z)") } } .task { let provider = AccelerometerProvider() // must retain reference provider.updateInterval = 0.1 do { for try await event in provider.monitor() { self.event = event // if cancelled { break } } } catch { logger.error("error updating accelerometer: \(error)") } provider.stop() } }} Gyroscope --------- [Section titled “Gyroscope”](https://skip.dev/docs/modules/skip-device/#gyroscope) The `GyroscopeProvider` type provides an `AsyncThrowingStream` of device gyroscope changes. It can be used in a View like this: import SwiftUIimport SkipDevice struct GyroscopeView : View { @State var event: GyroscopeEvent? var body: some View { VStack { if let event = event { Text("x: \(event.x)") Text("y: \(event.y)") Text("z: \(event.z)") } } .task { let provider = GyroscopeProvider() // must retain reference provider.updateInterval = 0.1 do { for try await event in provider.monitor() { self.event = event // if cancelled { break } } } catch { logger.error("error updating gyroscope: \(error)") } provider.stop() } }} Magnetometer ------------ [Section titled “Magnetometer”](https://skip.dev/docs/modules/skip-device/#magnetometer) The `MagnetometerProvider` type provides an `AsyncThrowingStream` of device magnetometer changes. It can be used in a View like this: import SwiftUIimport SkipDevice struct MagnetometerView : View { @State var event: MagnetometerEvent? var body: some View { VStack { if let event = event { Text("x: \(event.x)") // X-axis magnetic field in microteslas Text("y: \(event.y)") // Y-axis magnetic field in microteslas Text("z: \(event.z)") // Z-axis magnetic field in microteslas } } .font(Font.body.monospaced()) .task { let provider = MagnetometerProvider() // must retain reference provider.updateInterval = 0.1 do { for try await event in provider.monitor() { self.event = event // if cancelled { break } } } catch { logger.error("error updating magnetometer: \(error)") } provider.stop() } }} Barometer --------- [Section titled “Barometer”](https://skip.dev/docs/modules/skip-device/#barometer) The `BarometerProvider` type provides an `AsyncThrowingStream` of device barometer changes. It can be used in a View like this: import SwiftUIimport SkipDevice struct BarometerView : View { @State var event: BarometerEvent? var body: some View { VStack { if let event = event { Text("pressure: \(event.pressure)") // The recorded pressure, in kilopascals. Text("relativeAltitude: \(event.relativeAltitude)") // The change in altitude (in meters) since the first reported event. } } .font(Font.body.monospaced()) .task { let provider = BarometerProvider() // must retain reference provider.updateInterval = 0.1 do { for try await event in provider.monitor() { self.event = event // if cancelled { break } } } catch { logger.error("error updating barometer: \(error)") } provider.stop() } }} ### Barometer Permissions [Section titled “Barometer Permissions”](https://skip.dev/docs/modules/skip-device/#barometer-permissions) In order to access the device’s barometer, you will need to declare the permissions in the app’s metadata. On Android, the `app/src/main/AndroidManifest.xml` file will need to be edited to include: Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-device/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-device/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-device/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes --- # Camera & Media | Skip [Skip to content](https://skip.dev/docs/modules/skip-kit/#_top) Camera & Media ============== This Skip Lite module enhances the `SkipUI` package with commonly-used features, such as a permission checker and a picker for photos and other media. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-kit/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-kit.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipKit", package: "skip-kit") ]) ]) Cache ----- [Section titled “Cache”](https://skip.dev/docs/modules/skip-kit/#cache) The `Cache` class manages a memory-pressure-aware cache that can be used for storing temporary values. Example usage: // Create a cache that can store up to 100 bytes of Data instances// and will evict everything when the app is put in the backgroundlet cache = Cache(evictOnBackground: true, limit: 100, cost: \.count) cache.putValue(Data(count: 1), for: UUID()) // total cost = 1cache.putValue(Data(count: 99), for: UUID()) // total cost = 100cache.putValue(Data(count: 1), for: UUID()) // total cost = 101, so cache will evict older entries PermissionManager ----------------- [Section titled “PermissionManager”](https://skip.dev/docs/modules/skip-kit/#permissionmanager) The `PermissionManager` provides the ability to request device permissions. For example: import SkipKitimport SkipDevice let locationProvider = LocationProvider() if await PermissionManager.requestPermission(.ACCESS_FINE_LOCATION) == true { let location = try await locationProvider.fetchCurrentLocation()} In addition to symbolic constants, there are also functions for requesting specific permissions with various parameters: static func queryLocationPermission(precise: Bool, always: Bool) -> PermissionAuthorizationstatic func requestLocationPermission(precise: Bool, always: Bool) async -> PermissionAuthorization static func queryPostNotificationPermission() async -> PermissionAuthorizationstatic func requestPostNotificationPermission(alert: Bool = true, sound: Bool = true, badge: Bool = true) async throws -> PermissionAuthorization static func queryCameraPermission() -> PermissionAuthorizationstatic func requestCameraPermission() async -> PermissionAuthorization static func queryRecordAudioPermission() -> PermissionAuthorizationstatic func requestRecordAudioPermission() async -> PermissionAuthorization static func queryContactsPermission(readWrite: Bool) -> PermissionAuthorizationstatic func requestContactsPermission(readWrite: Bool) async throws -> PermissionAuthorization static func queryCalendarPermission(readWrite: Bool) -> PermissionAuthorizationstatic func requestCalendarPermission(readWrite: Bool) async throws -> PermissionAuthorization static func queryReminderPermission(readWrite: Bool) -> PermissionAuthorizationstatic func requestReminderPermission(readWrite: Bool) async throws -> PermissionAuthorization static func queryPhotoLibraryPermission(readWrite: Bool) -> PermissionAuthorizationstatic func requestPhotoLibraryPermission(readWrite: Bool) async -> PermissionAuthorization To request an arbitrary Android permission for which there may be no iOS equivalent, you can pass the string literal. For a list of common permission literals, see [https://developer.android.com/reference/android/Manifest.permission ↗](https://developer.android.com/reference/android/Manifest.permission) . For example, to request the SMS sending permission: let granted = await PermissionManager.requestPermission("android.permission.SEND_SMS") Camera and Media selection -------------------------- [Section titled “Camera and Media selection”](https://skip.dev/docs/modules/skip-kit/#camera-and-media-selection) The `View.withMediaPicker(type:isPresented:selectedImageURL:)` extension function can be used to enable the acquisition of an image from either the system camera or the user’s media library. On iOS, this camera selector will be presented in a `fullScreenCover` view, whereas the media library browser will be presented in a `sheet`. In both cases, a standad `UIImagePickerController` will be used to acquire the media. On Android, the camera and library browser will be activated through an Intent after querying for the necessary permissions. Following is an example of implementing a media selection button that will bring up the system user interface. import SkipKit /// A button that enables the selection of media from the library or the taking of a photo.////// The selected/captured image will be communicated through the `selectedImageURL` binding,/// which can be observed with `onChange` to perform an action when the media URL is acquired.struct MediaButton : View { let type: MediaPickerType // either .camera or .library @Binding var selectedImageURL: URL? @State private var showPicker = false var body: some View { Button(type == .camera ? "Take Photo" : "Select Media") { showPicker = true // activate the media picker } .withMediaPicker(type: .camera, isPresented: $showPicker, selectedImageURL: $selectedImageURL) }} ### Camera and Media Permissions [Section titled “Camera and Media Permissions”](https://skip.dev/docs/modules/skip-kit/#camera-and-media-permissions) In order to access the device’s photos or media library, you will need to declare the permissions in the app’s metadata. On iOS this can be done by editing the `Darwin/AppName.xcconfig` file and adding the lines: INFOPLIST_KEY_NSCameraUsageDescription = "This app needs to access the camera";INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "This app needs to access the photo library."; On Android, the `app/src/main/AndroidManifest.xml` file will need to be edited to include camera permissions as well as a FileProvider implementation so the camera can share a Uri with the app. For example: In addition to editing the manifest, you must also manually create the `xml/file_paths` reference from the manifest’s provider. This is done by creating the folder `Android/app/src/main/res/xml` in your Skip project and adding a file `file_paths.xml` with the following contents: For an example of a properly configured project, see the Photo Chat sample application. Document Picker --------------- [Section titled “Document Picker”](https://skip.dev/docs/modules/skip-kit/#document-picker) The `View.withDocumentPicker(isPresented: Binding, allowedContentTypes: [UTType], selectedDocumentURL: Binding, selectedFilename: Binding, selectedFileMimeType: Binding)` extension function can be used to select a document of the specified UTType from the device to use in the App. On iOS it will use an instance of `FileImporter` to display the system file picker, essentially allowing to select a file from the Files application, while on Android it relies on the the system document picker via the Activity result for the `ACTION_OPEN_DOCUMENT`. Once the user selects a file it will receive an `uri`, that need to be parsed to be used outside the scope of the caller. For doing so it will copy the file inside the App cache folder and expose the cached url instead of the original picked file url. For example: Button("Pick Document") { presentPreview = true}.buttonStyle(.borderedProminent).withDocumentPicker(isPresented: $presentPreview, allowedContentTypes: [.image, .pdf], selectedDocumentURL: $selectedDocument, selectedFilename: $filename, selectedFileMimeType: $mimeType) Document Preview ---------------- [Section titled “Document Preview”](https://skip.dev/docs/modules/skip-kit/#document-preview) The `View.withDocumentPreview(isPresented: Binding, documentURL: URL?, filename: String?, type: String?)` extension function can be used to preview a document available to the app (either selected with the provided `Document Picker` or downloaded locally by the App). On iOS it will use an instance of `QLPreviewController` to display the file at the provided url while on Android it will open an Intent chooser for selecting the appropriate app for the provided file mime type. On iOS there’s no need to provide a filename or a mime type, but sometimes on Android is necessary (for example when selecting a document using the document picker). On Android if no mime type is supplied it will try to guess it by the file url. If no mime type can be found the application chooser will be empty. A file provider (like the one used for using the `MediaPicker`) is necessary for the Intent to correctly pass reading permission to the receiving app. As long as your Skip already implements the FileProvider and the `file_paths.xml` as described in the `Camera and Media Permission` section there’s nothing else needed, otherwise you need to follow the instructions in the mentioned section. Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-kit/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-kit/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-kit/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes --- # Socket.IO | Skip [Skip to content](https://skip.dev/docs/modules/skip-socketio/#_top) Socket.IO ========= This is a Skip Swift/Kotlin library project that abstracts the Socket.io [iOS ↗](https://socket.io/blog/socket-io-on-ios/) and [Android ↗](https://socket.io/blog/native-socket-io-and-android/) APIs. About Socket.io --------------- [Section titled “About Socket.io”](https://skip.dev/docs/modules/skip-socketio/#about-socketio) [Socket.IO ↗](https://socket.io/) is a library for real-time, event-based, bidirectional communication that provides a robust abstraction layer over various transport protocols (primarily WebSocket and HTTP long-polling). Key features of the Socket.IO library that go beyond the raw WebSocket protocol include: * Automatic reconnection with exponential backoff. * Packet buffering and automatic acknowledgments. * Event-driven communication with a simplified API (emit and on). * Multiplexing through namespaces and broadcasting to rooms. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-socketio/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-socketio.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipSocketIO", package: "skip-socketio") ]) ]) API Compatibility ----------------- [Section titled “API Compatibility”](https://skip.dev/docs/modules/skip-socketio/#api-compatibility) SkipSocketIO provides a similar API surface as the [SkipSocketIO Swift SDK ↗](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketIOClient.html) ([source ↗](https://github.com/socketio/socket.io-client-swift) ), ensuring a familiar development experience. All methods should behave identically on both iOS and Android platforms, allowing you to write once and deploy everywhere. On Android, the API calls are forwarded to their equivalents in the [Socket.IO Java SDK ↗](https://socketio.github.io/socket.io-client-java/installation.html) ([source ↗](https://github.com/socketio/socket.io-client-java) ). Usage Examples -------------- [Section titled “Usage Examples”](https://skip.dev/docs/modules/skip-socketio/#usage-examples) ### Connection [Section titled “Connection”](https://skip.dev/docs/modules/skip-socketio/#connection) let socket = SkipSocketIOClient(socketURL: URL(string: "https://example.org")!, options: [ .compress, .path("/mypath/"), .secure(false), .forceNew(false), .forcePolling(false), .reconnects(true), .reconnectAttempts(5), .reconnectWait(2), .reconnectWaitMax(10), .extraHeaders(["X-Custom-Header": "Value"]),]) socket.on("connection") { params in logger.log("socket connection established")} socket.connect() socket.on("onUpdate") { params in logger.log("onUpdate event received with parameters: \(params)")} socket.emit("update", ["hello", 1, "2", Data()]) socket.disconnect() Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-socketio/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-socketio/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Ahoy Skipper (Split Fuse) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-ahoy/#_top) Ahoy Skipper (Split Fuse) ========================= This is a [Skip ↗](https://skip.tools/) dual-platform app project. Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-ahoy/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and translates the project into a Kotlin Gradle project for Android using the skipstone plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary Skip prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. The project can be validated with `skip verify`. Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-ahoy/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. The project can be opened and run in Xcode from `Project.xcworkspace`, which also enabled parallel development of any Skip libary dependencies. To run both the Swift and Kotlin apps simultaneously, launch the “AhoySkipper App” target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the Skip app to a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app, or using `adb logcat` from a terminal. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-ahoy/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Travel Bookings (Fuse) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-bookings-fuse/#_top) Travel Bookings (Fuse) ====================== This is a Skip Fuse dual-platform app project. It builds a native app for both iOS and Android. Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-bookings-fuse/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and generates a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-bookings-fuse/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the TravelBookingsFuse target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the generated Kotlin app. --- # Gradle Project Reference | Skip [Skip to content](https://skip.dev/docs/gradle/#_top) Gradle Project Reference ======================== Gradle is the build system for Android, and is the equivalent of SwiftPM for Darwin Swift development. When Skip builds your project, it bridges and/or transpiles your Swift code to Kotlin, and it also converts your `Package.swift` file into a `build.gradle.kts` file in the Skip plugin’s output directory. This conversion happens transitively on all your project’s Skip-enabled dependencies, including the [Skip Core Frameworks](https://skip.dev/docs/faq/#skipstack) modules. This results in a local inter-connected network of Gradle projects containing the entirety of your project’s source dependency tree. The following sections detail Skip’s use of Gradle, including how to build with Gradle from the command line. Skip App Projects ----------------- [Section titled “Skip App Projects”](https://skip.dev/docs/gradle/#skip-app-projects) Consider a minimal Swift app project created by the commands: skip init --show-tree --no-build --no-test --no-module-tests --appid=skip.hello.App --version 1.0.0 hello-skip HelloSkip * Package.swift Standard SwiftPM Package * README.md * Skip.env project config (name, version, appid, etc.) * DirectorySources * DirectoryHelloSkip * ContentView.swift * HelloSkip.swift * HelloSkipApp.swift * DirectoryResources * Localizable.xcstrings * DirectorySkip * skip.yml module config (dependencies, build mode) * DirectoryAndroid * Directoryapp * build.gradle.kts Top-level Gradle build config * proguard-rules.pro * Directorysrc * Directorymain * AndroidManifest.xml Android project metadata * Directorykotlin * Directoryhello * Directoryskip * Main.kt Entry point to Android app * Directorygradle * Directorywrapper * gradle-wrapper.properties * gradle.properties * settings.gradle.kts * DirectoryDarwin * DirectoryAssets.xcassets * DirectoryAccentColor.colorset * Contents.json * DirectoryAppIcon.appiconset * Contents.json * Contents.json * Entitlements.plist * HelloSkip.xcconfig iOS Project config * DirectoryHelloSkip.xcodeproj Xcode project * project.pbxproj * DirectorySources * Main.swift Entry point to iOS app Running `swift build` in the `hello-skip` folder builds the module and performs the Android translation. This derived project hierarchy will look like: hello-skip % tree .build/plugins/outputs/hello-skip/HelloSkip/skipstone/ * Directory.build/plugins/outputs/hello-skip/HelloSkip/skipstone/ * DirectoryHelloSkip * build.gradle.kts * proguard-rules.pro * Directorysrc * Directorymain * Directorykotlin * Directoryhello * Directoryskip * ContentView.kt * HelloSkip.kt * HelloSkipApp.kt * Directoryresources * Directoryhello * Directoryskip * DirectoryResources * Localizable.xcstrings \-> /private/tmp/hello-skip/Sources/HelloSkip/Resources/Localizable.xcstrings * resources.lst * HelloSkip.skipcode.json * DirectorySkipFoundation/ \-> ../../../skip-foundation/SkipFoundation/skipstone/SkipFoundation * … * DirectorySkipLib/ \-> ../../../skip-lib/SkipLib/skipstone/SkipLib * … * DirectorySkipModel/ \-> ../../../skip-model/SkipModel/skipstone/SkipModel * … * DirectorySkipUI/ \-> ../../../skip-ui/SkipUI/skipstone/SkipUI * … * DirectorySkipUnit/ \-> ../../../skip-unit/SkipUnit/skipstone/SkipUnit * … * Directorygradle * Directorywrapper * gradle-wrapper.properties * gradle.properties * settings.gradle.kts The resulting Gradle project hierarchy contains not only the links to the plugin output for the current project, but also links to the project output for each dependent module. These dependencies are automatically created as a result of the Skip plugin being run on the dependent projects. * * * Building Locally ---------------- [Section titled “Building Locally”](https://skip.dev/docs/gradle/#building-locally) Gradle projects can be run manually, either from Android Studio or using the `gradle` command-line tool. Skip app projects created with `skip init` have a root `Android/` folder that contains a `settings.gradle.kts` file. This can be opened in Android Studio, or it can be run directly from the command line. $ skip init --appid=com.xyz.HelloSkip hello-skip HelloSkip Initializing Skip library hello-skip[✓] Create project hello-skip (0.73s)[✓] Resolve dependencies (5.89s)[✓] Build hello-skip (15.2s)[✓] Created module HelloSkip in ~/Desktop/hello-skip/HelloSkip.xcodeproj $ cd hello-skip $ swift build --build-tests Fetched https://source.skip.tools/skip.git (0.92s)…[294/294] Linking libHelloSkip.dylibBuild complete! (23.13s) $ skip gradle -p .build/plugins/outputs/hello-skip/HelloSkip/skipstone/HelloSkip test GRADLE> > Task :HelloSkip:preBuild UP-TO-DATE48 collapsed lines…GRADLE> > Task :HelloSkip:testDebugUnitTestGRADLE> > Task :HelloSkip:testGRADLE>GRADLE> BUILD SUCCESSFUL in 20sGRADLE> 204 actionable tasks: 204 executednote: Gradle SUCCESSFUL $ skip gradle -p Android/ assemble GRADLE> > Task :app:preBuild UP-TO-DATEGRADLE> > Task :app:preDebugBuild UP-TO-DATEGRADLE> > Task :app:mergeDebugNativeDebugMetadata NO-SOURCEGRADLE> > Task :app:generateDebugBuildConfigGRADLE> > Task :HelloSkip:preBuild UP-TO-DATE…GRADLE> > Task :SkipUI:bundleReleaseAarGRADLE> > Task :HelloSkip:compileDebugJavaWithJavac NO-SOURCE…GRADLE> > Task :app:processReleaseJavaResGRADLE> > Task :HelloSkip:extractDebugAnnotationsGRADLE> > Task :HelloSkip:mergeDebugGeneratedProguardFilesGRADLE> > Task :HelloSkip:mergeDebugConsumerProguardFilesGRADLE> > Task :SkipUI:assembleReleaseGRADLE> > Task :SkipUI:assembleGRADLE> > Task :HelloSkip:mergeDebugJavaResourceGRADLE> > Task :app:compileDebugKotlinGRADLE> > Task :HelloSkip:syncDebugLibJarsGRADLE> > Task :app:compileDebugJavaWithJavacGRADLE> > Task :app:mergeReleaseJavaResourceGRADLE> > Task :app:dexBuilderDebugGRADLE> > Task :app:processDebugJavaResGRADLE> > Task :app:mergeDebugGlobalSyntheticsGRADLE> > Task :app:mergeProjectDexDebugGRADLE> > Task :app:mergeLibDexDebugGRADLE> > Task :HelloSkip:bundleDebugAarGRADLE> > Task :HelloSkip:assembleDebugGRADLE> > Task :HelloSkip:assembleGRADLE> > Task :app:mergeDebugJavaResourceGRADLE> > Task :app:minifyReleaseWithR8GRADLE> > Task :app:shrinkReleaseResGRADLE> > Task :app:packageReleaseGRADLE> > Task :app:createReleaseApkListingFileRedirectGRADLE> > Task :app:assembleReleaseGRADLE> > Task :app:assembleGRADLE>GRADLE> BUILD SUCCESSFUL in 53sGRADLE> 403 actionable tasks: 403 executed The `.apk` from the build will be placed in the `.build/Android/app/outputs/apk/` folder: $ ls -lah .build/Android/app/outputs/apk/*/*.apk 57M .build/Android/app/outputs/apk/debug/app-debug.apk13M .build/Android/app/outputs/apk/release/app-release-unsigned.apk * * * Skip Frameworks --------------- [Section titled “Skip Frameworks”](https://skip.dev/docs/gradle/#skip-frameworks) A valid Skip project must contain a `Skip/skip.yml` file in each Skip-enabled module. This file is a YAML document that describes the module’s representation in the Android/Gradle world. During the build process, this file is combined with all the dependency project’s `skip.yml` files and used to affect how the Gradle project is created. Use `skip.yml` to customize the Gradle build process, or to add dependencies on external Java or Kotlin libraries that you can then use directly from Skip code. Gradle projects use the files `settings.gradle.kts` and `build.gradle.kts` for configuration, much like a Swift Package Manager project uses `Package.swift`. These are kotlin-script files that can configure all aspects of the build process, from compilation arguments to output post-processing. More can be read on the topic at [https://docs.gradle.org/current/userguide/writing\_build\_scripts.html ↗](https://docs.gradle.org/current/userguide/writing_build_scripts.html) . Skip abstracts away the details of the Gradle project configuration for Framework projects by automatically deriving a valid Gradle project from the SwiftPM `Package.swift` dependencies combined with the contents of each project’s `Skip/skip.yml` file. The Skip build plugin outputs a Gradle project folder hierarchy as part of its output process. ### Framework Build Outputs [Section titled “Framework Build Outputs”](https://skip.dev/docs/gradle/#framework-build-outputs) _Framework_ projects differ from _app_ projects in that while app projects surface the various Gradle app-level build files (`Android/settings.gradle.kts` and `Android/app/build.gradle.kts`), framework projects generate and maintain the build structures for the generated Android project. Skip considers the generated Gradle project, along with any generated Kotlin code, to be ephemeral. Cleaning the build may delete the files, and re-running the transpiler will override any changes. Unless you (manually) export or archive the Gradle project, it may be overwritten at any time. When building from the command-line using `swift build` or `skip test`, the gradle projects will be output to the `.build/plugins/outputs/` folder, each in its own package-named directory. When building from Xcode, the outputs are instead placed in `~/Library/Developer/Xcode/DerivedData/ProjectName-identifier/SourcePackages/plugins/`. The Skip plugin is run in a restrictive sandbox that limits the output folders that the plugin can write to. For this reason, each separate dependency is output to its own separate folder, and each depending project references it by making a symbolic link to the local output for that project. For example, the `HelloSkip` app’s local dependencies on the `Skip Core` frameworks can be seen: $ tree -L 5 .build/plugins/outputs * Directory.build/plugins/outputs * Directoryhello-skip * DirectoryHelloSkip * Directoryskipstone * DirectoryHelloSkip * build.gradle.kts * proguard-rules.pro * src * HelloSkip.skipcode.json * DirectorySkipFoundation/ \-> ../../../skip-foundation/SkipFoundation/skipstone/SkipFoundation * … * DirectorySkipLib/ \-> ../../../skip-lib/SkipLib/skipstone/SkipLib * … * DirectorySkipModel/ \-> ../../../skip-model/SkipModel/skipstone/SkipModel * … * DirectorySkipUI/ \-> ../../../skip-ui/SkipUI/skipstone/SkipUI * … * DirectorySkipUnit/ \-> ../../../skip-unit/SkipUnit/skipstone/SkipUnit * … * Directorygradle * wrapper * gradle.properties * settings.gradle.kts * Directoryskip-foundation * DirectorySkipFoundation * Directoryskipstone * DirectorySkipFoundation * build.gradle.kts * proguard-rules.pro * src * SkipFoundation.skipcode.json * DirectorySkipLib/ \-> ../../../skip-lib/SkipLib/skipstone/SkipLib * … * DirectorySkipUnit/ \-> ../../../skip-unit/SkipUnit/skipstone/SkipUnit * … * Directorygradle * wrapper * gradle.properties * settings.gradle.kts * Directoryskip-lib * DirectorySkipLib * Directoryskipstone * DirectorySkipLib * build.gradle.kts * proguard-rules.pro * src * SkipLib.skipcode.json * DirectorySkipUnit/ \-> ../../../skip-unit/SkipUnit/skipstone/SkipUnit * … * Directorygradle * wrapper * gradle.properties * settings.gradle.kts * Directoryskip-model * DirectorySkipModel * Directoryskipstone * DirectorySkipLib/ \-> ../../../skip-lib/SkipLib/skipstone/SkipLib * … * DirectorySkipModel * build.gradle.kts * proguard-rules.pro * src * SkipModel.skipcode.json * DirectorySkipUnit/ \-> ../../../skip-unit/SkipUnit/skipstone/SkipUnit * … * Directorygradle * wrapper * gradle.properties * settings.gradle.kts * Directoryskip-ui * DirectorySkipUI * Directoryskipstone * DirectorySkipFoundation/ \-> ../../../skip-foundation/SkipFoundation/skipstone/SkipFoundation * … * DirectorySkipLib/ \-> ../../../skip-lib/SkipLib/skipstone/SkipLib * … * DirectorySkipModel/ \-> ../../../skip-model/SkipModel/skipstone/SkipModel * … * DirectorySkipUI * build.gradle.kts * proguard-rules.pro * src * SkipUI.skipcode.json * DirectorySkipUnit/ \-> ../../../skip-unit/SkipUnit/skipstone/SkipUnit * … * Directorygradle * wrapper * gradle.properties * settings.gradle.kts * Directoryskip-unit * DirectorySkipUnit * Directoryskipstone * DirectorySkipUnit * build.gradle.kts * proguard-rules.pro * src * SkipUnit.skipcode.json * Directorygradle * wrapper * gradle.properties * settings.gradle.kts Building an app project APK --------------------------- [Section titled “Building an app project APK”](https://skip.dev/docs/gradle/#building-an-app-project-apk) Building a framework project AAR -------------------------------- [Section titled “Building a framework project AAR”](https://skip.dev/docs/gradle/#building-a-framework-project-aar) For more information on the published output format [Publishing a project as module ↗](https://docs.gradle.org/current/userguide/publishing_setup.html) and Android Gradle Plugin [Upload your library ↗](https://developer.android.com/build/publish-library/upload-library#local-repo) . * * * Gradle Dependencies ------------------- [Section titled “Gradle Dependencies”](https://skip.dev/docs/gradle/#gradle-dependencies) Every Skip App project depends on the Skip Core Frameworks, which contain the five core modules for any application, and whose `skip.yml` files can serve as useful examples: [SkipUI ↗](https://source.skip.tools/skip-ui/blob/main/Sources/SkipUI/Skip/skip.yml) , [SkipModel ↗](https://source.skip.tools/skip-model/blob/main/Sources/SkipModel/Skip/skip.yml) , [SkipFoundation ↗](https://source.skip.tools/skip-foundation/blob/main/Sources/SkipFoundation/Skip/skip.yml) , [SkipLib ↗](https://source.skip.tools/skip-lib/blob/main/Sources/SkipLib/Skip/skip.yml) , and [SkipUnit ↗](https://source.skip.tools/skip-unit/blob/main/Sources/SkipUnit/Skip/skip.yml) . Each of these projects are automatically transpiled in turn, resulting in a web of project links that constitute a buildable Gradle project. As these are dependencies that are locally checked out, their transpiled output can be referenced from Gradle through the relative symbolic links that are created as part of a module’s transpilation. Read the [Dependencies](https://skip.dev/docs/dependencies) documentation to learn more about using `skip.yml` to add Java/Kotlin dependencies. ![Project Diagram](https://assets.skip.dev/diagrams/skip-diagrams-project.svg) --- # FFI | Skip [Skip to content](https://skip.dev/docs/modules/skip-ffi/#_top) FFI === This is a Skip Lite Swift/Kotlin library project that provides the capability for Skip’s Kotlin transpiled code to call into C and C++ libraries on Android. On the Kotlin side, SkipFFI uses the Java Native Access ([JNA ↗](https://github.com/java-native-access/jna) ) library to simulate Swift types like `Swift.OpaquePointer` as `com.sun.jna.Pointer` pointer references, and implements `Swift.withUnsafeMutablePointer` using a `com.sun.jna.ptr.PointerByReference` on the Java side. This capability is used by Skip frameworks like [SkipSQL](https://skip.dev/docs/modules/skip-sql) and [SkipScript](https://skip.dev/docs/modules/skip-script) to provide a unified API to underlying native C APIs on both Darwin and Android. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-ffi/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-ffi.git", from: "1.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipFFI", package: "skip-ffi") ]) ]) Example ------- [Section titled “Example”](https://skip.dev/docs/modules/skip-ffi/#example) #if !SKIPimport Darwin#elseimport SkipFFIlet Darwin = BionicDarwin() func BionicDarwin() -> BionicDarwin { com.sun.jna.Native.load("c", (BionicDarwin.self as kotlin.reflect.KClass).java)} protocol BionicDarwin : com.sun.jna.Library { func abs(_ value: Int32) -> Int32 func malloc(_ size: Int32) -> OpaquePointer func free(_ ptr: OpaquePointer) -> Int32 func getenv(_ key: String) -> String?}#endif // Fully-qualified Module.fname() will call through SkipFFI to the C interfaceDarwin.abs(-12) // 12Darwin.free(Darwin.malloc(8)) Implementation -------------- [Section titled “Implementation”](https://skip.dev/docs/modules/skip-ffi/#implementation) SkipFFI’s implementation provides: public typealias OpaquePointer = com.sun.jna.Pointerpublic typealias UnsafeMutableRawPointer = com.sun.jna.ptr.PointerByReference public func withUnsafeMutablePointer(to pointerRef: InOut, block: (UnsafeMutableRawPointer) throws -> T) rethrows -> T Working with Data ----------------- [Section titled “Working with Data”](https://skip.dev/docs/modules/skip-ffi/#working-with-data) SkipFFI doesn’t work with the Foundation Data API directly. If you need to access raw bytes, you can use the APIs directly: let blob = Data(…)let size = blob.count #if SKIPlet buf = java.nio.ByteBuffer.allocateDirect(size)buf.put(blob.kotlin(nocopy: true)) // transfer the byteslet ptr = com.sun.jna.Native.getDirectBufferPointer(buf)try check(code: SQLite3.sqlite3_bind_blob(stmnt, index, ptr, size, nil))#elsetry blob.withUnsafeBytes { ptr in try check(code: SQLite3.sqlite3_bind_blob(stmnt, index, ptr.baseAddress, size, nil))}#endif Embedded C Code --------------- [Section titled “Embedded C Code”](https://skip.dev/docs/modules/skip-ffi/#embedded-c-code) With SkipFFI you can embed C code in your dual-platform Skip framework, and use SkipFFI to create an idiomatic wrapper around the code that can be used both from Swift and the transpiled Kotlin. SkipFFI can be used to provide a direct interface from your transpiled Kotlin to an embedded C library. It configures gradle’s support for cmake build files and the Android NDK toolchain to build the embedded C library for each of Android’s supported architectures, much in the same way as Xcode and SwiftPM handle building and linking C source with Swift code for various architectures. See the [Skip C Demo ↗](http://source.skip.dev/skip-c-demo) sample project for an example of using C files to provide a unified API to both Swift and Kotlin. Local vs. Instrumeted Testing ----------------------------- [Section titled “Local vs. Instrumeted Testing”](https://skip.dev/docs/modules/skip-ffi/#local-vs-instrumeted-testing) When you build and test the `skip-c-demo` project out of the box, either from Xcode or the Terminal using `swift test`, the normal Skip testing process will occur: the Swift test cases will be compiled an run against the macOS architecture, and then the special `XCSkipTests` will cause the `SkipUnit` framework to invoke `gradle test` against the transpiled source and test case files. And while this does work transparently with any embedded C files, you should be aware that since local testing run on the local macOS JVM, it isn’t actually exercising the cross-compiled Android native libraries. It is, rather, linking to the locally-built C library that was built by SwiftPM. This is the fastest way to test the native SkipFFI bridging, but when your C code needs to interface with libraries that are only available on Android (such as the various NDK APIs: [https://developer.android.com/ndk/guides/stable\_apis ↗](https://developer.android.com/ndk/guides/stable_apis) ), then you will need to test non-locally, against an actual Android emulator or device. In order to test the cross-compiled shared libraries on a real Android system, you need to run the instrumented tests against an Android simulator or device. This is accomplished by launching a simulator from the Android Studio Device Manager and then obtaining the device identifier with the `adb devices` terminal command. If you have a device with the id “emulator-5554”, you can then run the transpiled tests against the simulator with the command: ANDROID_SERIAL=emulator-5554 swift test Similarly, you can set the `ANDROID_SERIAL` environment variable in the Run Arguments screen of the Xcode scheme for the target you are testing, which will have the same effect of running the instrumented tests against the specified emulator or device. Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-ffi/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-ffi/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-ffi/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes --- # QR Codes | Skip [Skip to content](https://skip.dev/docs/modules/skip-qrcode/#_top) QR Codes ======== This is a free Skip Swift/Kotlin library project containing the following modules: SkipQRCode Installation ------------ [Section titled “Installation”](https://skip.dev/docs/modules/skip-qrcode/#installation) Add SkipQRCode as a dependency in your `Package.swift`: dependencies: [ .package(url: "https://source.skip.dev/skip-qrcode.git", "0.0.1"..<"2.0.0")],targets: [ .target( name: "YourApp", dependencies: [ .product(name: "SkipQRCode", package: "skip-qrcode") ] )] Quick Start ----------- [Section titled “Quick Start”](https://skip.dev/docs/modules/skip-qrcode/#quick-start) ### Basic Usage [Section titled “Basic Usage”](https://skip.dev/docs/modules/skip-qrcode/#basic-usage) import SwiftUIimport SkipQRCode struct ContentView: View { @State private var showScanner = false @State private var scannedCode: String? var body: some View { VStack(spacing: 20) { Button("Scan QR Code") { showScanner = true } .buttonStyle(.borderedProminent) if let code = scannedCode { Text("Scanned: \(code)") .font(.headline) } } .sheet(isPresented: $showScanner) { #if os(iOS) BarcodeScannerView { code in scannedCode = code showScanner = false } .ignoresSafeArea() #elseif os(Android) // Android scanner launches as a native activity Color.clear .onAppear { showScanner = false AndroidBarcodeScanner.scan { code in if let code = code { scannedCode = code } } } #endif } }} ### Advanced Example with Error Handling [Section titled “Advanced Example with Error Handling”](https://skip.dev/docs/modules/skip-qrcode/#advanced-example-with-error-handling) import SwiftUIimport SkipQRCode struct AdvancedScannerView: View { @State private var showScanner = false @State private var scannedCode: String? @State private var scanError: String? @State private var isScanning = false var body: some View { VStack(spacing: 20) { Text("QR Code Scanner") .font(.title) if let code = scannedCode { VStack(alignment: .leading, spacing: 8) { Text("Scanned Code:") .font(.caption) .foregroundColor(.secondary) Text(code) .font(.body) .padding() .background(Color.gray.opacity(0.1)) .cornerRadius(8) } .padding() } if let error = scanError { Text("Error: \(error)") .foregroundColor(.red) .padding() } Button(action: startScanning) { HStack { Image(systemName: "qrcode.viewfinder") Text(isScanning ? "Scanning..." : "Scan QR Code") } } .buttonStyle(.borderedProminent) .disabled(isScanning) } .padding() .sheet(isPresented: $showScanner) { #if os(iOS) BarcodeScannerView { code in handleScanResult(code) } .ignoresSafeArea() #elseif os(Android) Color.clear .onAppear { showScanner = false performAndroidScan() } #endif } } private func startScanning() { scannedCode = nil scanError = nil isScanning = true showScanner = true } private func handleScanResult(_ code: String) { scannedCode = code showScanner = false isScanning = false } #if os(Android) private func performAndroidScan() { AndroidBarcodeScanner.scan { code in isScanning = false if let code = code { scannedCode = code } else { scanError = "Scan cancelled or failed" } } } #endif} Platform-Specific Behavior -------------------------- [Section titled “Platform-Specific Behavior”](https://skip.dev/docs/modules/skip-qrcode/#platform-specific-behavior) ### iOS [Section titled “iOS”](https://skip.dev/docs/modules/skip-qrcode/#ios) **Implementation**: Uses VisionKit’s `DataScannerViewController` * Requires iOS 17.0+ * Full-screen camera interface * Automatic barcode detection * Built-in guidance overlay * Pinch-to-zoom support * High frame rate tracking **Permissions**: Add camera usage description to your `Info.plist`: NSCameraUsageDescriptionWe need camera access to scan QR codes and barcodes ### Android [Section titled “Android”](https://skip.dev/docs/modules/skip-qrcode/#android) **Implementation**: Uses Google ML Kit with CameraX * Requires Android API 24+ * Native activity-based scanner * ML Kit barcode detection * Torch/flashlight control * Portrait orientation **Permissions**: Automatically merged from the package’s `AndroidManifest.xml`: * `android.permission.CAMERA` * Camera hardware features (optional) **Activities**: Scanner activities are automatically registered: * `skip.qrcode.MLKitScanActivity` - Main scanner with camera * `skip.qrcode.ScanHostActivity` - Activity launcher/bridge API Reference ------------- [Section titled “API Reference”](https://skip.dev/docs/modules/skip-qrcode/#api-reference) ### `AndroidBarcodeScanner` [Section titled “AndroidBarcodeScanner”](https://skip.dev/docs/modules/skip-qrcode/#androidbarcodescanner) A utility struct for launching the Android scanner. public struct AndroidBarcodeScanner { public static func scan(completion: @escaping @Sendable (String?) -> Void)} **Parameters:** * `completion`: Async callback invoked when scanning completes. Receives: * The scanned barcode string if successful * `nil` if the scan was cancelled or failed **Usage:** AndroidBarcodeScanner.scan { result in if let barcode = result { print("Scanned: \(barcode)") } else { print("Scan cancelled or failed") }} Supported Barcode Types ----------------------- [Section titled “Supported Barcode Types”](https://skip.dev/docs/modules/skip-qrcode/#supported-barcode-types) SkipQRCode supports all common 1D and 2D barcode formats: * **2D Codes**: QR Code, Data Matrix, Aztec, PDF417 * **1D Codes**: UPC-A, UPC-E, EAN-8, EAN-13 * **Other**: Code 39, Code 93, Code 128, ITF, Codabar * **Specialized**: Driver’s License, Calendar Events, Contact Info Architecture ------------ [Section titled “Architecture”](https://skip.dev/docs/modules/skip-qrcode/#architecture) SkipQRCode is a **transpiled Skip package with bridging support**, which means: * ✅ **iOS**: Direct Swift → VisionKit (native iOS framework) * ✅ **Android**: Swift → Transpiled Kotlin → ML Kit + CameraX * ✅ **Bridging**: Automatically bridges to native SkipFuse apps * ✅ **Platform Optimized**: Uses best-in-class libraries for each platform * ✅ **Zero Setup**: Import once, works on both platforms ### Component Overview [Section titled “Component Overview”](https://skip.dev/docs/modules/skip-qrcode/#component-overview) iOS Side: SwiftUI → VisionKit DataScannerViewController Android Side (Transpiled): Swift → AndroidBarcodeScanner.swift ↓ (transpiles to) Kotlin → ScanHostActivity → MLKitScanActivity (activity trampoline) (CameraX + ML Kit scanner) ### How It Works [Section titled “How It Works”](https://skip.dev/docs/modules/skip-qrcode/#how-it-works) 1. **On iOS**: Uses Apple’s VisionKit for native barcode scanning 2. **On Android**: * Swift code transpiles to Kotlin * Launches native Android Activities with ML Kit * Camera preview rendered programmatically (no XML resources) * Results passed back via polling mechanism Requirements ------------ [Section titled “Requirements”](https://skip.dev/docs/modules/skip-qrcode/#requirements) * **iOS**: 17.0 or later * **Android**: API 24 (Android 7.0) or later * **Skip**: 1.6.27 or later * **Xcode**: 15.0 or later Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-qrcode/#building) This project uses the Skip plugin with native compilation mode. Install Skip using [Homebrew ↗](https://brew.sh/) : brew install skiptools/skip/skip Build the package: swift build Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-qrcode/#testing) Run tests using: swift test For parity testing across platforms: skip test Troubleshooting --------------- [Section titled “Troubleshooting”](https://skip.dev/docs/modules/skip-qrcode/#troubleshooting) ### iOS [Section titled “iOS”](https://skip.dev/docs/modules/skip-qrcode/#ios-1) **Scanner doesn’t appear:** * Ensure you’re running on iOS 17.0 or later * Check that `NSCameraUsageDescription` is in your Info.plist * Verify camera permissions are granted **Scanner shows but doesn’t scan:** * Check that `DataScannerViewController.isSupported` returns true * Ensure good lighting conditions * Try different barcode types ### Android [Section titled “Android”](https://skip.dev/docs/modules/skip-qrcode/#android-1) **Scanner crashes on launch:** * Verify all dependencies are properly resolved * Check that camera permissions are declared in manifest * Ensure device has a working camera **Scanned results not returning:** * Check LogCat for error messages (tag: `MLKitScanActivity`, `ScanHostActivity`) * Ensure ML Kit dependencies are included * Verify network connectivity (ML Kit may download models) Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-qrcode/#contributing) Contributions are welcome! Please feel free to submit a Pull Request. ### Development Setup [Section titled “Development Setup”](https://skip.dev/docs/modules/skip-qrcode/#development-setup) 1. Clone the repository 2. Install Skip: `brew install skiptools/skip/skip` 3. Open in Xcode or your preferred IDE 4. Make your changes 5. Run tests: `swift test` or `skip test` 6. Submit a PR Credits ------- [Section titled “Credits”](https://skip.dev/docs/modules/skip-qrcode/#credits) Built with Skip - Swift for iOS and Android **Technologies Used:** * iOS: VisionKit, SwiftUI * Android: ML Kit, CameraX, Kotlin, Jetpack Compose --- # Showcase (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-showcase/#_top) Showcase (Lite) =============== Showcase is a Skip Lite app that demonstrates many of the components available in SkipUI. [![Download on the Google Play Store](https://appfair.org/assets/badges/google-play-store.svg)](https://play.google.com/store/apps/details?id=org.appfair.app.Showcase) [![Download on the Apple App Store](https://appfair.org/assets/badges/apple-app-store.svg)](https://apps.apple.com/us/app/skip-showcase/id6474885022) iPhone Screenshots ------------------ [Section titled “iPhone Screenshots”](https://skip.dev/docs/samples/skipapp-showcase/#iphone-screenshots) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Darwin/fastlane/screenshots/en-US/1_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Darwin/fastlane/screenshots/en-US/2_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Darwin/fastlane/screenshots/en-US/3_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Darwin/fastlane/screenshots/en-US/4_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Darwin/fastlane/screenshots/en-US/5_en-US.png) Android Screenshots ------------------- [Section titled “Android Screenshots”](https://skip.dev/docs/samples/skipapp-showcase/#android-screenshots) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-showcase/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png) Quickstart ---------- [Section titled “Quickstart”](https://skip.dev/docs/samples/skipapp-showcase/#quickstart) This repository contains an Xcode project with a SwiftUI app that uses the Skip plugin to transpile the app into Kotlin then build and launch it on Android. To get started: 1. Install skip (requires macOS 13+ with [Homebrew ↗](https://brew.sh/) , [Xcode ↗](https://developer.apple.com/xcode/) , and [Android Studio ↗](https://developer.android.com/studio) ) with the Terminal command: $ brew install skiptools/skip/skip 2. Configure and launch an Android emulator from the [Android Studio device manager ↗](https://developer.android.com/studio/run/emulator-launch-without-app) . 3. Download this [repository as a zip file ↗](https://source.skip.dev/skipapp-showcase/archive/main.zip) and unzip it, or clone the repository: $ git clone https://source.skip.dev/skipapp-showcase.git 4. Open the Xcode workspace: `Project.xcworkspace` 5. Select and Run the `Showcase App` target with an iOS simulator destination; the app will build and run side-by-side on the iOS simulator and Android emulator. Development ----------- [Section titled “Development”](https://skip.dev/docs/samples/skipapp-showcase/#development) This app is ideal for experimenting with various SwiftUI constructs and exploring the various integrations that Skip offers. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-showcase/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-showcase/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-showcase/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the FireSideApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # Transpilation Reference | Skip [Skip to content](https://skip.dev/docs/swiftsupport/#_top) Transpilation Reference ======================= Skip’s Swift to Kotlin language transpiler is able to convert a large subset of the Swift language into Kotlin. The transpiler has the following goals: 1. Avoid generating buggy code. We would rather give you an immediate error or generate Kotlin that fails to compile altogether than to generate Kotlin that compiles but behaves differently than your Swift source. 2. Allow you to write natural Swift. Swift is a sprawling language; we attempt to supports its most common and useful features so that you can code with confidence. 3. Generate idiomatic Kotlin. Where possible, we strive to generate clean and idiomatic Kotlin from your Swift source. These goals form a hierarchy. For example, if generating more idiomatic Kotlin would run the risk of introducing subtle behavioral differences from the source Swift, Skip will always opt for a less idiomatic but bug-free transpilation. Language Features ----------------- [Section titled “Language Features”](https://skip.dev/docs/swiftsupport/#swift-language-features) The following table details Skip’s support for transpiling various Swift language features. A ✓ indicates that a feature is fully or very strongly supported. A ~ indicates that a feature is partially supported. And a ✕ indicates that a feature is not supported, or is only weakly supported. Future releases may address some unsupported language features, but others reflect deep incompatibilities between the Swift and Kotlin languages. * ✓ Classes * ✓ Inheritance * ✓ `Codable` synthesis * ✓ Structs * ✓ Value semantics. See the [Structs](https://skip.dev/docs/swiftsupport/#structs) topic below * ✓ Constructor synthesis * ✓ `Equatable` synthesis * ✓ `Hashable` synthesis * ✓ `Codable` synthesis * ✓ Protocols * ✓ Inheritance * ✓ Property requirements * ✓ Function requirements * ✓ `where Self == Type` * ~ Constructor requirements. Limited support when combined with [Generics](https://skip.dev/docs/swiftsupport/#generics) * ~ Static requirements. Limited support when combined with [Generics](https://skip.dev/docs/swiftsupport/#generics) * ✓ Enums * ✓ Enums with associated values * ✓ `RawRepresentable` synthesis * ✓ `CaseIterable` synthesis * ✓ `Equatable` synthesis * ✓ `Hashable` synthesis * ~ `Codable` synthesis * Skip can only synthesize `Codable` conformance for `RawRepresentable` enums * ✓ Nested types * ✓ Types defined within types * ✕ Types defined within functions * ✓ Extensions * ✓ Concrete type extensions * ✓ Protocol extensions * ~ Limits on [generic specialization](https://skip.dev/docs/swiftsupport/#generics) * ~ Limits on extending types defined in other modules * ✓ Generic types * ~ See the [Generics](https://skip.dev/docs/swiftsupport/#generics) topic below for limitations * ✓ Tuples * ✓ Labeled or unlabeled * ✓ Destructuring * ✓ Arity 2 through 5 * ✕ Arity 6+ * ✓ Typealiases * ✓ Nested typealiases * Skip fully resolves typealiases during transpilation to work around Kotlin typealias limitations * ✓ Properties * ✓ `let` * ✓ `var` * ✓ Static properties * ✓ Stored properties * ✓ Computed properties * ✓ Throwing properties * ✓ Lazy properties * ✓ Custom get/set * ✓ `willSet` * ✓ `didSet` * ✓ SwiftUI property wrappers: `@State`, `@Environment`, etc * ✕ Custom property wrappers * ✓ Functions * ✓ Overloading on types * ✓ Overloading on param labels * ✕ Overloading on return type * ✓ Static functions * ✓ Generic functions * ✓ Throwing functions * ✓ `self` assignment in mutable functions * ✓ Default parameter values * ✓ `inout` parameters * ✓ Closures and trailing closures * ✓ Variadic parameters * ✕ `@autoclosure` parameters * ✕ Parameter packs * ✓ Nested functions * ✓ Constructors * ✓ Optional constructors * ✓ `self` assignment in constructors * ~ Kotlin imposes some limitations on calling `super.init` or `self.init` in a delegating constructor * ✕ Constructors cannot use generic parameter types that are not declared by the owning type * ✓ Deconstructors * ~ `deinit` is transpiled into Kotlin’s `finalize`. See the [Garbage Collection](https://skip.dev/docs/swiftsupport/#garbage-collection) topic * ✓ Closures * ✓ Explicit and implicit (`$0`, `$1`, etc) parameters * ~ Weak and unowned capture is ignored. We rely on Kotlin [garbage collection](https://skip.dev/docs/swiftsupport/#garbage-collection) * ✓ Error handling * ✓ `throw` * ✓ `do / catch` * ✓ `try, try?, try!` * ✓ Throw custom enums, structs, classes * ✓ Catch pattern matching * ✕ Error types cannot be subclasses * ✓ Concurrency * ✓ `Task` / `Task.detached` * ✓ Task groups * ✓ `async / await` * ~ `async let` * The implicit task group is not cancelled when exiting scope * ✓ Async functions * ✓ Async properties * ✓ Async closures * ✓ `AsyncSequence` * ✓ `AsyncStream` * ✓ `@MainActor` * ~ Custom actors * Non-private mutable properties not supported. Expose functions to access private state * ✕ Grand Central Dispatch * ✓ Defer * ✓ If * ✓ `if let` * See the [If let](https://skip.dev/docs/swiftsupport/#if-let) topic for additional information * ✓ `if case` * ✓ Guard * ✓ `guard let` * See the [If let](https://skip.dev/docs/swiftsupport/#if-let) topic for additional information * ✓ `guard case` * ✓ Switch * ✓ Case pattern matching * ✓ Case binding * ~ Limits on partial matching and binding * ✕ `case … where` * ✓ While loop * ✓ Do while loop * ✓ For in loop * ✓ `for … in … where …` * ✓ `for let …` * ✓ `for case …` * ✓ Operators * ✓ Standard operators * ✓ Logical operators * ✓ Optional chaining * ✓ Optional unwrapping * ✓ Range operators * ~ Slice operators * Slices are not mutable * ~ Some advanced operators not supported * ✓ Custom `Equatable` with `==` * ✓ Custom `Hashable` with `hash(into:)` * ✓ Custom `Comparable` with `<` * ~ Custom subscript operators * Cannot overload subscript operators on parameter labels or types * ✓ `callAsFunction` support * ✕ Other custom operators * ~ Key paths * ✓ As implicit closure parameters * ✓ As `@Environment` keys * ✕ Other uses * ✕ Macros * ✓ `@Observable` * ✓ `@ObservationIgnored` * ✕ Other macros * * * Builtin Types ------------- [Section titled “Builtin Types”](https://skip.dev/docs/swiftsupport/#swift-types) The following table details Skip’s support for using builtin Swift standard library types in transpiled code. Support for these types is divided between the Skip language transpiler and the SkipLib open source library. * ✓ Numeric types * ✓ Use Kotlin native types * ~ `Int` is 32 bit on JVM * ~ All unsigned and `Float` values must be explicit - e.g. `Float(1.0)`; no implicit conversion from signed types or `Double` * ✓ `String` * ✓ Uses Kotlin native `String` * ✕ Mutation is not supported * ✓ `Any`, `AnyObject` * ✓ Optionals * ✕ Kotlin does not represent `Optional` as its own type, so `.some` and `.none` do not exist * ✕ Compound types (e.g. `A & B`) * ✓ `Array` * ✓ Value semantics * ✓ Slicing * ✓ `Dictionary` * ✓ Value semantics * ✓ `Set` * ✓ Value semantics * ✓ `OptionSet` * ~ You must implement `OptionSet` with a `struct` * ✓ `CaseIterable` * ✓ Automatic synthesis * ✓ Custom implementations * ✓ `Codable` * ✓ Automatic synthesis * ✓ Custom implementations * ✓ `CustomStringConvertible` * ✓ `Comparable` * ✓ Automatic synthesis * ✓ Custom implementations * ✓ `Equatable` * ✓ Automatic synthesis * ✓ Custom implementations * ✓ `Error` * ✓ `Hashable` * ✓ Automatic synthesis * ✓ Custom implementations * ✓ `RawRepresentable` * ✓ Automatic synthesis * ✓ Custom implementations * ✓ \`Result“ * ~ Result builders * ✓ `@ViewBuilder` * The `@ViewBuilder` attribute is not inherited when overriding API other than `View.body`. Specify it explicitly * ✕ Other result builders * * * Special Topics -------------- [Section titled “Special Topics”](https://skip.dev/docs/swiftsupport/#special-topics) The Skip transpiler performs a large number of interesting code transformations to bridge the differences between Swift and Kotlin. The following sections address particular areas that deserve some explanation, either because the transpilation affects the behavior of your code, or because the resulting Kotlin is unusual in some way. ### Numeric Types [Section titled “Numeric Types”](https://skip.dev/docs/swiftsupport/#numeric-types) Numeric types are a particularly common source of subtle runtime and compilation problems in dual-platform apps. Runtime issues may arise because Kotlin `Ints` are 32 bits. Technically Swift `Ints` can be either 32 or 64 bits depending on the hardware, but all of Apple’s recent devices are 64 bit, so Swift programmers tend to assume 64 bit integers. Take care to use `Int64` when your code demands more than 32 bit integer values. In Java, overflowing the 32 bit range does not cause an error condition like in Swift, but instead silents wraps `Int.max` around to `Int.min`, making such issues a potential cause of hidden bugs. You may also experience Android compilation problems because Kotlin can be picky about converting between numeric types. In general, you should be explicit when using any types other than `Int` and `Double`. For example, if `var f` is a `Float`, write `f = Float(1.0)` rather than `f = 1.0`. Also, although `Int` and `Double` do not need explicit casts, Kotlin does not allow you to assign an integer literal to a double variable or parameter. For example, if `var d` is a `Double`, Kotlin requires you to write `d = 1.0` rather than `d = 1`. Skip attempts to convert your integer literals to decimals when needed, but there may be times when you’ll have to write your `Double` values as `1.0` rather than `1`. ### Other Primitive Types [Section titled “Other Primitive Types”](https://skip.dev/docs/swiftsupport/#other-primitive-types) Skip does not wrap Kotlin’s primitive types. We have chosen the massive efficiency and interoperability wins that come with using Kotlin’s primitive types directly over the additional Swift language compatibility we might be able to achieve if we wrapped Kotlin’s primitives in our own classes. This means that we have to live with Kotlin’s primitive types as-is, and they have some limitations that will impact your code. The most significant is that these types are immutable. Functions like `Bool.toggle()` are not supported, and **Strings are immutable** in Skip code. Rather than `append`ing to a `String` in place, you must create a new string. Rather than calling `String.sort()`, you must call `let sorted = string.sorted()`, etc. Additionally, **Strings are not Collections**. While we have added the `Collection` API to `String`, there is no way to add a new protocol to an existing Kotlin type. So while you can make all the `Collection` API calls you’re used to, you cannot pass a `String` to code that expects a `Collection`. ### Garbage Collection [Section titled “Garbage Collection”](https://skip.dev/docs/swiftsupport/#garbage-collection) Swift uses automatic reference counting to determine when to free memory for an object. Kotlin uses garbage collection. This difference has important consequences that you should keep in mind: * On Android, your `deinit` functions will be called at an indeterminate time, and may not be called at all. While Swift calls `deinit` functions and deallocates memory as soon as an object’s reference count reaches zero, the timing of these tasks on Android is entirely at the discretion of the garbage collector. * The Android garbage collector can detect and cleanup reference cycles. In Swift, the most common uses of the `weak` and `unowned` modifiers are to avoid strong reference cycles. This is not a problem in Kotlin, and Kotlin therefore does not have these modifiers. Skip has chosen to ignore `weak` and `unowned` modifiers on properties and in closure capture lists, relying on the garbage collector instead. If you were planning to use a `weak` or `unowned` reference for reasons _other_ than avoiding a strong reference cycle, you should consider alternatives. ### Structs [Section titled “Structs”](https://skip.dev/docs/swiftsupport/#structs) All Kotlin objects are reference types. Apart from primitives like `Int`, there are no value types. In order to allow you to use Swift structs but ensure identical behavior in your Android programs, Skip employs its own `MutableStruct` protocol. Skip automatically adds the `MutableStruct` protocol to all mutable struct types. It uses the functions of this protocol to give Kotlin classes value semantics. You will notice this when you examine any Kotlin transpiled from Swift that uses mutable struct types: * The Kotlin classes for your mutable struct types will adopt the `MutableStruct` protocol and implement its required functions. * You will see calls to `.sref()` sprinkled throughout your code. This stands for _struct reference_. Skip adds `sref()` calls when it is necessary to copy Kotlin objects representing structs in order to maintain value semantics - e.g. when assigning a struct to a variable. * Properties that hold mutable struct types will gain custom getter and setter code to copy the value on the way in and out as needed. * Functions that return mutable struct types will `sref()` the value being returned. While modern virtual machines are very good at managing large numbers of objects, in extreme cases you might want to modify your code to avoid excessive copying. We recommend that you do not worry about it until you see a performance problem. For cases in which a struct is technically mutable but is never modified after you set its properties once - i.e. a configuration object - add the `@nocopy` attribute. This instructs Skip to treat the struct as immutable and avoid copying. // SKIP @nocopystruct S { …} ### Generics [Section titled “Generics”](https://skip.dev/docs/swiftsupport/#generics) There’s no getting around it: Swift generics are complicated. And converting from Swift generics to Kotlin generics is even more so, because the two languages have very different generic implementation strategies. Swift generics are built deep into the language as first-class citizens of its type system. Kotlin generics, on the other hand, don’t exist at the JVM level and are only present at compile time. This difference has far-reaching effects. For example, because generics are built into Swift’s type system, `Dictionary.Entry` is a Swift type. But in Kotlin, the equivalent type is `Dictionary.Entry`. When it is used as a scope for other types or even static members, `Dictionary`’s generics disappear. Fortunately, Skip is able to bridge enough of the divergence between the languages that you may not run into issues in normal, day-to-day use. Skip fully supports: * ✓ Using built-in generic data structures like `Array`, `Dictionary`, and `Set` * ✓ Defining your own generic classes, structs, enums * ✓ Defining and conforming to protocols with `associatedtypes` * ✓ Generic functions * ✓ Generic constraints such as `where T: Equatable` But there are limits to the incompatibilities that Skip can overcome. The following features are not well supported: * ~ Static members of generic types are limited. Skip can only support static members that either don’t use the defining type’s generics or that can be converted into a generic function that is defined independently of the defining type’s generics * ~ Generic specialization by type extensions (e.g. `extension C where T: Equatable`) is limited * ✕ Inner types on generic outer types are not supported - see the `Dictionary` example above * ✕ Kotlin does not allow constructor functions to use generics other than those of the defining type * ✕ Kotlin does not allow `typealiases` to include generic constraints (e.g. `where T: Equatable`) * ✕ `is` testing and `as?` casts do not consider the generic portions of type signatures, because the generic types don’t exist at runtime The Skip transpiler generally detects unsupported patterns and provides an appropriate error message. You may, however, run into additional limitations as well. Our general advice is to take advantage of generics for straightforward use cases, but to avoid complex generics definitions and constraints. #### Reified Types [Section titled “Reified Types”](https://skip.dev/docs/swiftsupport/#reified-types) One way to preserve generic information in Kotlin is to use inline functions with _reified types_. You can read more about this topic in the [Kotlin language documentation ↗](https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters) . Skip automatically converts any Swift function with the `@inline(__always)` attribute into a Kotlin inline function with reified generics. @inline(__always) public func f(param: T) { ...} Transpiles to: inline fun f(param: T) { ...} ### Concurrency [Section titled “Concurrency”](https://skip.dev/docs/swiftsupport/#concurrency) Skip does not support Grand Central Dispatch. Rather, it supports Swift’s modern concurrency with `async` and `await`, `Task` and `TaskGroup`, and actors. Note that neither `@MainActor` nor custom actors are features of Kotlin. Skip supports actors by adding its own calls to jump to and from the actor’s isolated context. You will see these inserted calls in the generated Kotlin, and they may look surprising. Currently `@MainActor` is not automatically inherited from superclass and protocol members. Add the attribute to all overrides explicitly. Skip does, however, make an exception for `View.body` - your `View` bodies will automatically be `@MainActor`\-bound. ### Enums and case matching [Section titled “Enums and case matching”](https://skip.dev/docs/swiftsupport/#enums-and-case-matching) Skip transpiles enums to Kotlin enums, along with creating similar case statements. There are some comlpex case matching constructions that Kotlin doesn’t support, meaning you would need to find and alternative way of expressing the logic in Swift. These limitation assume: 1. Cannot translate compound case matches with associated values, like `case .caseA(let value), .caseB(let value):` 2. Cannot translate case matches that also conditionally check values, like `case .caseA(let value) where value == "X"` ### If Let [Section titled “If Let”](https://skip.dev/docs/swiftsupport/#if-let) Swift’s `if let x = f()` (or `guard let x = f()`) syntax does a few things at the same time: 1. Executes `f()` exactly once. 2. Tests that the value is not `nil`. 3. Binds the value to a new variable with the appropriate scope. While Kotlin’s `if (x != null)` checks do have some intelligence - Kotlin will _usually_ let you treat `x` as non-null in the body of of the `if` block - there is no Kotlin language construct that can do all of the things `if let` does. Depending on the details of how your Swift code uses `if let`, therefore, Skip may have to generate a significant amount of Kotlin to ensure identical behavior across platforms. This includes generating nested `if` statements and potentially duplicating entire `else` code blocks. While the resulting Kotlin may look complicated, it is no less efficient than the original Swift. Transpilation Examples ---------------------- [Section titled “Transpilation Examples”](https://skip.dev/docs/swiftsupport/#examples) The following examples show the approximate Kotlin code that Skip’s transpilation will generate for various common Swift code. These may vary depending on project context and Skip version (these were generated with Skip 1.7.0). ### Basic Class [Section titled “Basic Class”](https://skip.dev/docs/swiftsupport/#basic-class) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-0) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-1) class MyClass { static let staticValue = 1 var stringField = "abc" var intField = 123 var doubleField = 456.78} internal open class MyClass { internal open var stringField = "abc" internal open var intField = 123 internal open var doubleField = 456.78 companion object { internal val staticValue = 1 }} ### Class Inheritance [Section titled “Class Inheritance”](https://skip.dev/docs/swiftsupport/#class-inheritance) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-2) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-3) public class Base { var type: String init(type: String = "Base") { self.type = type }} internal class Sub : Base { var level: Int init(level: Int) { super.init(type: "Sub") self.level = level }} open class Base { internal open var type: String internal constructor(type: String = "Base") { this.type = type } companion object: CompanionClass() { } open class CompanionClass { }} internal open class Sub: Base { internal open var level: Int internal constructor(level: Int): super(type = "Sub") { this.level = level } companion object: Base.CompanionClass() { }} ### Immutable Struct [Section titled “Immutable Struct”](https://skip.dev/docs/swiftsupport/#immutable-struct) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-4) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-5) struct MyStruct { let str: String = "abc" let num: Int} internal class MyStruct { internal val str: String = "abc" internal val num: Int constructor(num: Int) { this.num = num }} ### Mutable Struct [Section titled “Mutable Struct”](https://skip.dev/docs/swiftsupport/#mutable-struct) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-6) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-7) // Skip adds methods to mutable structs that allow Skip to replicate Swift struct value semanticsstruct MyStruct { var str: String = "abc" var num: Int} // Skip adds methods to mutable structs that allow Skip to replicate Swift struct value semanticsinternal class MyStruct: MutableStruct { internal var str: String set(newValue) { willmutate() field = newValue didmutate() } internal var num: Int set(newValue) { willmutate() field = newValue didmutate() } constructor(str: String = "abc", num: Int) { this.str = str this.num = num } override var supdate: ((Any) -> Unit)? = null override var smutatingcount = 0 override fun scopy(): MutableStruct = MyStruct(str, num)} ### Hashable & Codable Struct [Section titled “Hashable & Codable Struct”](https://skip.dev/docs/swiftsupport/#hashable--codable-struct) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-8) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-9) struct MyStruct : Hashable, Codable { var str: String = "abc" var num: Int} internal class MyStruct: Codable, MutableStruct { internal var str: String set(newValue) { willmutate() field = newValue didmutate() } internal var num: Int set(newValue) { willmutate() field = newValue didmutate() } constructor(str: String = "abc", num: Int) { this.str = str this.num = num } override var supdate: ((Any) -> Unit)? = null override var smutatingcount = 0 override fun scopy(): MutableStruct = MyStruct(str, num) override fun equals(other: Any?): Boolean { if (other !is MyStruct) return false return str == other.str && num == other.num } override fun hashCode(): Int { var result = 1 result = Hasher.combine(result, str) result = Hasher.combine(result, num) return result } private enum class CodingKeys(override val rawValue: String, @Suppress("UNUSED_PARAMETER") unusedp: Nothing? = null): CodingKey, RawRepresentable { str("str"), num("num"); companion object { fun init(rawValue: String): CodingKeys? { return when (rawValue) { "str" -> CodingKeys.str "num" -> CodingKeys.num else -> null } } } } override fun encode(to: Encoder) { val container = to.container(keyedBy = CodingKeys::class) container.encode(str, forKey = CodingKeys.str) container.encode(num, forKey = CodingKeys.num) } constructor(from: Decoder) { val container = from.container(keyedBy = CodingKeys::class) this.str = container.decode(String::class, forKey = CodingKeys.str) this.num = container.decode(Int::class, forKey = CodingKeys.num) } companion object: DecodableCompanion { override fun init(from: Decoder): MyStruct = MyStruct(from = from) private fun CodingKeys(rawValue: String): CodingKeys? = CodingKeys.init(rawValue = rawValue) }} ### Basic Protocol [Section titled “Basic Protocol”](https://skip.dev/docs/swiftsupport/#basic-protocol) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-10) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-11) protocol MyContract { var name: String { get } func perform() throws -> Int} internal interface MyContract { val name: String fun perform(): Int} ### Protocol with Extension [Section titled “Protocol with Extension”](https://skip.dev/docs/swiftsupport/#protocol-with-extension) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-12) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-13) protocol MyContract { var name: String { get } func perform() throws -> Int} extension MyContract { var name: String { "default" }} internal interface MyContract { val name: String get() = "default" fun perform(): Int} ### Protocol with associatedtype [Section titled “Protocol with associatedtype”](https://skip.dev/docs/swiftsupport/#protocol-with-associatedtype) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-14) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-15) protocol MyContract { associatedtype R var name: String { get } func perform() throws -> R} class MyClass: MyContract { var name: String var value: Int init(name: String, value: Int) { self.name = name self.value = value } func perform() -> Int { return value }} internal interface MyContract { val name: String fun perform(): R} internal open class MyClass: MyContract { override var name: String internal open var value: Int internal constructor(name: String, value: Int) { this.name = name this.value = value } override fun perform(): Int = value} ### Basic Enum [Section titled “Basic Enum”](https://skip.dev/docs/swiftsupport/#basic-enum) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-16) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-17) enum Size { case small, medium, large} enum Position : CaseIterable { case one, two, three, four, five} import skip.lib.Array internal enum class Size { small, medium, large;} internal enum class Position: CaseIterable { one, two, three, four, five; companion object: CaseIterableCompanion { override val allCases: Array get() = arrayOf(one, two, three, four, five) }} ### RawRepresentable Enum [Section titled “RawRepresentable Enum”](https://skip.dev/docs/swiftsupport/#rawrepresentable-enum) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-18) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-19) enum Size : String { case small, medium, large = "LG"} internal enum class Size(override val rawValue: String, @Suppress("UNUSED_PARAMETER") unusedp: Nothing? = null): RawRepresentable { small("small"), medium("medium"), large("LG"); companion object { fun init(rawValue: String): Size? { return when (rawValue) { "small" -> Size.small "medium" -> Size.medium "LG" -> Size.large else -> null } } }} internal fun Size(rawValue: String): Size? = Size.init(rawValue = rawValue) ### Enum with Associated Values [Section titled “Enum with Associated Values”](https://skip.dev/docs/swiftsupport/#enum-with-associated-values) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-20) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-21) // Sealed classes are Kotlin's closest analog to enums with associated valuesenum E : Hashable { case option1 case option2(Int, String)} func process(e: E) { if case .option2(let i, _) = e { print("i = \(i)") }} // Sealed classes are Kotlin's closest analog to enums with associated valuesinternal sealed class E { class Option1Case: E() { } class Option2Case(val associated0: Int, val associated1: String): E() { override fun equals(other: Any?): Boolean { if (other !is Option2Case) return false return associated0 == other.associated0 && associated1 == other.associated1 } override fun hashCode(): Int { var result = 1 result = Hasher.combine(result, associated0) result = Hasher.combine(result, associated1) return result } } companion object { val option1: E = Option1Case() fun option2(associated0: Int, associated1: String): E = Option2Case(associated0, associated1) }} internal fun process(e: E) { if (e is E.Option2Case) { val i = e.associated0 print("i = ${i}") }} ### Dictionary [Section titled “Dictionary”](https://skip.dev/docs/swiftsupport/#dictionary) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-22) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-23) let d = [1: "a", 2: "b", 3: "c"]for (key, value) in d { print(key) print(value)} internal val d = dictionaryOf(Tuple2(1, "a"), Tuple2(2, "b"), Tuple2(3, "c"))for ((key, value) in d.sref()) { print(key) print(value)} ### Optional Constructor [Section titled “Optional Constructor”](https://skip.dev/docs/swiftsupport/#optional-constructor) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-24) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-25) // Kotlin does not have optional constructors. Skip replicates their behaviorclass C { let i: Int init?(param: Int) { if param == 0 { return nil } else { i = param } }}func f() -> Int { return C(param: 0)?.i ?? -1}func g() -> C { return C(param: 0)!} // Kotlin does not have optional constructors. Skip replicates their behaviorinternal open class C { internal val i: Int internal constructor(param: Int) { if (param == 0) { throw NullReturnException() } else { i = param } }}internal fun f(): Int { return (try { C(param = 0) } catch (_: NullReturnException) { null })?.i ?: -1}internal fun g(): C = C(param = 0) ### Async Function [Section titled “Async Function”](https://skip.dev/docs/swiftsupport/#async-function) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-26) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-27) func compute(_ input1: Int, _ input2: Int) async throws -> Int { // Do some expensive things return input1 * input2} func printResult() async throws { let result = try await compute(3, 5) print(result)} internal suspend fun compute(input1: Int, input2: Int): Int = Async.run l@{ // Do some expensive things return@l input1 * input2} internal suspend fun printResult(): Unit = Async.run { val result = compute(3, 5) print(result)} ### Type Inference [Section titled “Type Inference”](https://skip.dev/docs/swiftsupport/#type-inference) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-28) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-29) enum Size { case small, medium, large} func mySizes() -> [Size] { // Kotlin does not have implicitly-qualified members [.medium, .large]} import skip.lib.Array internal enum class Size { small, medium, large;} internal fun mySizes(): Array { // Kotlin does not have implicitly-qualified members return arrayOf(Size.medium, Size.large)} ### SwiftUI [Section titled “SwiftUI”](https://skip.dev/docs/swiftsupport/#swiftui) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-30) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-31) import SwiftUI struct ContentView : View { @State var value = 0.0 var body: some View { VStack { Text("Current value: \(value)") Slider(value: $value) Button("Reset") { value = 0.0 } } }} import androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.saveable.Saverimport androidx.compose.runtime.saveable.rememberSaveableimport androidx.compose.runtime.setValue import skip.ui.*import skip.foundation.*import skip.model.* internal class ContentView: View { internal var value: Double get() = _value.wrappedValue set(newValue) { _value.wrappedValue = newValue } internal var _value: skip.ui.State override fun body(): View { return ComposeBuilder { composectx: ComposeContext -> VStack { -> ComposeBuilder { composectx: ComposeContext -> Text({ val str = LocalizedStringKey.StringInterpolation(literalCapacity = 0, interpolationCount = 0) str.appendLiteral("Current value: ") str.appendInterpolation(value) LocalizedStringKey(stringInterpolation = str) }()).Compose(composectx) Slider(value = Binding({ _value.wrappedValue }, { it -> _value.wrappedValue = it })).Compose(composectx) Button(LocalizedStringKey(stringLiteral = "Reset")) { -> value = 0.0 }.Compose(composectx) ComposeResult.ok } }.Compose(composectx) } } @Composable @Suppress("UNCHECKED_CAST") override fun Evaluate(context: ComposeContext, options: Int): kotlin.collections.List { val rememberedvalue by rememberSaveable(stateSaver = context.stateSaver as Saver, Any>) { mutableStateOf(_value) } _value = rememberedvalue return super.Evaluate(context, options) } constructor(value: Double = 0.0) { this._value = skip.ui.State(value) }} ### Conditional Compilation [Section titled “Conditional Compilation”](https://skip.dev/docs/swiftsupport/#conditional-compilation) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-32) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-33) // Use #if os(Android) to conditionally include code for Android and non-Android platformsfunc languageName() -> String { #if os(Android) "Kotlin" #else "Swift" #endif} func languageName2() -> String { #if !os(Android) "Swift" #else "Kotlin" #endif} // Use #if os(Android) to conditionally include code for Android and non-Android platformsinternal fun languageName(): String = "Kotlin" internal fun languageName2(): String = "Kotlin" ### SKIP Comments [Section titled “SKIP Comments”](https://skip.dev/docs/swiftsupport/#skip-comments) * [Swift](https://skip.dev/docs/swiftsupport/#tab-panel-34) * [Kotlin](https://skip.dev/docs/swiftsupport/#tab-panel-35) // Skip has a set of special comments for specifying Kotlin codefunc languageName() -> String { // SKIP REPLACE: return "Kotlin" "Swift"} // Skip has a set of special comments for specifying Kotlin codeinternal fun languageName(): String = "Kotlin" --- # SkipFoundation | Skip [Skip to content](https://skip.dev/docs/modules/skip-foundation/#_top) SkipFoundation ============== Foundation support for Skip Lite transpiled Swift. See what API is currently implemented [here](https://skip.dev/docs/modules/skip-foundation/#foundation-support) . About ----- [Section titled “About”](https://skip.dev/docs/modules/skip-foundation/#about) SkipFoundation vends the `skip.foundation` Kotlin package. It is a reimplementation of Foundation for Kotlin on Android. Its goal is to mirror as much of Foundation as possible, allowing Skip developers to use Foundation API with confidence. SkipFoundation also implements portions of the CryptoKit API. Dependencies ------------ [Section titled “Dependencies”](https://skip.dev/docs/modules/skip-foundation/#dependencies) SkipFoundation depends on the [skip ↗](https://source.skip.dev/skip) transpiler plugin as well as the [SkipLib](https://skip.dev/docs/modules/skip-lib) package. SkipFoundation is part of the _Skip Core Frameworks_ and is not intended to be imported directly. The module is transparently adopted through the translation of `import Foundation` into `import skip.foundation.*` by the Skip transpiler. ### Android Libraries [Section titled “Android Libraries”](https://skip.dev/docs/modules/skip-foundation/#android-libraries) * SkipFoundation includes source code from the [UrlEncoderUtil ↗](https://github.com/ethauvin/urlencoder) library to implement percent escaping. Status ------ [Section titled “Status”](https://skip.dev/docs/modules/skip-foundation/#status) SkipFoundation supports many of the Foundation framework’s most common APIs, but there are many more that are not yet ported. See [Foundation Support](https://skip.dev/docs/modules/skip-foundation/#foundation-support) . When you want to use a Foundation API that has not been implemented, you have options. You can try to find a workaround using only supported API, embed Kotlin code directly as described in the [Skip docs](https://skip.dev/docs) , or [add support to SkipFoundation](https://skip.dev/docs/modules/skip-foundation/#contributing) . If you choose to enhance SkipFoundation itself, please consider [contributing](https://skip.dev/docs/modules/skip-foundation/#contributing) your code back for inclusion in the official release. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-foundation/#contributing) We welcome contributions to SkipFoundation. The Skip product [documentation](https://skip.dev/docs/contributing/) includes helpful instructions and tips on local Skip library development. The most pressing need is to implement more of the most-used Foundation APIs. To help fill in unimplemented API in SkipFoundation: 1. Find unimplemented API. 2. Write an appropriate Kotlin implementation. See [Implementation Strategy](https://skip.dev/docs/modules/skip-foundation/#implementation-strategy) below. 3. Edit the corresponding tests to make sure they are no longer skipped, and that they pass. If there aren’t existing tests, write some. See [Tests](https://skip.dev/docs/modules/skip-foundation/#tests) . 4. [Submit a PR. ↗](https://source.skip.dev/skip-foundation/pulls) Other forms of contributions such as test cases, comments, and documentation are also welcome! Implementation Strategy ----------------------- [Section titled “Implementation Strategy”](https://skip.dev/docs/modules/skip-foundation/#implementation-strategy) The goal of SkipFoundation is to mirror the Foundation framework for Android. When possible, `SkipFoundation` types wrap corresponding Kotlin or Java foundation types. When a `SkipFoundation` type wraps a corresponding Kotlin or Java type, please conform to the `skip.lib.KotlinConverting` protocol, which means adding a `.kotlin()` function: #if SKIPextension Calendar: KotlinConverting { public override func kotlin(nocopy: Bool = false) -> java.util.Calendar { return nocopy ? platformValue : platformValue.clone() as java.util.Calendar }}#endif You should also implement a constructor that accepts the equivalent Kotlin or Java object. Tests ----- [Section titled “Tests”](https://skip.dev/docs/modules/skip-foundation/#tests) SkipFoundation’s `Tests/` folder contains the entire set of official Foundation framework test cases. Through the magic of [SkipUnit](https://skip.dev/docs/modules/skip-unit) , this allows us to validate our SkipFoundation API implementations on Android against the same test suite used by the Foundation team on iOS. It is SkipFoundation’s goal to include - and pass - as much of the official test suite as possible. Foundation Support ------------------ [Section titled “Foundation Support”](https://skip.dev/docs/modules/skip-foundation/#foundation-support) The following table summarizes SkipFoundation’s Foundation API support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within `#if !SKIP` blocks - you can use any Foundation API you want. Support levels: * ✅ – Full * 🟢 – High * 🟡 – Medium * 🟠 – Low | Support | API | | --- | --- | | 🟠 | `AttributedString`

* `init()`
* `init(stringLiteral: String)`
* `init(markdown: String)`
* `init(localized keyAndValue: String.LocalizationValue, table: String? = nil, bundle: Bundle? = nil, locale: Locale? = nil, comment: String? = nil)`
* `init(localized key: String, table: String? = nil, bundle: Bundle? = nil, locale: Locale = Locale.current, comment: String? = nil)` | | 🟡 | `Bundle`

* `static var main: Bundle`
* `static var module: Bundle`
* `init?(path: String)`
* `init?(url: URL)`
* `init(for forClass: AnyClass)`
* `init()`
* `var bundleURL: URL`
* `var resourceURL: URL?`
* `var bundlePath: String`
* `var resourcePath: String?`
* `func url(forResource: String? = nil, withExtension: String? = nil, subdirectory: String? = nil, localization: String? = nil) -> URL?`
* `func path(forResource: String? = nil, ofType: String? = nil, inDirectory: String? = nil, forLocalization: String? = nil) -> String?`
* `var developmentLocalization: String`
* `var localizations: [String]`
* `func localizedString(forKey key: String, value: String?, table tableName: String?) -> String`
* `var localizations: [String]` | | 🟡 | `Calendar`

* Only `.gregorian` and `.iso8601` identifiers are supported
* `init(identifier: Calendar.Identifier)`
* `static var current: Calendar`
* `var locale: Locale`
* `var timeZone: TimeZone`
* `var identifier: Calendar.Identifier`
* `var eraSymbols: [String]`
* `var monthSymbols: [String]`
* `var shortMonthSymbols: [String]`
* `var weekdaySymbols: [String]`
* `var shortWeekdaySymbols: [String]`
* `var amSymbol: String`
* `var pmSymbol: String`
* `func date(from components: DateComponents) -> Date?`
* `func dateComponents(in zone: TimeZone? = nil, from date: Date) -> DateComponents`
* `func dateComponents(_ components: Set, from start: Date, to end: Date) -> DateComponents`
* `func dateComponents(_ components: Set, from date: Date) -> DateComponents`
* `func component(_ component: Calendar.Component, from date: Date) -> Int`
* `func date(byAdding components: DateComponents, to date: Date, wrappingComponents: Bool = false) -> Date?`
* `func date(byAdding component: Calendar.Component, value: Int, to date: Date, wrappingComponents: Bool = false) -> Date?`
* `func isDateInWeekend(_ date: Date) -> Bool` | | 🟠 | `CharacterSet`

* Vended character sets are not complete
* `static var whitespaces: CharacterSet`
* `static var whitespacesAndNewlines: CharacterSet`
* `static var newlines: CharacterSet`
* `static var urlHostAllowed: CharacterSet`
* `static var urlFragmentAllowed: CharacterSet`
* `static var urlPathAllowed: CharacterSet`
* `static var urlQueryAllowed: CharacterSet`
* `init()`
* `init(charactersIn: String)`
* `func insert(_ character: Unicode.Scalar) -> (inserted: Bool, memberAfterInsert: Unicode.Scalar)`
* `mutating func insert(charactersIn: String)`
* `func update(with character: Unicode.Scalar) -> Unicode.Scalar?`
* `func remove(_ character: Unicode.Scalar) -> Unicode.Scalar?`
* `mutating func remove(charactersIn: String)`
* `func contains(_ member: Unicode.Scalar) -> Bool`
* `func union(_ other: CharacterSet) -> CharacterSet`
* `mutating func formUnion(_ other: CharacterSet)`
* `func intersection(_ other: CharacterSet) -> CharacterSet`
* `mutating func formIntersection(_ other: CharacterSet)`
* `func subtracting(_ other: CharacterSet)`
* `mutating func subtract(_ other: CharacterSet)`
* `func symmetricDifference(_ other: CharacterSet) -> CharacterSet`
* `mutating func formSymmetricDifference(_ other: CharacterSet)`
* `func isSuperset(of other: CharacterSet) -> Bool`
* `func isSubset(of other: CharacterSet) -> Bool`
* `func isDisjoint(with other: CharacterSet) -> Bool`
* `func isStrictSubset(of other: CharacterSet) -> Bool`
* `func isStrictSuperset(of other: CharacterSet) -> Bool`
* `var isEmpty: Bool` | | ✅ | `CocoaError` | | ✅ | `ComparisonResult` | | 🟠 | `CryptoKit`

* See the [CryptoKit](https://skip.dev/docs/modules/skip-foundation/#cryptokit)
topic for details on supported API. | | ✅ | `CustomNSError` | | 🟡 | `Data`

* `Data` does **not** conform to `Collection` protocols
* `init()`
* `init(count: Int)`
* `init(capacity: Int)`
* `init(_ data: Data)`
* `init(_ bytes: [UInt8], length: Int? = nil)`
* `init(_ checksum: Digest)`
* `init?(base64Encoded: String, options: Data.Base64DecodingOptions = [])`
* `init(contentsOfFile filePath: String) throws`
* `init(contentsOf url: URL, options: Data.ReadingOptions = [])`
* `var count: Int`
* `var isEmpty: Bool`
* `var bytes: [UInt8]`
* `var utf8String: String?`
* `func base64EncodedString() -> String`
* `func sha256() -> Data`
* `func hex() -> String`
* `mutating func reserveCapacity(_ minimumCapacity: Int)`
* `mutating func append(_ other: Data)`
* `mutating func append(contentsOf bytes: [UInt8])`
* `mutating func append(contentsOf data: Data)`
* `subscript(index: Int) -> UInt8`
* `func write(to url: URL, options: Data.WritingOptions = []) throws` | | 🟡 | `Date`

* Formatting functions not supported with the exception of:
* `func ISO8601Format(_ style: Date.ISO8601FormatStyle = .iso8601) -> String`
* `func formatted(date: Date.FormatStyle.DateStyle, time: Date.FormatStyle.TimeStyle) -> String` | | 🟢 | `DateComponents`

* `nanosecond`, `weekdayOrdinal`, `quarter`, `yearForWeekOfYear` are not supported | | 🟡 | `DateFormatter`

* `var dateStyle: DateFormatter.Style`
* `var timeStyle: DateFormatter.Style`
* `var isLenient: Bool`
* `var dateFormat: String`
* `func setLocalizedDateFormatFromTemplate(dateFormatTemplate: String)`
* `static func dateFormat(fromTemplate: String, options: Int, locale: Locale?) -> String?`
* `static func localizedString(from date: Date, dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style) -> String`
* `var timeZone: TimeZone?`
* `var locale: Locale?`
* `var calendar: Calendar?`
* `func date(from string: String) -> Date?`
* `func string(from date: Date) -> String`
* `func string(for obj: Any?) -> String?` | | ✅ | `DateInterval` | | 🟠 | `Decimal`

* Aliased to `java.math.BigDecimal` | | 🟠 | `DispatchQueue`

* `static let main: DispatchQueue`
* `func async(execute: () -> Void)`
* `func asyncAfter(deadline: DispatchTime, execute: () -> Void)`
* `func asyncAfter(wallDeadline: DispatchWallTime, execute: () -> Void)` | | 🟢 | `FileManager`

* ``static let `default`: FileManager``
* `let temporaryDirectory: URL`
* `let currentDirectoryPath: String`
* `func createSymbolicLink(at url: URL, withDestinationURL destinationURL: URL) throws`
* `func createSymbolicLink(atPath path: String, withDestinationPath destinationPath: String) throws`
* `func createDirectory(at url: URL, withIntermediateDirectories: Bool, attributes: [FileAttributeKey : Any]? = nil) throws`
* `func createDirectory(atPath path: String, withIntermediateDirectories: Bool, attributes: [FileAttributeKey : Any]? = nil) throws`
* `func destinationOfSymbolicLink(atPath path: String) throws`
* `func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: Any]`
* `func setAttributes(_ attributes: [FileAttributeKey : Any], ofItemAtPath path: String) throws`
* `func createFile(atPath path: String, contents: Data? = nil, attributes: [FileAttributeKey : Any]? = nil) -> Bool`
* `func copyItem(atPath path: String, toPath: String) throws`
* `func copyItem(at url: URL, to: URL) throws`
* `func moveItem(atPath path: String, toPath: String) throws`
* `func moveItem(at path: URL, to: URL) throws`
* `func subpathsOfDirectory(atPath path: String) throws -> [String]`
* `func subpaths(atPath path: String) -> [String]?`
* `func removeItem(atPath path: String) throws`
* `func removeItem(at url: URL) throws`
* `func fileExists(atPath path: String) -> Bool`
* `func isReadableFile(atPath path: String) -> Bool`
* `func isExecutableFile(atPath path: String) -> Bool`
* `func isDeletableFile(atPath path: String) -> Bool`
* `func isWritableFile(atPath path: String) -> Bool`
* `func contentsOfDirectory(at url: URL, includingPropertiesForKeys: [URLResourceKey]?) throws -> [URL]`
* `func contentsOfDirectory(atPath path: String) throws -> [String]`
* `func url(for directory: FileManager.SearchPathDirectory, in domain: FileManager.SearchPathDomainMask, appropriateFor url: URL?, create shouldCreate: Bool) throws -> URL` | | ✅ | `HTTPURLResponse` | | 🟢 | `IndexPath`

* Cannot assign from an array literal
* Cannot assign to a range subscript | | 🟡 | `IndexSet`

* This is an **inefficient** implementation using an internal `Set`
* `init(integersIn range: any RangeExpression)`
* `init(integer: Int)`
* `init()`
* `func integerGreaterThan(_ integer: Int) -> Int?`
* `func integerLessThan(_ integer: Int) -> Int?`
* `func integerGreaterThanOrEqualTo(_ integer: Int) -> Int?`
* `func integerLessThanOrEqualTo(_ integer: Int) -> Int?`
* `func count(in range: any RangeExpression) -> Int`
* `func contains(integersIn range: any RangeExpression) -> Bool`
* `func contains(integersIn indexSet: IntSet) -> Bool`
* `func intersects(integersIn range: any RangeExpression) -> Bool`
* `mutating func insert(integersIn range: any RangeExpression)`
* `mutating func remove(integersIn range: any RangeExpression)`
* `func filteredIndexSet(in range: any RangeExpression, includeInteger: (Int) throws -> Bool) rethrows -> IndexSet`
* `func filteredIndexSet(includeInteger: (Int) throws -> Bool) rethrows -> IndexSet`
* Supports the full `SetAlgebra` protocol | | 🟡 | `ISO8601DateFormatter`

* `static func string(from date: Date, timeZone: TimeZone) -> String`
* `var timeZone: TimeZone?`
* `func date(from string: String) -> Date?`
* `func string(from date: Date) -> String`
* `func string(for obj: Any?) -> String?` | | ✅ | `JSONDecoder` | | ✅ | `JSONEncoder` | | ✅ | `JSONSerialization` | | ✅ | `Locale` | | ✅ | `LocalizedError` | | ✅ | `LocalizedStringResource` | | ✅ | `OSLog.Logger`

* `Log messages on Android can be viewed with the adb logcat command, or in the Android Studio console` | | ✅ | `Notification` | | 🟡 | `NotificationCenter`

* `static let default: NotificationCenter`
* `func addObserver(forName name: Notification.Name?, object: Any?, queue: OperationQueue?, using block: (Notification) -> Void) -> Any`
* `func removeObserver(_ observer: Any)`
* `func post(_ notification: Notification)`
* `func post(name: Notification.Name, object: Any?, userInfo: [AnyHashable: Any]? = nil)`
* `func notifications(named: Notification.Name, object: AnyObject? = nil) -> Notifications`
* Also see support for `NotificationCenter.publisher` in the `SkipModel` module | | ✅ | `NSError` | | ✅ | `NSLock` | | ✅ | `NSLocalizedString` | | ✅ | `NSRecursiveLock` | | 🟢 | `Number`

* `init(value: Int8)`
* `init(value: Int16)`
* `init(value: Int32)`
* `init(value: Int64)`
* `init(value: UInt8)`
* `init(value: UInt16)`
* `init(value: UInt32)`
* `init(value: UInt64)`
* `init(value: Float)`
* `init(value: Double)`
* `init(_ value: Int8)`
* `init(_ value: Int16)`
* `init(_ value: Int32)`
* `init(_ value: Int64)`
* `init(_ value: UInt8)`
* `init(_ value: UInt16)`
* `init(_ value: UInt32)`
* `init(_ value: UInt64)`
* `init(_ value: Float)`
* `init(_ value: Double)`
* `var doubleValue: Double`
* `var intValue: Int`
* `var longValue: Int64`
* `var int64Value: Int64`
* `var int32Value: Int32`
* `var int16Value: Int16`
* `var int8Value: Int8` | | 🟢 | `NumberFormatter`

* The following styles are supported: `.none, .decimal, .currency, .percent`
* `var numberStyle: NumberFormatter.Style`
* `var locale: Locale?`
* `var format: String`
* `var groupingSize: Int`
* `var generatesDecimalNumbers: Bool`
* `var alwaysShowsDecimalSeparator: Bool`
* `var usesGroupingSeparator: Bool`
* `var multiplier: NSNumber?`
* `var groupingSeparator: String?`
* `var percentSymbol: String?`
* `var currencySymbol: String?`
* `var zeroSymbol: String?`
* `var minusSign: String?`
* `var exponentSymbol: String?`
* `var negativeInfinitySymbol: String`
* `var positiveInfinitySymbol: String`
* `var internationalCurrencySymbol: String?`
* `var decimalSeparator: String?`
* `var currencyCode: String?`
* `var currencyDecimalSeparator: String?`
* `var notANumberSymbol: String?`
* `var positiveSuffix: String?`
* `var negativeSuffix: String?`
* `var positivePrefix: String?`
* `var negativePrefix: String?`
* `var maximumFractionDigits: Int`
* `var minimumFractionDigits: Int`
* `var maximumIntegerDigits: Int`
* `var minimumIntegerDigits: Int`
* `func string(from number: NSNumber) -> String?`
* `func string(from number: Int) -> String?`
* `func string(for object: Any?) -> String?`
* `func number(from string: String) -> NSNumber?` | | 🟠 | `OperationQueue`

* `static let main: OperationQueue` | | 🟢 | `OSAllocatedUnfairLock`

* `init()`
* `init(initialState: State)`
* `init(uncheckedState initialState: State)`
* `func lock()`
* `func unlock()`
* `func lockIfAvailable() -> Bool`
* `func withLockUnchecked(_ body: (inout State) throws -> R) rethrows -> R`
* `func func withLockUnchecked(_ body: () throws -> R) rethrows -> R`
* `func withLock(_ body: (inout State) throws -> R) rethrows -> R`
* `func withLock(_ body: () throws -> R) rethrows -> R`
* `func withLockIfAvailableUnchecked(_ body: (inout State) throws -> R) rethrows -> R?`
* `func withLockIfAvailableUnchecked(_ body: () throws -> R) rethrows -> R?`
* `func withLockIfAvailable(_ body: @Sendable (inout State) throws -> R) rethrows -> R?`
* `func withLockIfAvailable(_ body: () throws -> R) rethrows -> R?` | | ✅ | `POSIXError` | | 🟡 | `ProcessInfo`

* `static let processInfo: ProcessInfo`
* `var globallyUniqueString: String`
* `var systemProperties: [String: String]`
* `var environment: [String : String]`
* `var processIdentifier: Int32`
* `var arguments: [String]`
* `var hostName: String`
* `var processorCount: Int`
* `var operatingSystemVersionString: String`
* `var isMacCatalystApp: Bool`
* `var isiOSAppOnMac: Bool` | | 🟠 | `PropertyListSerialization`

* `static func propertyList(from: Data, options: PropertyListSerialization.ReadOptions = [], format: Any?) throws -> [String: String]?`
* Ignores any given `options` and `format` | | 🟠 | `RelativeDateTimeFormatter`

* `localizedString(from dateComponents: DateComponents) -> String`
* `func localizedString(fromTimeInterval timeInterval: TimeInterval) -> String`
* `func localizedString(for date: Date, relativeTo referenceDate: Date) -> String`
* `func string(for obj: Any?) -> String?` | | 🟠 | `RunLoop`

* `static let main: RunLoop` | | ✅ | `RecoverableError` | | 🟢 | `String`

* Core `String` API is defined in `SkipLib`. These extensions are defined in `SkipFoundation`
* `init(data: Data, encoding: StringEncoding)`
* `init(bytes: [UInt8], encoding: StringEncoding)`
* `init(contentsOf: URL)`
* `var capitalized: String`
* `var deletingLastPathComponent: String`
* `func replacingOccurrences(of search: String, with replacement: String) -> String`
* `func components(separatedBy separator: String) -> [String]`
* `func trimmingCharacters(in set: CharacterSet) -> String`
* `func addingPercentEncoding(withAllowedCharacters: CharacterSet) -> String?`
* `var removingPercentEncoding: String?`
* `var utf8Data: Data`
* `var utf8: [UInt8]`
* `var utf16: [UInt8]`
* `var utf32: [UInt8]`
* `var unicodeScalars: [UInt8]`
* `func data(using: StringEncoding, allowLossyConversion: Bool = true) -> Data?`
* `func write(to url: URL, atomically useAuxiliaryFile: Bool, encoding enc: StringEncoding) throws`
* `func write(toFile path: String, atomically useAuxiliaryFile: Bool, encoding enc: StringEncoding) throws` | | ✅ | `StringLocalizationValue` | | ✅ | `func strlen(_ string: String) -> Int` | | ✅ | `func strncmp(_ str1: String, _ str2: String) -> Int` | | 🟡 | `Thread`

* `current`
* `main`
* `isMainThread`
* `sleep(for: TimeInterval)`
* `sleep(until: Date)`
* `callStackSymbols`
* Starting and stopping threads is not implemented, nor is constructing a Thread with a block | | 🟡 | `Timer`

* `init(timeInterval: TimeInterval, repeats: Bool, block: (Timer) -> Void)`
* `static func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: (Timer) -> Void) -> Timer`
* `var timeInterval: TimeInterval`
* `func invalidate()`
* `var isValid: Bool`
* `var userInfo: Any?`
* Also see support for `Timer.publish` in the `SkipModel` module | | 🟢 | `TimeZone`

* `static var current: TimeZone`
* `static var autoupdatingCurrent: TimeZone`
* ``static var `default`: TimeZone``
* `static var system: TimeZone`
* `static var local: TimeZone`
* `static var gmt: TimeZone`
* `init?(identifier: String)`
* `init?(abbreviation: String)`
* `init?(secondsFromGMT seconds: Int)`
* `var identifier: String`
* `func abbreviation(for date: Date = Date()) -> String?`
* `func secondsFromGMT(for date: Date = Date()) -> Int`
* `func isDaylightSavingTime(for date: Date = Date()) -> Bool`
* `func daylightSavingTimeOffset(for date: Date = Date()) -> TimeInterval`
* `var nextDaylightSavingTimeTransition: Date?`
* `func nextDaylightSavingTimeTransition(after date: Date) -> Date?`
* `static var knownTimeZoneIdentifiers: [String]`
* `static var knownTimeZoneNames: [String]`
* `static var abbreviationDictionary: [String : String]`
* `func localizedName(for style: NameStyle, locale: Locale?) -> String?` | | ✅ | `UnknownNSError` | | 🟢 | `URL`

* `init(_ url: URL)`
* `init?(string: String, relativeTo baseURL: URL? = nil)`
* `init?(string: String, encodingInvalidCharacters: Bool)`
* `init(fileURLWithPath path: String, isDirectory: Bool? = nil, relativeTo base: URL? = nil)`
* `static func currentDirectory() -> URL`
* `static var homeDirectory: URL`
* `static var temporaryDirectory: URL`
* `static var cachesDirectory: URL`
* `static var documentsDirectory: URL`
* `let baseURL: URL?`
* `var scheme: String?`
* `var host: String?`
* `func host(percentEncoded: Bool = true) -> String?`
* `var port: Int?`
* `var path: String?`
* `func path(percentEncoded: Bool = true) -> String?`
* `var hasDirectoryPath: Bool`
* `var query: String?`
* `func query(percentEncoded: Bool = true) -> String?`
* `var fragment: String?`
* `func fragment(percentEncoded: Bool = true) -> String?`
* `var standardized: URL`
* `var standardizedFileURL: URL`
* `mutating func standardize()`
* `var absoluteURL: URL`
* `var absoluteString: String`
* `var relativePath: String`
* `var relativeString: String`
* `var pathComponents: [String]`
* `var lastPathComponent: String`
* `var pathExtension: String`
* `var isFileURL: Bool`
* `func appendingPathComponent(_ pathComponent: String) -> URL`
* `mutating func appendPathComponent(_ pathComponent: String)`
* `func appendingPathComponent(_ pathComponent: String, isDirectory: Bool) -> URL`
* `mutating func appendPathComponent(_ pathComponent: String, isDirectory: Bool)`
* `func appendingPathExtension(_ pathExtension: String) -> URL`
* `mutating func appendPathExtension(_ pathExtension: String)`
* `func deletingLastPathComponent() -> URL`
* `mutating func deleteLastPathComponent() -> URL`
* `func deletingPathExtension() -> URL`
* `mutating func deletePathExtension()`
* `func resolvingSymlinksInPath() -> URL`
* `mutating func resolveSymlinksInPath() -> URL`
* `func checkResourceIsReachable() throws -> Bool` | | 🟢 | `URLComponents`

* `init()`
* `init?(url: URL, resolvingAgainstBaseURL resolve: Bool)`
* `init?(string: String)`
* `init?(string: String, encodingInvalidCharacters: Bool)`
* `var url: URL?`
* `func url(relativeTo base: URL?) -> URL?`
* `var string: String?`
* `var scheme: String?`
* `var host: String?`
* `var port: Int?`
* `var path: String`
* `var fragment: String?`
* `var query: String?`
* `var queryItems: [URLQueryItem]?`
* `var percentEncodedHost: String?`
* `var encodedHost: String?`
* `var percentEncodedPath: String`
* `var percentEncodedQuery: String?`
* `var percentEncodedFragment: String?`
* `var percentEncodedQueryItems: [URLQueryItem]?` | | ✅ | `URLError` | | ✅ | `URLQueryItem` | | 🟡 | `URLRequest`

* Many properties are currently ignored by `URLSession`
* `httpBodyStream` is not supported | | ✅ | `URLResponse` | | 🟡 | `URLSession`

* `static let shared: URLSession`
* `init(configuration: URLSessionConfiguration, delegate: URLSessionDelegate? = nil, delegateQueue: OperationQueue? = nil)`
* `var configuration: URLSessionConfiguration`
* `var sessionDescription: String?`
* `var delegate: URLSessionDelegate?`
* `var delegateQueue: OperationQueue?`
* `func data(for request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (Data, URLResponse)`
* `func data(from url: URL, delegate: URLSessionTaskDelegate? = nil) async throws -> (Data, URLResponse)`
* `func upload(for request: URLRequest, fromFile fileURL: URL, delegate: URLSessionTaskDelegate? = nil) async throws -> (Data, URLResponse)`
* `func upload(for request: URLRequest, from bodyData: Data, delegate: URLSessionTaskDelegate? = nil) async throws -> (Data, URLResponse)`
* `func bytes(for request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (AsyncBytes, URLResponse)`
* `func bytes(from url: URL, delegate: URLSessionTaskDelegate? = nil) async throws -> (AsyncBytes, URLResponse)`
* `func dataTask(with url: URL, completionHandler: ((Data?, URLResponse?, Error?) -> Void)? = nil) -> URLSessionDataTask`
* `func dataTask(with request: URLRequest, completionHandler: ((Data?, URLResponse?, Error?) -> Void)? = nil) -> URLSessionDataTask`
* `func uploadTask(with: URLRequest, from: Data?, completionHandler: ((Data?, URLResponse?, Error?) -> Void)? = nil) -> URLSessionUploadTask`
* `func uploadTask(with: URLRequest, fromFile: URL, completionHandler: ((Data?, URLResponse?, Error?) -> Void)?) -> URLSessionUploadTask`
* `func webSocketTask(with url: URL) -> URLSessionWebSocketTask`
* `func webSocketTask(with request: URLRequest) -> URLSessionWebSocketTask`
* `func webSocketTask(with url: URL, protocols: [String]) -> URLSessionWebSocketTask`
* `func finishTasksAndInvalidate()`
* `func getTasksWithCompletionHandler(_ handler: ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask]) -> Void)`
* `var tasks: ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])`
* `func getAllTasks(handler: ([URLSessionTask]) -> Void)`
* `var allTasks: [URLSessionTask]`
* `func invalidateAndCancel()`
* `struct AsyncBytes: AsyncSequence` | | 🟡 | `URLSessionConfiguration`

* Many properties are currently ignored by `URLSession` | | 🟢 | `URLSessionDataTask`

* `static let defaultPriority: Float`
* `static let lowPriority: Float`
* `static let highPriority: Float`
* `var taskIdentifier: Int`
* `var taskDescription: String?`
* `var originalRequest: URLRequest?`
* `var delegate: URLSessionTaskDelegate?`
* `var state: URLSessionTask.State`
* `var error: Error?`
* `var priority: Float`
* `func suspend()`
* `func resume()`
* `func cancel()` | | 🟢 | `URLSessionUploadTask`

* `static let defaultPriority: Float`
* `static let lowPriority: Float`
* `static let highPriority: Float`
* `var taskIdentifier: Int`
* `var taskDescription: String?`
* `var originalRequest: URLRequest?`
* `var delegate: URLSessionTaskDelegate?`
* `var state: URLSessionTask.State`
* `var error: Error?`
* `var priority: Float`
* `func suspend()`
* `func resume()`
* `func cancel()` | | 🟢 | `URLSessionWebSocketTask`

* `static let defaultPriority: Float`
* `static let lowPriority: Float`
* `static let highPriority: Float`
* `var taskIdentifier: Int`
* `var taskDescription: String?`
* `var originalRequest: URLRequest?`
* `var delegate: URLSessionTaskDelegate?`
* `var state: URLSessionTask.State`
* `var error: Error?`
* `var priority: Float`
* `func suspend()`
* `func resume()`
* `func cancel()`
* `func cancel(with closeCode: CloseCode, reason: Data?)`
* `var maximumMessageSize: Int`
* `var closeCode: CloseCode`
* `var closeReason: Data?`
* `func send(_ message: Message) async throws -> Void`
* `func receive() async throws -> Message` | | 🟢 | `UserDefaults`

* `static var standard: UserDefaults`
* `init(suiteName: String?)`
* `func register(defaults registrationDictionary: [String : Any])`
* `func set(_ value: Int, forKey defaultName: String)`
* `func set(_ value: Boolean, forKey defaultName: String)`
* `func set(_ value: Double, forKey defaultName: String)`
* `func set(_ value: String, forKey defaultName: String)`
* `func set(_ value: Any?, forKey defaultName: String)`
* `func removeObject(forKey defaultName: String)`
* `func object(forKey defaultName: String) -> Any?`
* `func string(forKey defaultName: String) -> String?`
* `func double(forKey defaultName: String) -> Double?`
* `func integer(forKey defaultName: String) -> Int?`
* `func bool(forKey defaultName: String) -> Bool?`
* `func url(forKey defaultName: String) -> URL?`
* `func data(forKey defaultName: String) -> Data?`
* `func dictionaryRepresentation() -> [String : Any]`
* Array and dictionary values are not supported | | 🟢 | `UUID`

* `init()`
* `init?(uuidString: String)`
* `static func fromString(uuidString: String) -> UUID?`
* `var uuidString: String` | Topics ------ [Section titled “Topics”](https://skip.dev/docs/modules/skip-foundation/#topics) ### CryptoKit [Section titled “CryptoKit”](https://skip.dev/docs/modules/skip-foundation/#cryptokit) SkipFoundation vends portions of the CryptoKit framework by delegating to the built-in Java implementations: * `SHA256` * `SHA256Digest` * `SHA384` * `SHA384Digest` * `SHA512` * `SHA512Digest` * `Insecure.MD5` * `Insecure.MD5Digest` * `Insecure.SHA1` * `Insecure.SHA1Digest` * `HMACMD5` * `HMACSHA1` * `HMACSHA256` * `HMACSHA384` * `HMACSHA512` Each supported algorithm includes the following API: associatedtype Digest public static func hash(data: Data) -> Digestpublic func update(_ data: DataProtocol)public func finalize() -> Digest The returned `Digest` in turn acts as a sequence of `UInt8` bytes. ### Files [Section titled “Files”](https://skip.dev/docs/modules/skip-foundation/#files) Skip implements much of `Foundation.FileManager`, which should be the primary interface for interacting with the file system. The app-specific folder can be accessed like: // on Android, this is Context.getFilesDir()let folder = URL.documentsDirectory // which is shorthand for the following:let folder = try FileManager.default.url(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false) And to read and write to the cache folders: // on Android, this is Context.getCachesDir()let caches = URL.cachesDirectory // which is shorthand for the following:let caches = try FileManager.default.url(for: FileManager.SearchPathDirectory.cachesDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false) And the system temporary folder can be accessed with: // on Android, this will be the same as Context.getCachesDir()let tmpdir = URL.temporaryDirectory // you can also use:let tmpdir = NSTemporaryDirectory() None of the other `FileManager.SearchPathDirectory` enumerations are implemented in Skip. Both `Data` and `String` have the ability to read and write to and from URLs and path strings. --- # Supabase | Skip [Skip to content](https://skip.dev/docs/modules/skip-supabase/#_top) Supabase ======== This package provides Supabase support for Skip Lite transpiled Swift. The Swift side uses the official Supabase iOS SDK directly, with the various `SkipSupabase*` modules passing the transpiled calls through to the community Supabase Android SDK. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-supabase/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-supabase.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipSupabase", package: "skip-supabase") ]) ]) Usage ----- [Section titled “Usage”](https://skip.dev/docs/modules/skip-supabase/#usage) The current Supabase API coverage is currently very limited, and mostly used as a starting point. Browse the test cases at [SkipSupabaseTests.swift ↗](https://source.skip.dev/skip-supabase/blob/main/Tests/SkipSupabaseTests/SkipSupabaseTests.swift) to see what API is supported. Package ------- [Section titled “Package”](https://skip.dev/docs/modules/skip-supabase/#package) The modules in the SkipSupabase framework project mirror the division of the SwiftPM modules in the Supabase iOS SDK (at [http://github.com/supabase/supabase-swift ↗](http://github.com/supabase/supabase-swift) ), which is generally mirrored in the division of the Supabase Kotlin Android gradle modules (at [https://github.com/supabase-community/supabase-kt ↗](https://github.com/supabase-community/supabase-kt) ). Status ------ [Section titled “Status”](https://skip.dev/docs/modules/skip-supabase/#status) This project is in a very early stage, but some amount of Auth and Database API is implemented. For examples of what is working, see the [SkipSupabaseTests.swift ↗](https://source.skip.dev/skip-supabase/blob/main/Tests/SkipSupabaseTests/SkipSupabaseTests.swift) test case, which also shows how setup can be performed. Please file an [issue ↗](https://source.skip.dev/skip-supabase/issues) if there is a particular API that you need for you project, or if something isn’t working right. And please consider contributing to this project by filing [pull requests ↗](https://source.skip.dev/skip-supabase/pulls) . ### Implementation Details [Section titled “Implementation Details”](https://skip.dev/docs/modules/skip-supabase/#implementation-details) This package mimics the API shape of the [supabase-swift ↗](http://github.com/supabase/supabase-swift) package by adapting it to the [supabase-kt ↗](https://github.com/supabase-community/supabase-kt) project. Unlike other Skip API adaptations (like [Skip Firebase](https://skip.dev/docs/modules/skip-firebase) ), this is a challenging task because the Swift and Kotlin interfaces to Supabase were designed and implemented separately, and so their API shapes differ drastically. For an example of some of the gymnastics that are required to achieve the goal is a single unified API, see the implementation of [SkipSupabase.swift ↗](https://source.skip.dev/skip-supabase/blob/main/Sources/SkipSupabase/SkipSupabase.swift) . --- # Marketplace | Skip [Skip to content](https://skip.dev/docs/modules/skip-marketplace/#_top) Marketplace =========== This module provide support for interfacing with an app’s marketplace, such as the Google Play Store for Android and the Apple App Store for iOS. Currently, the framework provides the ability to request a store rating for the app from the user. In the future, this framework will provide the ability to perform in-app purchases and subscription management. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-marketplace/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.dev/skip-marketplace.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipMarketplace", package: "skip-marketplace") ]) ]) App Review Requests ------------------- [Section titled “App Review Requests”](https://skip.dev/docs/modules/skip-marketplace/#app-review-requests) You can use this library to request that the app marketplace show a prompt to the user requesting a rating for the app for the given marketplace. import SkipMarketplace // request that the system show an app review request at most once every monthMarketplace.current.requestReview(period: .days(31)) For guidance on how and when to make these sorts of requests, see the relevant documentation for the [Apple App Store ↗](https://developer.android.com/guide/playcore/in-app-review#when-to-request) and [Google PlayStore ↗](https://developer.apple.com/design/human-interface-guidelines/ratings-and-reviews#Best-practices) . Prompting for updates --------------------- [Section titled “Prompting for updates”](https://skip.dev/docs/modules/skip-marketplace/#prompting-for-updates) import SkipMarketplace struct ContentView: View { var body: some View { YourViewCode() .appUpdatePrompt() }} On iOS, we’ll query `https://itunes.apple.com/lookup?bundleId=\(bundleId)` for the current latest version, presenting an [`.appStoreOverlay()` ↗](https://developer.apple.com/documentation/swiftui/view/appstoreoverlay%28ispresented%3Aconfiguration%3A%29) for the current app when a newer version is available to install. The overlay will include an “Update” button which will initiate an update, forcing your app to quit. On Android, we’ll use the [Google Play In-App Updates Library ↗](https://developer.android.com/guide/playcore/in-app-updates) , displaying a dismissable fullscreen “immediate” update. Google provides instructions to [test in-app updates ↗](https://developer.android.com/guide/playcore/in-app-updates/test) using internal app sharing. Querying App Installation Source -------------------------------- [Section titled “Querying App Installation Source”](https://skip.dev/docs/modules/skip-marketplace/#querying-app-installation-source) Determining which source was used to install the app (Apple App store, Google Play Store, AltStore, F-Droid, etc.) can be useful for determining what billing mechanism to use. This can be done by querying the `Marketplace.current.installationSource` property like: switch await Marketplace.current.installationSource {case .appleAppStore: canUseNativeBillling = truecase .googlePlayStore: canUseNativeBillling = truecase .other(let id): canUseNativeBillling = false // handle other markerplaces heredefault: canUseNativeBillling = false} Listing and purchasing in-app purchases --------------------------------------- [Section titled “Listing and purchasing in-app purchases”](https://skip.dev/docs/modules/skip-marketplace/#listing-and-purchasing-in-app-purchases) > Rather than building all of that yourself to integrate with SkipMarketplace, you might prefer to use [RevenueCat ↗](https://www.revenuecat.com/) > for this, using the [skip-revenue](https://skip.dev/docs/modules/skip-revenue) > library. (RevenueCat does cost money; if you want to roll your own subscription-management software, you can do it with SkipMarketplace.) ### Android Configuration [Section titled “Android Configuration”](https://skip.dev/docs/modules/skip-marketplace/#android-configuration) You must set the `com.android.vending.BILLING` permission in your `AndroidManifest.xml` file like so: ### Start by defining your products in App Store Connect and/or the Google Play Store [Section titled “Start by defining your products in App Store Connect and/or the Google Play Store”](https://skip.dev/docs/modules/skip-marketplace/#start-by-defining-your-products-in-app-store-connect-andor-the-google-play-store) * One-time products * App Store Connect: [Create consumable or non-consumable In-App Purchases ↗](https://developer.apple.com/help/app-store-connect/manage-in-app-purchases/create-consumable-or-non-consumable-in-app-purchases/) * Google Play Console: [Overview of one-time products ↗](https://support.google.com/googleplay/android-developer/answer/16430488) * Subscriptions * App Store Connect: [Offer auto-renewable subscriptions ↗](https://developer.apple.com/help/app-store-connect/manage-subscriptions/offer-auto-renewable-subscriptions) * Google Play Console: [Create and manage subscriptions ↗](https://support.google.com/googleplay/android-developer/answer/140504?hl=en) ### One-Time Purchases: Fetch ProductInfo and Prices [Section titled “One-Time Purchases: Fetch ProductInfo and Prices”](https://skip.dev/docs/modules/skip-marketplace/#one-time-purchases-fetch-productinfo-and-prices) do { let productIdentifiers = ["product1", "product2", "product3"] let products: [ProductInfo] = try await Marketplace.current.fetchProducts( for: productIdentifiers, subscription: false ) for product in products { print("product \(product.id) \(product.displayName)") let oneTimePurchaseOfferInfo: [OneTimePurchaseOfferInfo] = product.oneTimePurchaseOfferInfo! for offer in oneTimePurchaseOfferInfo { // On iOS, there will be only one offer, and its ID will be nil // On GPS, there may be multiple offers, if you configured additional offers in the console print("product \(product.id) offer \(offer.id ?? "nil") \(offer.displayPrice) \(offer.price)") } }} ### Subscriptions: Fetch ProductInfo and Prices [Section titled “Subscriptions: Fetch ProductInfo and Prices”](https://skip.dev/docs/modules/skip-marketplace/#subscriptions-fetch-productinfo-and-prices) do { let productIdentifiers = ["product1", "product2", "product3"] let products: [ProductInfo] = try await Marketplace.current.fetchProducts(for: productIdentifiers, subscription: true) for product in products { print("product \(product.id) \(product.displayName)") let subscriptionOffers: [SubscriptionOfferInfo] = product.subscriptionOffers! for offer in subscriptionOffers { #if !SKIP print("product \(product.id) offer \(offer.id ?? "nil") type \(offer.type)") #endif let pricingPhases: [SubscriptionPricingPhase] = offer.pricingPhases for pricingPhase in pricingPhases { print("product \(product.id) offer \(offer.id ?? "nil") \(pricingPhase.displayPrice) \(pricingPhase.price)") } } }} catch { print("Error fetching products: \(error)")} ### Purchasing (displaying a purchase sheet) [Section titled “Purchasing (displaying a purchase sheet)”](https://skip.dev/docs/modules/skip-marketplace/#purchasing-displaying-a-purchase-sheet) do { let product: ProductInfo = try await Marketplace.current.fetchProducts(for: ["productIdentifier"], subscription: false).first! if let purchaseTransaction: PurchaseTransaction = try await Marketplace.current.purchase(item: product) { print("Purchased \(purchaseTransaction.products)") // after you've stored the transaction somewhere, you should finish every PurchaseTransaction to acknowledge receipt try await Marketplace.current.finish(purchaseTransaction: purchaseTransaction) }} catch { print("Error purchasing product: \(error)")} You can also pass in a purchase offer (with a discounted price). do { let product: ProductInfo = try await Marketplace.current.fetchProducts(for: ["productIdentifier"], subscription: false).first! let offer = product.oneTimePurchaseOfferInfo.first! if let purchaseTransaction: PurchaseTransaction = try await Marketplace.current.purchase(item: product, offer: offer) { print("Purchased \(purchaseTransaction.products)") // after you've stored the transaction somewhere, you should finish every PurchaseTransaction to acknowledge receipt try await Marketplace.current.finish(purchaseTransaction: purchaseTransaction) }} catch { print("Error purchasing product: \(error)")} ### Querying for entitlements [Section titled “Querying for entitlements”](https://skip.dev/docs/modules/skip-marketplace/#querying-for-entitlements) “Entitlements” ane non-consumable one-time products and subscriptions, something that the user is entitled to because they’ve currently purchased it. do { let entitlements: [PurchaseTransaction] = try await Marketplace.current.fetchEntitlements() for purchaseTransaction in entitlements { let products: [String] = purchaseTransaction.products print("You own \(products)") // after you've stored the transaction somewhere, you should finish every PurchaseTransaction to acknowledge receipt // it's OK to "finish" the same transaction more than once try await Marketplace.current.finish(purchaseTransaction: purchaseTransaction) }} catch { print("Error fetching entitlements: \(error)")} ### Handling updates to purchase transactions [Section titled “Handling updates to purchase transactions”](https://skip.dev/docs/modules/skip-marketplace/#handling-updates-to-purchase-transactions) do { for try await purchaseTransaction in Marketplace.current.getPurchaseTransactionUpdates() { print("Transaction update: \(purchaseTransaction)") // after you've stored the transaction somewhere, you should finish every PurchaseTransaction to acknowledge receipt // it's OK to "finish" the same transaction more than once try await Marketplace.current.finish(purchaseTransaction: purchaseTransaction) }} catch { print("Error loading transaction updates: \(error)")} ### Testing purchases during development [Section titled “Testing purchases during development”](https://skip.dev/docs/modules/skip-marketplace/#testing-purchases-during-development) * iOS: [Setting up StoreKit testing in Xcode ↗](https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode) * Google Play: [Test your Google Play Billing Library integration ↗](https://developer.android.com/google/play/billing/test) Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-marketplace/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-marketplace/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. --- # Hello Skip (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-hello/#_top) Hello Skip (Lite) ================= This is a Skip Lite dual-platform app project. It builds a native app for both iOS and Android. This is the exact project with will be output when running the command: skip init --zero --appid=skip.hello.App skipapp-hello HelloSkip The project structure looks like this: skipapp-hello├── Android│   ├── app│   │   ├── build.gradle.kts│   │   ├── proguard-rules.pro│   │   └── src│   │   └── main│   │   ├── AndroidManifest.xml│   │   └── kotlin│   │   └── hello│   │   └── skip│   │   └── Main.kt│   ├── gradle.properties│   └── settings.gradle.kts├── CHANGELOG.md├── Darwin│   ├── Assets.xcassets│   │   ├── AccentColor.colorset│   │   │   └── Contents.json│   │   └── Contents.json│   ├── Entitlements.plist│   ├── HelloSkip.xcconfig│   ├── HelloSkip.xcodeproj│   │   └── project.pbxproj│   ├── Info.plist│   └── Sources│   └── HelloSkipAppMain.swift├── Package.swift├── README.md├── Skip.env├── Sources│   └── HelloSkip│   ├── ContentView.swift│   ├── HelloSkipApp.swift│   ├── Resources│   │   ├── Localizable.xcstrings│   │   └── Module.xcassets│   │   └── Contents.json│   ├── Skip│   │   └── skip.yml│   └── ViewModel.swift└── Tests └── HelloSkipTests ├── HelloSkipTests.swift ├── Resources │   └── TestData.json ├── Skip │   └── skip.yml └── XCSkipTests.swift Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-hello/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-hello/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-hello/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-hello/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the HelloSkipApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # Skip CLI Reference | Skip [Skip to content](https://skip.dev/docs/skip-cli/#_top) Skip CLI Reference ================== The Skip command-line interface (CLI) is an installed tool that can be run from the terminal on macOS, Linux, or Windows. It provides an interface for creating new Skip projects (both Framework and App projects), as well as running tests, validating projects, and exporting built artifacts for publication. Installing ---------- [Section titled “Installing”](https://skip.dev/docs/skip-cli/#installing) The `skip` CLI is installed using [Homebrew ↗](https://brew.sh/) . Skip is distributed as a binary Homebrew “Cask” for macOS, Linux, and Windows (with [WSL 2 ↗](https://learn.microsoft.com/en-us/windows/wsl/about) ). For complete details, see the [Getting Started Guide](https://skip.dev/docs/gettingstarted/) . Once Homebrew is set up, Skip can be installed (and updated) by running the Terminal command: % brew install skiptools/skip/skip ==> Tapping skiptools/skipCloning into '/opt/homebrew/Library/Taps/skiptools/homebrew-skip'...Tapped 1 cask (15 files, 417KB).==> Downloading https://source.skip.dev/skip/releases/download/1.0.0/skip.zip==> Installing dependencies: android-platform-tools==> Downloading https://dl.google.com/android/repository/platform-tools_r34.0.5-darwin.zip==> Installing Cask android-platform-tools==> Linking Binary 'adb' to '/opt/homebrew/bin/adb'🍺 android-platform-tools was successfully installed!==> Installing Cask skip==> Linking Binary 'skip' to '/opt/homebrew/bin/skip' ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄ ▄▄ ▄▄▄▄▄▄▄ █ ██ █ █ ██ ██ █ █ ▄▄▄▄▄██ █▄█ ██ ██ ▄ █ █ █▄▄▄▄▄██ ▄██ ██ █▄█ █ █▄▄▄▄▄ ██ █▄██ ██ ▄▄▄█ ▄▄▄▄▄█ ██ ▄ ██ ██ █ █▄▄▄▄▄▄▄██▄▄▄█ █▄██▄▄██▄▄▄█ Welcome to Skip 1.7.0! Run "skip checkup" to perform a full system evaluation.Run "skip create" to start a new project. Visit https://skip.dev for documentation, samples, and FAQs. Happy Skipping! 🍺 skip was successfully installed! This will download and install the `skip` tool itself, as well as the `gradle` and Android SDK dependencies that are necessary for building and testing the Kotlin/Android side of your apps. * * * Skip Commands ------------- [Section titled “Skip Commands”](https://skip.dev/docs/skip-cli/#intro) OVERVIEW: skip 1.7.0 USAGE: skip OPTIONS: -h, --help Show help information. SUBCOMMANDS: version Print the skip version doctor Evaluate and diagnose Skip development environment checkup Run tests to ensure Skip is in working order upgrade Upgrade to the latest Skip version create Create a new Skip project interactively init Initialize a new Skip project verify Verify Skip project icon Create and manage app icons android Perform a native Android package command export Export the Gradle project and built artifacts devices List connected devices and emulators/simulators test Run parity tests and generate reports See 'skip help ' for detailed help. ### skip upgrade: Upgrade to the latest Skip version [Section titled “skip upgrade: Upgrade to the latest Skip version”](https://skip.dev/docs/skip-cli/#upgrade) OVERVIEW: Upgrade to the latest Skip version USAGE: skip upgrade OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OPTIONS: -h, --help Show help information. ### skip checkup: Run tests to ensure Skip is in working order [Section titled “skip checkup: Run tests to ensure Skip is in working order”](https://skip.dev/docs/skip-cli/#checkup) OVERVIEW: Run tests to ensure Skip is in working order This command performs a full system checkup to ensure that Skip can create andbuild a sample project. It runs all the checks performed by skip doctor, andalso creates and builds a conventional Skip app project. USAGE: # Perform a basic system checkup skip checkup # Perform a system checkup for native app support skip checkup --native OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OPTIONS: -c, --configuration Configuration debug/release (default: debug) --double-check Check twice that sample build outputs produce identical artifacts --native Generate native app when running checkup --native-app Generate native app when running checkup --native-model Generate native module when running checkup --fail-fast/--no-fail-fast Fail immediately when an error occurs (default: --fail-fast) --project-name Name of checkup project (default: hello-skip) --module-name Name of checkup project module (default: HelloSkip) -h, --help Show help information. ### skip create: Create a new Skip project interactively [Section titled “skip create: Create a new Skip project interactively”](https://skip.dev/docs/skip-cli/#create) OVERVIEW: Create a new Skip project interactively Create a new project by following a series of interactive prompts. USAGE: skip create OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) TOOLCHAIN OPTIONS: --swift-version Swift version to use --sdk Swift Android SDK path --ndk Android NDK path --toolchain Swift toolchain path --package-path Path to the package to run --scratch-path <.build> Custom scratch directory path -Xswiftc Pass flag through to all Swift compiler invocations -Xcc Pass flag through to all C compiler invocations -Xlinker Pass flag through to all linker invocations -Xcxx Pass flag through to all C++ compiler invocations -c, --configuration Build with configuration --arch Destination architectures (values: automatic, current, default, all, aarch64, armv7, x86_64) --android-api-level Android API level (default: 28) --swift-sdk-home Root path for Swift SDK --bridge/--no-bridge Enable SKIP_BRIDGE bridging to Kotlin (default: --bridge) --aggregate/--no-aggregate Enable bundling all libraries into a single shared object (default: --no-aggregate) --prune/--no-prune Prune non-dependent libraries from build output (default: --prune) CREATE OPTIONS: -d, --dir Base folder for project creation -c, --configuration Configuration debug/release (default: debug) --resource-path Resource folder name (default: Resources) --swift-package-version Swift version for project (default: 6.1) --ios-min-version Minimum iOS version to target (default: 17.0) --macos-min-version Minimum macOS version to target --chain/--no-chain Create library dependencies between modules (default: --chain) --zero/--no-zero Add SKIP_ZERO environment check to Package.swift (default: --no-zero) --git-repo/--no-git-repo Create a local git repository for the app (default: --no-git-repo) --free Create package with free software license --show-tree/--no-show-tree Display a file system tree summary of the new files (default: --no-show-tree) --kotlincompat/--no-kotlincompat Whether native model should use kotlincompat (default: --no-kotlincompat) --bridged/--no-bridged Whether transpiled model should br bridged (default: --no-bridged) --module-tests/--no-module-tests Whether to create test modules --fastlane/--no-fastlane Whether to create fastlane metadata (default: --fastlane) --github/--no-github Whether to create github metadata (default: --no-github) --validate-package/--no-validate-package Validate generated Package.swift files (default: --validate-package) OPTIONS: -h, --help Show help information. ### skip init: Initialize a new Skip project [Section titled “skip init: Initialize a new Skip project”](https://skip.dev/docs/skip-cli/#init) OVERVIEW: Initialize a new Skip project This command will create a conventional Skip app or library project. USAGE: # Create a new native Skip Fuse app project skip init --native-app --appid=some.app.id app-project AppName # Create a new transpiled Skip Lite app project skip init --transpiled-app --appid=some.app.id app-project AppName # Create a new native library project skip init --native-model lib-project ModuleName # Create a new transpiled library project skip init --transpiled-model lib-project ModuleName # Create a new app project with multiple modules skip init --native-app --appid=some.app.id app-project AppName ModuleName ARGUMENTS: Project folder name The module name(s) to create OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) CREATE OPTIONS: -d, --dir Base folder for project creation -c, --configuration Configuration debug/release (default: debug) --resource-path Resource folder name (default: Resources) --swift-package-version Swift version for project (default: 6.1) --ios-min-version Minimum iOS version to target (default: 17.0) --macos-min-version Minimum macOS version to target --chain/--no-chain Create library dependencies between modules (default: --chain) --zero/--no-zero Add SKIP_ZERO environment check to Package.swift (default: --no-zero) --git-repo/--no-git-repo Create a local git repository for the app (default: --no-git-repo) --free Create package with free software license --show-tree/--no-show-tree Display a file system tree summary of the new files (default: --no-show-tree) --kotlincompat/--no-kotlincompat Whether native model should use kotlincompat (default: --no-kotlincompat) --bridged/--no-bridged Whether transpiled model should br bridged (default: --no-bridged) --module-tests/--no-module-tests Whether to create test modules --fastlane/--no-fastlane Whether to create fastlane metadata (default: --fastlane) --github/--no-github Whether to create github metadata (default: --no-github) --validate-package/--no-validate-package Validate generated Package.swift files (default: --validate-package) PROJECT OPTIONS: --native-app/--transpiled-app/--native-model/--transpiled-model TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) BUILD OPTIONS: --build/--no-build Run the project build (default: --build) --test/--no-test Run the project tests (default: --no-test) --verify/--no-verify Verify the project output (default: --no-verify) OPTIONS: --appid Embed the library as an app with the given bundle id --no-icon Disable icon generation --icon Path to icon input file (SVG, PDF, PNG) --icon-background RGB hexadecimal color for icon background --icon-foreground RGB hexadecimal color for icon foreground --icon-shadow The amount of shadow to draw around the target --icon-inset The amount of inset to place on the image --version Set the initial version to the given value --apk/--no-apk Build the Android .apk file (default: --no-apk) --ipa/--no-ipa Build the iOS .ipa file (default: --no-ipa) --open-xcode Open the resulting Xcode project --open-gradle Open the resulting Gradle project -h, --help Show help information. ### skip doctor: Evaluate and diagnose Skip development environment [Section titled “skip doctor: Evaluate and diagnose Skip development environment”](https://skip.dev/docs/skip-cli/#doctor) OVERVIEW: Evaluate and diagnose Skip development environment This command will check for system configuration and prerequisites. It is asubset of the skip checkup command. USAGE: skip doctor OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OPTIONS: --native/--no-native Check for native SDK (default: --no-native) --fail-fast/--no-fail-fast Fail immediately when an error occurs (default: --no-fail-fast) -h, --help Show help information. ### skip verify: Verify Skip project [Section titled “skip verify: Verify Skip project”](https://skip.dev/docs/skip-cli/#verify) OVERVIEW: Verify Skip project This command is run on a Skip project to ensure that the structure and contentsare valid. USAGE: skip verify OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OPTIONS: --project

Project folder (default: .) --free/--no-free Validate free project --fastlane/--no-fastlane Validate fastlane config --fail-fast/--no-fail-fast Fail immediately when an error occurs (default: --no-fail-fast) --fix/--no-fix Attempt to automatically fix issues (default: --no-fix) -h, --help Show help information. ### skip export: Export the Gradle project and built artifacts [Section titled “skip export: Export the Gradle project and built artifacts”](https://skip.dev/docs/skip-cli/#export) OVERVIEW: Export the Gradle project and built artifacts Build and export the Skip modules defined in the Package.swift, with librariesexported as .aar files and the app exported as an .apk and .adb file suitablefor distribution. USAGE: # export just the debug version of the archives skip export --debug # export just the "ModuleName" module skip export --module ModuleName OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OPTIONS: -d, --dir Export output folder --package App package name --module Modules to export --project Project folder (default: .) --summary-file Output summary path --build/--no-build Build the Swift project before exporting (default: --build) --show-tree/--no-show-tree Display a file system tree summary (default: --no-show-tree) --release Perform release build --debug Perform debug build --export-project/--no-export-project Export project sources (default: --export-project) --ios/--no-ios Export iOS .ipa (default: --ios) --android/--no-android Export Android .apk (default: --android) --nested/--no-nested Output folders to variant sub-folders (default: --no-nested) --sdk-path SDK path for export build --scheme-name Project scheme name to export --arch Destination architectures for native libraries (values: automatic, current, default, all, aarch64, armv7, x86_64) -h, --help Show help information. ### skip test: Run parity tests and generate reports [Section titled “skip test: Run parity tests and generate reports”](https://skip.dev/docs/skip-cli/#test) OVERVIEW: Run parity tests and generate reports USAGE: skip test OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OPTIONS: --test/--no-test Run the project tests (default: --test) --filter Test filter(s) to run --project Project folder (default: .) --xunit Path to xunit test report --junit Path to junit test report --max-column-length Maximum table column length (default: 25) -c, --configuration Configuration debug/release (default: debug) --summary-file Output summary table -h, --help Show help information. ### skip icon: Create and manage app icons [Section titled “skip icon: Create and manage app icons”](https://skip.dev/docs/skip-cli/#icon) OVERVIEW: Create and manage app icons This command will create and update icons in the Darwin and Android folders ofa Skip project. skip icon should be run in the root folder of a conventional Skip app projectthat contains Darwin and Android folders. USAGE: # Resize the given PNG icon for each of the required icon sizes skip icon app_icon.png # Resize separate icons for each of Android and iOS skip icon --android app_icon_android.png skip icon --darwin app_icon_darwin.png # Generate a random icon set and open them in Preview.app skip icon --open-preview --random-icon --random-background # Generate new icons with a named color background skip icon --open-preview --background skyblue # Create new icons with a background gradient skip icon --open-preview --background #3E8E41-#2F4F4F # Create new icons with a background gradient overlaid with an SVG image skip icon --open-preview --background #5C6BC0-#3B3F54 symbol.svg # Create new icons with custom image inset and shadow radius skip icon --background #F7DC6F-#F2C464 --inset 0.4 --shadow 0.02 symbol.svg ARGUMENTS: Path or URL to icon source SVG, PNG, or PDF files OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) OPTIONS: -d, --dir Root folder for icon generation --open-preview Open the generated icons in Preview --android/--no-android Generate an Android icon set --android-path Path the Android resources root folder (default: Android/app/src/main/res) --darwin/--no-darwin Generate an Android icon set --darwin-path Path the Darwin icon assets folder (default: Darwin/Assets.xcassets/AppIcon.appiconset) --foreground Name or RGB hex color/gradient for icon color (default: white) --background Name or RGB hex color/gradient for icon background --inset The percentage amount of inset for the shape (default: 0.1) --shadow The percentage amount of shadow to dear around the path (default: 0.01) --random-icon Create a random icon shape --random-background Create a random icon color -h, --help Show help information. ### skip devices: List connected devices and emulators/simulators [Section titled “skip devices: List connected devices and emulators/simulators”](https://skip.dev/docs/skip-cli/#devices) OVERVIEW: List connected devices and emulators/simulators This command will list all the connected Android emulators and devices and iOSsimulators and devices. USAGE: skip devices TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) OPTIONS: -h, --help Show help information. ### skip android: Perform a native Android package command [Section titled “skip android: Perform a native Android package command”](https://skip.dev/docs/skip-cli/#android) OVERVIEW: Perform a native Android package command USAGE: skip android OPTIONS: -h, --help Show help information. SUBCOMMANDS: build Build the native project for Android run Run the executable target Android device or emulator test Test the native project on an Android device or emulator sdk Manage installation of Swift Android SDK emulator Manage Android emulators toolchain Manage installation of Swift Android Host Toolchain See 'skip help android ' for detailed help. ### skip android build: Build the native project for Android [Section titled “skip android build: Build the native project for Android”](https://skip.dev/docs/skip-cli/#android-build) OVERVIEW: Build the native project for Android USAGE: skip android build [] [ ...] ARGUMENTS: Command arguments OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) TOOLCHAIN OPTIONS: --swift-version Swift version to use --sdk Swift Android SDK path --ndk Android NDK path --toolchain Swift toolchain path --package-path Path to the package to run --scratch-path <.build> Custom scratch directory path -Xswiftc Pass flag through to all Swift compiler invocations -Xcc Pass flag through to all C compiler invocations -Xlinker Pass flag through to all linker invocations -Xcxx Pass flag through to all C++ compiler invocations -c, --configuration Build with configuration --arch Destination architectures (values: automatic, current, default, all, aarch64, armv7, x86_64) --android-api-level Android API level (default: 28) --swift-sdk-home Root path for Swift SDK --bridge/--no-bridge Enable SKIP_BRIDGE bridging to Kotlin (default: --bridge) --aggregate/--no-aggregate Enable bundling all libraries into a single shared object (default: --no-aggregate) --prune/--no-prune Prune non-dependent libraries from build output (default: --prune) OPTIONS: -d, --dir Archive output folder -h, --help Show help information. ### skip android test: Test the native project on an Android device or emulator [Section titled “skip android test: Test the native project on an Android device or emulator”](https://skip.dev/docs/skip-cli/#android-test) OVERVIEW: Test the native project on an Android device or emulator USAGE: skip android test [] [ ...] ARGUMENTS: Command arguments OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) TOOLCHAIN OPTIONS: --swift-version Swift version to use --sdk Swift Android SDK path --ndk Android NDK path --toolchain Swift toolchain path --package-path Path to the package to run --scratch-path <.build> Custom scratch directory path -Xswiftc Pass flag through to all Swift compiler invocations -Xcc Pass flag through to all C compiler invocations -Xlinker Pass flag through to all linker invocations -Xcxx Pass flag through to all C++ compiler invocations -c, --configuration Build with configuration --arch Destination architectures (values: automatic, current, default, all, aarch64, armv7, x86_64) --android-api-level Android API level (default: 28) --swift-sdk-home Root path for Swift SDK --bridge/--no-bridge Enable SKIP_BRIDGE bridging to Kotlin (default: --bridge) --aggregate/--no-aggregate Enable bundling all libraries into a single shared object (default: --no-aggregate) --prune/--no-prune Prune non-dependent libraries from build output (default: --prune) OPTIONS: --cleanup/--no-cleanup Cleanup test folders after running (default: --cleanup) --remote-folder Remote folder on emulator/device for build upload --testing-library Testing library name (default: all) --env Environment key/value pairs for remote execution --copy Additional files or folders to copy to Android -h, --help Show help information. ### skip android emulator create: Install and create an Android emulator image [Section titled “skip android emulator create: Install and create an Android emulator image”](https://skip.dev/docs/skip-cli/#android-emulator-create) OVERVIEW: Install and create an Android emulator image This command acts as a frontend to the Android SDK tools sdkmanager,avdmanager, and emulator. Run with the --verbose argument to observe the exactcommands that it executes. USAGE: # Creates the default Android emulator (API 34) skip android emulator create # Creates a custom Android emulator skip android emulator create --name 'pixel_7_api_36' --device-profile pixel_7 --android-api-level 36 --system-image google_apis_playstore_ps16k # Installs a specific emulator package android emulator install --package 'system-images;android-31;default;arm64-v8a' OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) OPTIONS: --android-api-level Android API emulator level (default: 34) --device-profile Android emulator device profile (default: medium_phone) -n, --name Android emulator name --package The full package name of the emulator to install --system-image Android emulator APIs (default: google_apis) --arch Android emulator architecture (default: arm64-v8a) -h, --help Show help information. ### skip android emulator list: List installed Android emulators [Section titled “skip android emulator list: List installed Android emulators”](https://skip.dev/docs/skip-cli/#android-emulator-list) OVERVIEW: List installed Android emulators USAGE: skip android emulator list OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OPTIONS: -h, --help Show help information. ### skip android emulator launch: Launch an Android emulator [Section titled “skip android emulator launch: Launch an Android emulator”](https://skip.dev/docs/skip-cli/#android-emulator-launch) OVERVIEW: Launch an Android emulator This command acts as a frontend to the Android SDK emulator command.Install new emulators with: skip android emulator createList installed emulators with: skip android emulator listRun with the --verbose argument to observe the exact commands that it executes. USAGE: # Launches the most recently created or used emulator skip android emulator launch # Launches an emulator with a certain name skip android emulator launch --name emulator-34-medium_phone ARGUMENTS: Emulator arguments OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OPTIONS: -n, --name Android emulator name --logcat Logcat filter (see https://developer.android.com/tools/logcat) (default: *:D) --background/--no-background Background the emulator process once it is launched (default: --no-background) --headless/--no-headless Run in headless mode (default: --no-headless) -h, --help Show help information. ### skip android sdk list: List the installed Swift Android SDKs [Section titled “skip android sdk list: List the installed Swift Android SDKs”](https://skip.dev/docs/skip-cli/#android-sdk-list) OVERVIEW: List the installed Swift Android SDKs USAGE: skip android sdk list TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) OPTIONS: --remote List remote SDKs that can be installed --devel Include development SDKs in remote list -h, --help Show help information. ### skip android sdk install: Install the native Swift Android SDK [Section titled “skip android sdk install: Install the native Swift Android SDK”](https://skip.dev/docs/skip-cli/#android-sdk-install) OVERVIEW: Install the native Swift Android SDK USAGE: # Installs the default version of the Android SDK skip android sdk install # Installs the latest nightly for 6.3 skip android sdk install --version nightly-6.3 OUTPUT OPTIONS: -o, --output Send output to the given file (stdout: -) -E, --message-errout Emit messages to the output rather than stderr -v, --verbose Whether to display verbose messages -q, --quiet Quiet mode: suppress output -J, --json Emit output as formatted JSON -j, --json-compact Emit output as compact JSON -M, --message-plain Show console messages as plain text rather than JSON --log-file Send log output to the file -A, --json-array Wrap and delimit JSON output as an array --plain/--no-plain Show no colors or progress animations (default: --no-plain) TOOL OPTIONS: --xcodebuild Xcode command path --swift Swift command path --gradle Gradle command path --adb ADB command path --emulator Android emulator path --android-home Path to the Android SDK (ANDROID_HOME) TOOLCHAIN OPTIONS: --swift-version Swift version to use --sdk Swift Android SDK path --ndk Android NDK path --toolchain Swift toolchain path --package-path Path to the package to run --scratch-path <.build> Custom scratch directory path -Xswiftc Pass flag through to all Swift compiler invocations -Xcc Pass flag through to all C compiler invocations -Xlinker Pass flag through to all linker invocations -Xcxx Pass flag through to all C++ compiler invocations -c, --configuration Build with configuration --arch Destination architectures (values: automatic, current, default, all, aarch64, armv7, x86_64) --android-api-level Android API level (default: 28) --swift-sdk-home Root path for Swift SDK --bridge/--no-bridge Enable SKIP_BRIDGE bridging to Kotlin (default: --bridge) --aggregate/--no-aggregate Enable bundling all libraries into a single shared object (default: --no-aggregate) --prune/--no-prune Prune non-dependent libraries from build output (default: --prune) OPTIONS: --version Version of the Swift Android SDK to install (default: 6.2) --ndk-version Version of the Android NDK to link to the toolchain (default: r27d) --reinstall Reinstall the Android SDK --verify/--no-verify Verify Android SDK installation (default: --verify) -h, --help Show help information. --- # Howdy Skip (Fuse) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-howdy/#_top) Howdy Skip (Fuse) ================= This is a Skip dual-platform app project. It builds a native app for both iOS and Android. This is a fully-native compiled Swift app, in contrast with the [skipapp-hello](https://skip.dev/docs/samples/skipapp-hello) sample, which is a fully-transpiled sample app. To learn about the distinction, see the [Native and Transpiled Modes](https://skip.dev/docs/modes/) documentaton. This repository contains the exact project with will be output when running the command: skip init --native-app --appid=skip.howdy.App skipapp-howdy HowdySkip The project structure looks like this: skipapp-howdy├── Android│   ├── app│   │   ├── build.gradle.kts│   │   ├── proguard-rules.pro│   │   └── src│   │   └── main│   │   ├── AndroidManifest.xml│   │   ├── kotlin│   │   │   └── Main.kt│   │   └── res│   │   ├── mipmap-anydpi│   │   │   └── ic_launcher.xml│   │   └── mipmap-hdpi│   │   ├── ic_launcher_background.png│   │   ├── ic_launcher_foreground.png│   │   ├── ic_launcher_monochrome.png│   │   └── ic_launcher.png│   ├── gradle│   │   └── wrapper│   │   └── gradle-wrapper.properties│   ├── gradle.properties│   └── settings.gradle.kts├── Darwin│   ├── Assets.xcassets│   │   ├── AccentColor.colorset│   │   │   └── Contents.json│   │   ├── AppIcon.appiconset│   │   │   ├── AppIcon@2x.png│   │   │   ├── AppIcon@3x.png│   │   │   └── Contents.json│   │   └── Contents.json│   ├── Entitlements.plist│   ├── HowdySkip.xcconfig│   ├── HowdySkip.xcodeproj│   │   ├── project.pbxproj│   │   ├── project.xcworkspace│   │   │   └── contents.xcworkspacedata│   │   └── xcshareddata│   │   └── xcschemes│   │   └── HowdySkip App.xcscheme│   ├── Info.plist│   └── Sources│   └── Main.swift├── Package.swift├── README.md├── Skip.env└── Sources └── HowdySkip ├── ContentView.swift ├── HowdySkipApp.swift ├── Resources │   ├── Localizable.xcstrings │   └── Module.xcassets │   └── Contents.json ├── Skip │   └── skip.yml └── ViewModel.swift Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-howdy/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-howdy/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-howdy/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the HowdySkipApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # Travel Posters (Split) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-travelposters-native/#_top) Travel Posters (Split) ====================== This is a demonstration of using a shared [Skip Fuse ↗](https://skip.tools/) dual-platform model library in native Swift to power both an iOS and an Android app. For more information on this project, see the blog post [Native Swift on Android, Part 3: Sharing a Swift Model Layer](https://skip.dev/blog/shared-swift-model/) . Workflow -------- [Section titled “Workflow”](https://skip.dev/docs/samples/skipapp-travelposters-native/#workflow) Iterate on both the `travel-posters-model` shared model package and the iOS app by opening the `TravelPostersNative.xcworkspace` Xcode workspace. Iterate on the Android app by opening the `Android/settings.gradle.kts` file in Android Studio. To donate the latest `travel-posters-model` shared model code to the Android app’s debug target and then build the app: $ skip export --project travel-posters-model -d Android/lib/debug/ --debug$ gradle -p Android assembleDebug Similarly, for the release build, you would run: $ skip export --project travel-posters-model -d Android/lib/release/ --release$ gradle -p Android assembleRelease There are many ways to automate this process, from simple scripting to git submodules to publishing the Android `travel-posters-model` output to a local Maven repository. Use whatever system fits your team’s workflow best. For example, to re-build and re-launch the app after making changes to the Swift code, you might run: skip export --project travel-posters-model -d Android/lib/debug/ --debuggradle -p Android installDebugadb shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n tools.skip.travelposters/tools.skip.travelposters.MainActivity Building & Running ------------------ [Section titled “Building & Running”](https://skip.dev/docs/samples/skipapp-travelposters-native/#building--running) Use Xcode to build and run the iOS app and the shared `travel-posters-model` package. Use Android Studio to build and run the Android app. Before building the Android app the first time, you must follow the instructions above to donate the native model build. Then use “Sync Project with Gradle Files” in Android Studio to sync the donated libraries. Do this every time you update the library versions. Deployment Notes ---------------- [Section titled “Deployment Notes”](https://skip.dev/docs/samples/skipapp-travelposters-native/#deployment-notes) Note the following declaration in `Android/app/build.gradle.kts`: android { packaging { jniLibs { // doNotStrip is needed to prevent errors like: java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH/DT_GNU_HASH in "/data/app/…/base.apk!/lib/arm64-v8a/libdispatch.so" (new hash type from the future?) (see: https://github.com/finagolfin/swift-android-sdk/issues/67) keepDebugSymbols.add("**/*.so") } }} This is needed to prevent a known issue with some compiled Swift libraries. --- # Accessibility | Skip [Skip to content](https://skip.dev/docs/components/accessibility/#_top) Accessibility ============= Skip support for [SwiftUI.Accessibility ↗](https://developer.apple.com/documentation/swiftui/view-accessibility) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`AccessibilityPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/AccessibilityPlayground.swift) import SwiftUI struct AccessibilityPlayground: View { @State var isOn = false var body: some View { ScrollView { VStack(spacing: 16) { Text("Simulate a custom control with an accessibility label, value, and traits:") Text(isOn ? "+" : "-").font(.largeTitle) .onTapGesture { isOn = !isOn } .accessibilityLabel("My custom control") .accessibilityValue(isOn ? "On" : "Off") .accessibilityAddTraits(.isButton) // Use .isToggle on iOS 17+ Divider() Text("Hide the following element from accessibility:") Text("Hidden").font(.largeTitle) .accessibilityHidden(true) } .padding() } .toolbar { PlaygroundSourceLink(file: "AccessibilityPlayground.swift") } }} --- # Travel Bookings (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-bookings/#_top) Travel Bookings (Lite) ====================== This is a demonstration of a full-featured Skip Lite dual-platform app project for both iOS and Android. It is a travel exploration app that displays cities loaded from a bundled JSON asset, and has the following features: * A navigation list of the bundled city list * A persistent favorites list that can be modified and reordered * An embedded maps view for any given city * An embedded web view showing the wikipedia page for the city * Dynamic temperature update using a weather web service endpoint * A settings view enabling override of preferred color scheme and display units Your browser does not support the video tag. | iOS | Android | | --- | --- | | ![iOS Screenshot](https://assets.skip.dev/skipapp-bookings/city-list-iphone.png) | ![Android Screenshot](https://assets.skip.dev/skipapp-bookings/city-list-android.png) | | ![iOS Screenshot](https://assets.skip.dev/skipapp-bookings/city-view-iphone.png) | ![Android Screenshot](https://assets.skip.dev/skipapp-bookings/city-view-android.png) | | ![iOS Screenshot](https://assets.skip.dev/skipapp-bookings/city-settings-iphone.png) | ![Android Screenshot](https://assets.skip.dev/skipapp-bookings/city-settings-android.png) | Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-bookings/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-bookings/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-bookings/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-bookings/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the HelloSkipApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # Fireside (Fuse) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-fireside-fuse/#_top) Fireside (Fuse) =============== This is a Skip Fuse dual-platform app project demonstrating the use of Firebase Firestore and Firebase Messaging. It builds a native app for both iOS and Android. See the `skip-firebase` module [documentation](https://skip.dev/docs/modules/skip-firebase) for details on integrating Firebase into your cross-platform Skip projects. Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-fireside-fuse/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-fireside-fuse/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-fireside-fuse/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the FireSideFuse target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # Firebase | Skip [Skip to content](https://skip.dev/docs/modules/skip-firebase/#_top) Firebase ======== This package provides Firebase support for Skip Swift projects. The Swift side uses the official Firebase iOS SDK directly, with the various `SkipFirebase*` modules passing the transpiled calls through to the Firebase Android SDK. For an example of using Firebase in a [Skip Fuse](https://skip.dev/docs/status/#skip_fuse) app, see the [FiresideFuse Sample](https://skip.dev/docs/samples/skipapp-fireside-fuse) . For a [Skip Lite](https://skip.dev/docs/status/#skip_fuse) app, see the [Fireside Sample](https://skip.dev/docs/samples/skipapp-fireside) . Package ------- [Section titled “Package”](https://skip.dev/docs/modules/skip-firebase/#package) The modules in the SkipFirebase framework project mirror the division of the SwiftPM modules in the Firebase iOS SDK (at [https://github.com/firebase/firebase-ios-sdk.git ↗](https://github.com/firebase/firebase-ios-sdk.git) ), which is also mirrored in the division of the Firebase Kotlin Android gradle modules (at [https://github.com/firebase/firebase-android-sdk.git ↗](https://github.com/firebase/firebase-android-sdk.git) ). See the `Package.swift` files in the [FiresideFuse](https://skip.dev/docs/samples/skipapp-fireside-fuse) and [Fireside](https://skip.dev/docs/samples/skipapp-fireside) apps for examples of integrating Firebase dependencies. Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-firebase/#setup) For a Skip app, the simplest way to setup Firebase support is to create a Firebase project at [https://console.firebase.google.com/project ↗](https://console.firebase.google.com/project) . Follow the Firebase setup instructions to obtain the `GoogleService-Info.plist` and `google-services.json` files and add them to the iOS and Android sides of the project: * The `GoogleService-Info.plist` file should be placed in the `Darwin/` folder of the Skip project * The `google-services.json` file should be placed in the `Android/app/` folder of the Skip project In addition, the `com.google.gms.google-services` plugin will need to be added to the Android app’s `Android/app/build.gradle.kts` file in order to process the `google-services.json` file for the app, like so: plugins { alias(libs.plugins.kotlin.android) alias(libs.plugins.android.application) id("skip-build-plugin") id("com.google.gms.google-services") version "4.4.4" apply true id("com.google.firebase.crashlytics") version "3.0.2" apply true # (if using Crashlytics)} For concrete examples, see the [FireSideFuse Sample](https://skip.dev/docs/samples/skipapp-fireside-fuse) project. {: class=“callout info”} Once Firebase has been added to your project, you need to configure the `FirebaseApp` on app startup. This is typically done in the `onInit()` callback of the `*AppDelegate` in your `*App.swift` file. Here is a snippet from the FireSideFuse sample app: #if os(Android)import SkipFirebaseCore#elseimport FirebaseCore#endif ... /* SKIP @bridge */public final class FireSideFuseAppDelegate : Sendable { /* SKIP @bridge */public static let shared = FireSideFuseAppDelegate() ... /* SKIP @bridge */public func onInit() { logger.debug("onInit") FirebaseApp.configure() ... } ...} After configuring the `FirebaseApp`, you will be able to access the singleton type for each of the imported Firebase modules. For example, the following actor uses the `Firestore` singleton: #if os(Android)import SkipFirebaseFirestore#elseimport FirebaseFirestore#endif ... public actor Model { /// The shared model singleton public static let shared = Model() private let firestore: Firestore private init() { self.firestore = Firestore.firestore() } public func queryData() async throws -> [DataModel] { ... } public func saveData(model: DataModel) async throws { ... } ...} Messaging --------- [Section titled “Messaging”](https://skip.dev/docs/modules/skip-firebase/#messaging) After [setting up](https://skip.dev/docs/modules/skip-firebase/#setup) your app to use Firebase, enabling push notifications via Firebase Cloud Messaging (FCM) requires a number of additional steps. 1. Follow Firebase’s [instructions ↗](https://firebase.google.com/docs/cloud-messaging/ios/client) for creating and uploading your Apple Push Notification Service (APNS) key. 2. Use Xcode to [add the Push capability ↗](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app/) to your iOS app. 3. Add Skip’s Firebase messaging service and default messaging channel to `Android/app/src/main/AndroidManifest.xml`: ... ... 4. Consider increasing the `minSdk` version of your Android app. Prior to SDK 33, Android does not provide any control over asking the user for push notification permissions. Rather, the system will prompt the user for permission only after receiving a notification and opening the app. Increasing your `minSdk` will allow you to decide when to request notification permissions. To do so, edit your `Android/app/build.gradle.kts` file and change the `minSdk` value to 33. 5. Define a delegate to receive notification callbacks. In keeping with Skip’s philosophy of _transparent adoption_, both the iOS and Android sides of your app will receive callbacks via iOS’s standard `UNUserNotificationCenterDelegate` API, as well as the Firebase iOS SDK’s `MessagingDelegate`. Here are example [Skip Fuse](https://skip.dev/docs/status/#skip_fuse) delegate implementations that works across both platforms: import SwiftFuseUI#if os(Android)import SkipFirebaseMessaging#elseimport FirebaseMessaging#endif final class NotificationDelegate : NSObject, UNUserNotificationCenterDelegate, Sendable { public func requestPermission() { let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] Task { @MainActor in do { if try await UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { logger.info("notification permission granted") } else { logger.info("notification permission denied") } } catch { logger.error("notification permission error: \(error)") } } } public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { let content = notification.request.content logger.info("willPresentNotification: \(content.title): \(content.body) \(content.userInfo)") return [.banner, .sound] } public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { let content = response.notification.request.content logger.info("didReceiveNotification: \(content.title): \(content.body) \(content.userInfo)") #if os(Android) || !os(macOS) // Example of using a deep_link key passed in the notification to route to the app's `onOpenURL` handler if let deepLink = response.notification.request.content.userInfo["deep_link"] as? String, let url = URL(string: deepLink) { Task { @MainActor in await UIApplication.shared.open(url) } } #endif }} // Your Firebase MessageDelegate must bridge because we use the Firebase Kotlin API on Android./* SKIP @bridge */final class MessageDelegate : NSObject, MessagingDelegate, Sendable { /* SKIP @bridge */public func messaging(_ messaging: Messaging, didReceiveRegistrationToken token: String?) { logger.info("didReceiveRegistrationToken: \(token ?? "nil")") }} 1. Wire everything up. This includes assigning your shared delegate, registering for remote notifications, and other necessary steps. Below we build on our [previous Firebase setup code](https://skip.dev/docs/modules/skip-firebase/#setup) to perform these actions. This is taken from our FireSideFuse sample app: #if os(Android)import SkipFirebaseCore#elseimport FirebaseCore#endif ... /* SKIP @bridge */public final class FireSideFuseAppDelegate : Sendable { /* SKIP @bridge */public static let shared = FireSideFuseAppDelegate() private let notificationDelegate = NotificationDelegate() private let messageDelegate = MessageDelegate() private init() { } /* SKIP @bridge */public func onInit() { logger.debug("onInit") // Configure Firebase and notifications FirebaseApp.configure() Messaging.messaging().delegate = messageDelegate UNUserNotificationCenter.current().delegate = notificationDelegate } /* SKIP @bridge */public func onLaunch() { logger.debug("onLaunch") // Ask for permissions at a time appropriate for your app notificationDelegate.requestPermission() } ...} ... class AppMainDelegate: NSObject, AppMainDelegateBase { ... func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { AppDelegate.shared.onLaunch() application.registerForRemoteNotifications() // <-- Insert return true } ...} ... open class MainActivity: AppCompatActivity { ... override fun onCreate(savedInstanceState: android.os.Bundle?) { ... setContent { ... } skip.firebase.messaging.Messaging.messaging().onActivityCreated(this) // <-- Insert FireSideFuseAppDelegate.shared.onLaunch() ... } ...} 1. See Firebase’s [iOS instructions ↗](https://firebase.google.com/docs/cloud-messaging/ios/client) and [Android instructions ↗](https://firebase.google.com/docs/cloud-messaging/android/client) for additional details and options, including how to send test messages to your apps! The [FiresideFuse](https://skip.dev/docs/samples/skipapp-fireside-fuse) and [Fireside](https://skip.dev/docs/samples/skipapp-fireside) projects are great references for seeing complete, working Skip Fuse and Skip Lite apps using Firebase push notifications. {: class=“callout info”} Error handling -------------- [Section titled “Error handling”](https://skip.dev/docs/modules/skip-firebase/#error-handling) ### Firestore [Section titled “Firestore”](https://skip.dev/docs/modules/skip-firebase/#firestore) The Firestore API converts `com.google.firebase.firestore.FirebaseFirestoreException` to NSError so you can handle errors the same way on both platforms: do { try await Firestore.firestore().collection("foo").document("bar").updateData(...)} catch let error as NSError { if error.domain == FirestoreErrorDomain && error.code == FirestoreErrorCode.notFound.rawValue { ... }} ### Catching other errors [Section titled “Catching other errors”](https://skip.dev/docs/modules/skip-firebase/#catching-other-errors) Other parts of this library have not been updated to this unified error handling. Instead, you can access the underlying Kotlin exceptions in SKIP blocks according to the documentation: * FirebaseAuth: [https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseAuth ↗](https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseAuth) * FirebaseMessaging.MessagingService.onSendError: [https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/SendException ↗](https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/SendException) * FirebaseStorage: [https://firebase.google.com/docs/reference/android/com/google/firebase/storage/StorageException ↗](https://firebase.google.com/docs/reference/android/com/google/firebase/storage/StorageException) do { try await Storage.storage().reference().child("nonexistent").delete()} catch { #if !SKIP let error = error as NSError let errorCode = error.domain == StorageError.errorDomain ? error.code : nil #else let exception = (error as Exception).cause as? com.google.firebase.storage.StorageException let errorCode = exception?.code.value() #endif if errorCode == -13010 { // Object not found }} Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-firebase/#testing) For unit testing, where there isn’t a standard place to store the `GoogleService-Info.plist` and `google-services.json` configuration files, you can create an configure the app using the `SkipFirebaseCore.FirebaseApp` API manually from the information provided from the Firebase console, like so: import SkipFirebaseCoreimport SkipFirebaseAuthimport SkipFirebaseStorageimport SkipFirebaseDatabaseimport SkipFirebaseAppCheckimport SkipFirebaseFunctionsimport SkipFirebaseFirestoreimport SkipFirebaseMessagingimport SkipFirebaseCrashlyticsimport SkipFirebaseRemoteConfigimport SkipFirebaseInstallations let appName = "myapp"let options = FirebaseOptions(googleAppID: "1:GCM:ios:HASH", gcmSenderID: "GCM")options.projectID = "some-firebase-projectid"options.storageBucket = "some-firebase-demo.appspot.com"options.apiKey = "some-api-key" FirebaseApp.configure(name: appName, options: options)guard let app = FirebaseApp.app(name: appName) else { fatalError("Cannot load Firebase config")} // customize the app hereapp.isDataCollectionDefaultEnabled = false // use the app to create and test serviceslet auth = Auth.auth(app: app)let storage = Storage.storage(app: app)let database = Database.database(app: app)let appcheck = AppCheck.appCheck(app: app)let functions = Functions.functions(app: app)let firestore = Firestore.firestore(app: app)let crashlytics = Crashlytics.crashlytics(app: app)let remoteconfig = RemoteConfig.remoteConfig(app: app)let installations = Installations.installations(app: app) Common Errors ------------- [Section titled “Common Errors”](https://skip.dev/docs/modules/skip-firebase/#common-errors) Error in adb logcat: FirebaseApp: Default FirebaseApp failed to initialize because no default options were found.This usually means that com.google.gms:google-services was not applied to your gradle project. The app’s `com.google.gms:google-services` plugin must be applied to the `build.gradle.kts` file for the app’s target. --- # Notes (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-notes/#_top) Notes (Lite) ============ Skip notes is a Skip Fuse dual-platform note taking app built using Skip and distributed through the [App Fair ↗](https://appfair.org/) . [![Download on the Google Play Store](https://appfair.org/assets/badges/google-play-store.svg)](https://play.google.com/store/apps/details?id=org.appfair.app.SkipNotes) [![Download on the Apple App Store](https://appfair.org/assets/badges/apple-app-store.svg)](https://apps.apple.com/us/app/skip-notes/id6740916318) iPhone Screenshots ------------------ [Section titled “iPhone Screenshots”](https://skip.dev/docs/samples/skipapp-notes/#iphone-screenshots) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-notes/main/Darwin/fastlane/screenshots/en-US/1_en-US.png) ![iPhone Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-notes/main/Darwin/fastlane/screenshots/en-US/2_en-US.png) Android Screenshots ------------------- [Section titled “Android Screenshots”](https://skip.dev/docs/samples/skipapp-notes/#android-screenshots) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-notes/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png) ![Android Screenshot](https://raw.githubusercontent.com/skiptools/skipapp-notes/main/Android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png) Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-notes/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-notes/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-notes/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-notes/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the SkipNotesApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # Border | Skip [Skip to content](https://skip.dev/docs/components/border/#_top) Border ====== Skip support for [SwiftUI.View.border ↗](https://developer.apple.com/documentation/swiftui/view/border(_:width:)) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`BorderPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/BorderPlayground.swift) ![Android screenshot for Border component (light mode)](http://assets.skip.dev/showcase/Border-android-light_framed.png) ![iPhone screenshot for Border component (light mode)](http://assets.skip.dev/showcase/Border-iphone-light_framed.png) ![iPhone screenshot for Border component (dark mode)](http://assets.skip.dev/showcase/Border-iphone-dark_framed.png) ![Android screenshot for Border component (dark mode)](http://assets.skip.dev/showcase/Border-android-dark_framed.png) import SwiftUI struct BorderPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text(".border") Spacer() Color.red .frame(width: 100.0, height: 100.0) .border(.primary) } HStack { Text(".padding()") Spacer() Color.red .frame(width: 100.0, height: 100.0) .padding() .border(.primary) } HStack { Text(".padding([.top, .leading])") Spacer() Color.red .frame(width: 100.0, height: 100.0) .padding([.top, .leading], 32.0) .border(.primary) } HStack { Text(".blue, 5.0") Spacer() Color.red .frame(width: 100.0, height: 100.0) .border(.blue, width: 5.0) } HStack { Text(".blue.gradient, 10.0") Spacer() Color.red .frame(width: 100.0, height: 100.0) .border(.blue.gradient, width: 10.0) } HStack { Text("VStack") Spacer() VStack { Color.red .frame(width: 100.0, height: 100.0) .padding() Color.red .frame(width: 100.0, height: 100.0) .padding() } .border(.primary) } } .padding() } .toolbar { PlaygroundSourceLink(file: "BorderPlayground.swift") } }} --- # Scrumskipper (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-scrumskipper/#_top) Scrumskipper (Lite) =================== Scrumskipper is a Skip Lite dual-platform app project using the source from [Scrumdinger ↗](https://developer.apple.com/tutorials/app-dev-training/getting-started-with-scrumdinger) , Apple’s canonical SwiftUI sample app. Scrumskipper builds a native app for both iOS and Android. Read more about this sample on the [Skip blog](https://skip.dev/blog/scrumskipper/) . Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-scrumskipper/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip Lite be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-scrumskipper/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-scrumskipper/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-scrumskipper/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the ScrumskipperApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # Color | Skip [Skip to content](https://skip.dev/docs/components/color/#_top) Color ===== Skip support for [SwiftUI.Color ↗](https://developer.apple.com/documentation/swiftui/color) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ColorPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ColorPlayground.swift) ![Android screenshot for Color component (light mode)](http://assets.skip.dev/showcase/Color-android-light_framed.png) ![iPhone screenshot for Color component (light mode)](http://assets.skip.dev/showcase/Color-iphone-light_framed.png) ![iPhone screenshot for Color component (dark mode)](http://assets.skip.dev/showcase/Color-iphone-dark_framed.png) ![Android screenshot for Color component (dark mode)](http://assets.skip.dev/showcase/Color-android-dark_framed.png) import SwiftUI struct ColorPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { colorRow(label: Text("Red"), color: .red) colorRow(label: Text("Red, .opacity(0.5)"), color: Color.red) colorRow(label: Text("RGB"), color: Color(red: 1.0, green: 0.0, blue: 0.0)) colorRow(label: Text("White, Opacity"), color: Color(white: 0.5, opacity: 1.0)) colorRow(label: Text("Accent color"), color: .accentColor) colorRow(label: Text("Red"), color: .red) colorRow(label: Text("Orange"), color: .orange) colorRow(label: Text("Yellow"), color: .yellow) colorRow(label: Text("Green"), color: .green) colorRow(label: Text("Mint"), color: .mint) colorRow(label: Text("Teal"), color: .teal) colorRow(label: Text("Cyan"), color: .cyan) colorRow(label: Text("Blue"), color: .blue) colorRow(label: Text("Indigo"), color: .indigo) colorRow(label: Text("Purple"), color: .purple) colorRow(label: Text("Pink"), color: .pink) colorRow(label: Text("Brown"), color: .brown) colorRow(label: Text("White"), color: .white) colorRow(label: Text("Gray"), color: .gray) colorRow(label: Text("Black"), color: .black) colorRow(label: Text("Clear"), color: .clear) colorRow(label: Text("Primary"), color: .primary) colorRow(label: Text("Secondary"), color: .secondary) } .padding() } .toolbar { PlaygroundSourceLink(file: "ColorPlayground.swift") } } func colorRow(label: Text, color: Color) -> some View { HStack { label Spacer() color .frame(width: 100.0, height: 100.0) } }} --- # Button | Skip [Skip to content](https://skip.dev/docs/components/button/#_top) Button ====== Skip support for [SwiftUI.Button ↗](https://developer.apple.com/documentation/swiftui/button) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ButtonPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ButtonPlayground.swift) ![Android screenshot for Button component (light mode)](http://assets.skip.dev/showcase/Button-android-light_framed.png) ![iPhone screenshot for Button component (light mode)](http://assets.skip.dev/showcase/Button-iphone-light_framed.png) ![iPhone screenshot for Button component (dark mode)](http://assets.skip.dev/showcase/Button-iphone-dark_framed.png) ![Android screenshot for Button component (dark mode)](http://assets.skip.dev/showcase/Button-android-dark_framed.png) import SwiftUI struct ButtonPlayground: View { @State var tapCount = 0 var body: some View { ScrollView { VStack(spacing: 16.0) { Button(action: { tapCount += 1 }) { Text(".init(action:label:): \(tapCount)") } Button(".init(_ label:action:): \(tapCount)") { tapCount += 1 } Button(".destructive: \(tapCount)", role: .destructive) { tapCount += 1 } Button(".plain: \(tapCount)") { tapCount += 1 } .buttonStyle(.plain) Button(".bordered: \(tapCount)") { tapCount += 1 } .buttonStyle(.bordered) Button(".borderedProminent: \(tapCount)") { tapCount += 1 } .buttonStyle(.borderedProminent) Button(".borderedProminent, .destructive: \(tapCount)", role: .destructive) { tapCount += 1 } .buttonStyle(.borderedProminent) Button(".disabled(true): \(tapCount)") { tapCount += 1 } .disabled(true) Button(".plain, .disabled(true): \(tapCount)") { tapCount += 1 } .buttonStyle(.plain) .disabled(true) Button(".bordered, .disabled(true): \(tapCount)") { tapCount += 1 } .buttonStyle(.bordered) .disabled(true) Button(".borderedProminent, .disabled(true): \(tapCount)") { tapCount += 1 } .buttonStyle(.borderedProminent) .disabled(true) Button { tapCount += 1 } label: { Label("Label", systemImage: "star") } Button(".foregroundStyle(.red): \(tapCount)") { tapCount += 1 } .foregroundStyle(.red) Button(".foregroundStyle(.red), .disabled(true): \(tapCount)") { tapCount += 1 } .foregroundStyle(.red).disabled(true) Button(".tint(.red): \(tapCount)") { tapCount += 1 } .tint(.red) Button(".tint(.red), .disabled(true): \(tapCount)") { tapCount += 1 } .tint(.red).disabled(true) Button(".bordered, .foregroundStyle(.red): \(tapCount)") { tapCount += 1 } .buttonStyle(.bordered) .foregroundStyle(.red) Button(".bordered, .tint(.red): \(tapCount)") { tapCount += 1 } .buttonStyle(.bordered) .tint(.red) Button(".borderedProminent, .foregroundStyle(.red): \(tapCount)") { tapCount += 1 } .buttonStyle(.borderedProminent) .foregroundStyle(.red) Button(".borderedProminent, .tint(.red): \(tapCount)") { tapCount += 1 } .buttonStyle(.borderedProminent) .tint(.red) } .padding() } .toolbar { PlaygroundSourceLink(file: "ButtonPlayground.swift") } }} --- # Alert | Skip [Skip to content](https://skip.dev/docs/components/alert/#_top) Alert ===== Skip support for [SwiftUI.Alert ↗](https://developer.apple.com/documentation/swiftui/alert) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`AlertPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/AlertPlayground.swift) import SwiftUI struct AlertPlayground: View { @State var value = "" @State var error: AlertPlaygroundError? = nil @State var data: Int? = nil @State var titleIsPresented = false @State var titleMessageIsPresented = false @State var twoButtonsIsPresented = false @State var threeButtonsIsPresented = false @State var fiveButtonsIsPresented = false @State var dataIsPresented = false var body: some View { VStack(spacing: 16) { Text(value).bold() Group { Button("Title") { titleIsPresented = true } Button("Title + Message") { titleMessageIsPresented = true } Button("Two Buttons") { twoButtonsIsPresented = true } Button("Three Buttons") { threeButtonsIsPresented = true } Button("Five Buttons") { fiveButtonsIsPresented = true } } Divider() Group { Text("Present with data") Button("Data: \(String(describing: data))") { if data == nil { data = 1 } else { data = data! + 1 } } Button("Nil data") { data = nil } Button("Present") { dataIsPresented = true } } } .padding() .toolbar { PlaygroundSourceLink(file: "AlertPlayground.swift") } .alert("Title", isPresented: $titleIsPresented) { } .alert("Title + Message", isPresented: $titleMessageIsPresented) { } message: { Text("This is the alert message to show beneath the title") } .alert("Two Buttons", isPresented: $twoButtonsIsPresented) { Button("Option") { value = "Option" } Button("Cancel", role: .cancel) { value = "Custom Cancel" } } .alert("Three Buttons", isPresented: $threeButtonsIsPresented) { Button("Cancel", role: .cancel) { value = "Custom Cancel" } Button("Option") { value = "Option" } Button("Destructive", role: .destructive) { value = "Destructive" } } .alert("Five Buttons", isPresented: $fiveButtonsIsPresented) { Button("Cancel", role: .cancel) { value = "Custom Cancel" } Button("Destructive", role: .destructive) { value = "Destructive" } Button("Option 1") { value = "Option 1" } Button("Option 2") { value = "Option 2" } Button("Option 3") { value = "Option 3" } } .alert("Data", isPresented: $dataIsPresented, presenting: data) { d in Button("Data: \(d)") { value = "\(d)" } Button("Nil Data", role: .destructive) { data = nil } } }} enum AlertPlaygroundError: LocalizedError { case testError} --- # Data Bake (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-databake/#_top) Data Bake (Lite) ================ This app demonstrates using a local database in a Skip Lite app using the SkipSQL package to update an embedded database. ![Screenshot](https://assets.skip.dev/skipapp-databake-dev-screenshot.png) Architecture ------------ [Section titled “Architecture”](https://skip.dev/docs/samples/skipapp-databake/#architecture) This sample app build on the [SkipSQL ↗](http://skip.dev/docs/modules/skip-sql) package to provide an embedded database and a domain-specific model layer (“DataBakeModel”) that provides an interface for storing and retrieving instance of a `DataItem` from rows in the database. Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-databake/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-databake/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-databake/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-databake/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the DataBakeApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. Project ------- [Section titled “Project”](https://skip.dev/docs/samples/skipapp-databake/#project) This project was initialized with the [skip ↗](https://skip.dev/) command: skip init --appid=skip.data.bake.App --icon-color=34AADC --free skipapp-databake DataBake DataBakeModel --- # Fireside (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-fireside/#_top) Fireside (Lite) =============== This is a Skip Lite dual-platform app project. It creates a native app for both iOS and Android. This app shows Skip Lite’s integration with the Firebase backend cloud computing services using the official native Firebase SDKs for iOS and Android. It utilizes the [Skip Firebase](https://skip.dev/docs/modules/skip-firebase) framework. Your browser does not support the video tag. Quickstart ---------- [Section titled “Quickstart”](https://skip.dev/docs/samples/skipapp-fireside/#quickstart) This repository contains an Xcode project with a SwiftUI app that uses the Skip plugin to transpile the app into Kotlin then build and launch it on Android. To get started: 1. Install skip (requires macOS 13+ with [Homebrew ↗](https://brew.sh/) , [Xcode ↗](https://developer.apple.com/xcode/) , and [Android Studio ↗](https://developer.android.com/studio) ): $ brew install skiptools/skip/skip 2. Configure and launch an Android emulator from the [Android Studio device manager ↗](https://developer.android.com/studio/run/emulator-launch-without-app) , or by launching a pre-existing emulator: $ ~/Library/Android/sdk/emulator/emulator @Pixel_6_API_30 3. Download this [repository as a zip file ↗](https://source.skip.dev/skipapp-fireside/archive/main.zip) and unzip it, or clone the repository: $ git clone https://source.skip.dev/skipapp-fireside.git 4. Open the Xcode project in the _Darwin_ folder: $ open skipapp-fireside/Darwin/FireSide.xcodeproj 5. Select and Run the `FireSide` target with an iOS simulator destination; the app will build and run side-by-side on the iOS simulator and Android emulator. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-fireside/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-fireside/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-fireside/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the FireSideApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # DatePicker | Skip [Skip to content](https://skip.dev/docs/components/datepicker/#_top) DatePicker ========== Skip support for [SwiftUI.DatePicker ↗](https://developer.apple.com/documentation/swiftui/datepicker) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`DatePickerPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/DatePickerPlayground.swift) ![Android screenshot for DatePicker component (light mode)](http://assets.skip.dev/showcase/DatePicker-android-light_framed.png) ![iPhone screenshot for DatePicker component (light mode)](http://assets.skip.dev/showcase/DatePicker-iphone-light_framed.png) ![iPhone screenshot for DatePicker component (dark mode)](http://assets.skip.dev/showcase/DatePicker-iphone-dark_framed.png) ![Android screenshot for DatePicker component (dark mode)](http://assets.skip.dev/showcase/DatePicker-android-dark_framed.png) import SwiftUI struct DatePickerPlayground: View { @State var selectedDate = Date.now var body: some View { ScrollView { VStack(spacing: 16.0) { DatePicker(selection: $selectedDate) { Text("Viewbuilder init") } DatePicker("String init", selection: $selectedDate) VStack { Text(".labelsHidden():") DatePicker("Label", selection: $selectedDate) } .labelsHidden() DatePicker(".buttonStyle(.plain)", selection: $selectedDate) .buttonStyle(.plain) DatePicker(".disabled(true)", selection: $selectedDate) .disabled(true) DatePicker(".foregroundStyle(.red)", selection: $selectedDate) .foregroundStyle(.red) DatePicker(".tint(.red)", selection: $selectedDate) .tint(.red) DatePicker("Date only", selection: $selectedDate, displayedComponents: .date) DatePicker("Time only", selection: $selectedDate, displayedComponents: .hourAndMinute) } .padding() } .toolbar { PlaygroundSourceLink(file: "DatePickerPlayground.swift") } }} --- # Divider | Skip [Skip to content](https://skip.dev/docs/components/divider/#_top) Divider ======= Skip support for [SwiftUI.Divider ↗](https://developer.apple.com/documentation/swiftui/divider) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`DividerPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/DividerPlayground.swift) ![Android screenshot for Divider component (light mode)](http://assets.skip.dev/showcase/Divider-android-light_framed.png) ![iPhone screenshot for Divider component (light mode)](http://assets.skip.dev/showcase/Divider-iphone-light_framed.png) ![iPhone screenshot for Divider component (dark mode)](http://assets.skip.dev/showcase/Divider-iphone-dark_framed.png) ![Android screenshot for Divider component (dark mode)](http://assets.skip.dev/showcase/Divider-android-dark_framed.png) import SwiftUI struct DividerPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { VStack { Text("Default") Divider() } VStack { Text("Fixed width") Divider() .frame(width: 100.0) } HStack { Text("Vertical") .padding() Divider() } .frame(height: 100.0) HStack { Text("Vertical fixed height") .padding() Divider() .frame(height: 50.0) } .frame(height: 100.0) VStack { Text(".foregroundStyle(.red)") Divider() .foregroundStyle(.red) } VStack { Text(".tint(.red)") Divider() .tint(.red) } Text("Note: colors should not affect Divider appearance") .font(.caption) } .padding() } .toolbar { PlaygroundSourceLink(file: "DividerPlayground.swift") } }} --- # ColorScheme | Skip [Skip to content](https://skip.dev/docs/components/colorscheme/#_top) ColorScheme =========== Skip support for [SwiftUI.ColorScheme ↗](https://developer.apple.com/documentation/swiftui/colorscheme) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ColorSchemePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ColorSchemePlayground.swift) import SwiftUI struct ColorSchemePlayground: View { @State var isDarkMode = false @State var preferredColorScheme = "" @State var isPresented = false var body: some View { VStack(spacing: 16) { VStack { Text("This text is in \(isDarkMode ? "Dark" : "Light") mode") .font(.largeTitle) Button("Toggle") { isDarkMode = !isDarkMode } .buttonStyle(.borderedProminent) } .padding(32) .background() .padding(32) .colorScheme(isDarkMode ? .dark : .light) Picker(".preferredColorScheme", selection: $preferredColorScheme) { Text("System").tag("") Text("Light").tag("light") Text("Dark").tag("dark") } NavigationLink("Push") { Text("Pushed") } Button("Present") { isPresented = true } } .preferredColorScheme(namedColorScheme(for: preferredColorScheme)) .sheet(isPresented: $isPresented) { ColorSchemeSheetView() } .toolbar { PlaygroundSourceLink(file: "ColorSchemePlayground.swift") } }} private struct ColorSchemeSheetView: View { @Environment(\.dismiss) var dismiss @State var preferredColorScheme = "" var body: some View { NavigationStack { VStack(spacing: 16) { Picker(".preferredColorScheme", selection: $preferredColorScheme) { Text("System").tag("") Text("Light").tag("light") Text("Dark").tag("dark") } NavigationLink("Push") { Text("Pushed") } } .preferredColorScheme(namedColorScheme(for: preferredColorScheme)) .navigationTitle("Sheet") .toolbar { Button("Dismiss") { dismiss() } } } }} private func namedColorScheme(for string: String) -> ColorScheme? { switch string { case "light": return .light case "dark": return .dark default: return nil }} --- # Background | Skip [Skip to content](https://skip.dev/docs/components/background/#_top) Background ========== Skip support for [SwiftUI.View.background ↗](https://developer.apple.com/documentation/swiftui/adding-a-background-to-your-view) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`BackgroundPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/BackgroundPlayground.swift) ![Android screenshot for Background component (light mode)](http://assets.skip.dev/showcase/Background-android-light_framed.png) ![iPhone screenshot for Background component (light mode)](http://assets.skip.dev/showcase/Background-iphone-light_framed.png) ![iPhone screenshot for Background component (dark mode)](http://assets.skip.dev/showcase/Background-iphone-dark_framed.png) ![Android screenshot for Background component (dark mode)](http://assets.skip.dev/showcase/Background-android-dark_framed.png) import SwiftUI struct BackgroundPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text(".red") Spacer() ZStack { } .frame(width: 100.0, height: 100.0) .background(.red) } HStack { Text(".red.gradient") Spacer() ZStack { } .frame(width: 100.0, height: 100.0) .background(.red.gradient) } HStack { Text("background()") Spacer() ZStack { Text("Hello") .background() } .frame(width: 100.0, height: 100.0) .background(.red) } HStack { Text(".backgroundStyle(.red)") Spacer() ZStack { Text("Hello") .background() } .frame(width: 100.0, height: 100.0) .backgroundStyle(.red) } HStack { Text(".clipShape(.capsule)") Spacer() Image(systemName: "heart.fill") .frame(width: 100.0, height: 50.0) .background(.red.gradient) .clipShape(.capsule) } HStack { Text("in: Capsule()") Spacer() Text("Hello") .padding() .background(.red.opacity(0.2), in: Capsule()) } HStack { Text("Circles") Spacer() Text("Hello") .padding() .background { HStack { Circle().fill(.red.opacity(0.2)) Circle().fill(.green.opacity(0.2)) } } .border(.blue) } HStack { Text("Large circle") Spacer() Text("Hello") .padding() .background { Circle() .fill(.red.opacity(0.2)) .frame(width: 100.0, height: 100.0) } .border(.blue) } HStack { Text(".clipped()") Spacer() Text("Hello") .padding() .background { HStack { Circle().fill(.red.opacity(0.2)) Circle().fill(.red.opacity(0.2)) } .frame(width: 200.0, height: 100.0) } .clipped() .border(.blue) } HStack { Text("Small circle") Spacer() Text("Hello") .padding() .background { Circle() .fill(.red.opacity(0.2)) .frame(width: 20.0, height: 20.0) } .border(.blue) } HStack { Text("alignment: .topLeading") Spacer() Text("Hello") .padding() .background(alignment: .topLeading) { Circle() .fill(.red.opacity(0.2)) .frame(width: 20.0, height: 20.0) } .border(.blue) } HStack { Text("alignment: .bottomTrailing") Spacer() Text("Hello") .padding() .background(alignment: .bottomTrailing) { Circle() .fill(.red.opacity(0.2)) .frame(width: 20.0, height: 20.0) } .border(.blue) } } .padding() } .toolbar { PlaygroundSourceLink(file: "BackgroundPlayground.swift") } }} --- # DisclosureGroup | Skip [Skip to content](https://skip.dev/docs/components/disclosuregroup/#_top) DisclosureGroup =============== Skip support for [SwiftUI.DisclosureGroup ↗](https://developer.apple.com/documentation/swiftui/disclosuregroup) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`DisclosureGroupPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/DisclosureGroupPlayground.swift) import SwiftUI struct DisclosureGroupPlayground: View { @State var expanded = false @State var nestedModel = DisclosureGroupPlaygroundModel(title: "Multi-Level", items: ["AAAA", "BBBB", "CCCC"], nested: [DisclosureGroupPlaygroundModel(title: "Nested", items: ["1111", "2222", "3333"])]) var body: some View { ScrollView { VStack(spacing: 16) { Button("Toggle Group") { withAnimation { expanded = !expanded } } .buttonStyle(.bordered) DisclosureGroup("DisclosureGroup", isExpanded: $expanded) { Text("AAAA") Text("BBBB") Text("CCCC") } DisclosureGroup(nestedModel.title, isExpanded: $nestedModel.isExpanded) { ForEach(nestedModel.items, id: \.self) { text in Text(text) } ForEach($nestedModel.nested, id: \.title) { $item in DisclosureGroup(item.title, isExpanded: $item.isExpanded) { ForEach(item.items, id: \.self) { text in Text(text) } } } } DisclosureGroup("Disabled", isExpanded: $expanded) { Text("AAAA") Text("BBBB") Text("CCCC") } .disabled(true) } .padding() } .toolbar { PlaygroundSourceLink(file: "DisclosureGroupPlayground.swift") } }} struct DisclosureGroupPlaygroundModel { var title = "" var isExpanded = false var items: [String] = [] var nested: [DisclosureGroupPlaygroundModel] = []} --- # Weather (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-weather/#_top) Weather (Lite) ============== The SkipApp weather is a demonstration of a useful Skip App that uses the Open-Meteo to show the weather in a location. This is that app that is demonstrated in the [Skip Tour](https://skip.dev/tour) video. ![Screenshot](https://assets.skip.dev/skipapp-weather-dev-screenshot.png) This app uses the [SkipDevice](https://skip.dev/docs/modules/skip-device) framework to access the current location. Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-weather/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation requirements can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-weather/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-weather/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-weather/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the HelloSkipApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. Package ------- [Section titled “Package”](https://skip.dev/docs/samples/skipapp-weather/#package) This package was initialized with the command: skip init --appid=skip.weather.App --icon-color="16a085" skipapp-weather SkipWeather SkipWeatherModel --- # Lottie (Lite) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-lottiedemo/#_top) Lottie (Lite) ============= This is a Skip Lite dual-platform app project. It builds a native app for both iOS and Android that renders an embedded Lottie JSON animation using the [SkipMotion](https://skip.dev/docs/modules/skip-motion) framework. Your browser does not support the video tag. Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-lottiedemo/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-lottiedemo/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-lottiedemo/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-lottiedemo/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the LottieDemoApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. Project Structure ----------------- [Section titled “Project Structure”](https://skip.dev/docs/samples/skipapp-lottiedemo/#project-structure) This project was initialized with the command: skip init --show-tree --icon-color='1abc9c' --appid=skip.lottie.demo.App --version 0.0.1 skipapp-lottiedemo LottieDemo:skip-motion/SkipMotion:skip-ui/SkipUI --- # WebView | Skip [Skip to content](https://skip.dev/docs/modules/skip-web/#_top) WebView ======= SkipWeb provides an embedded WebView for Skip Lite transpiled Swift. On iOS it uses a [WKWebView ↗](https://developer.apple.com/documentation/webkit/wkwebview) and on Android it uses an [android.webkit.WebView ↗](https://developer.android.com/develop/ui/views/layout/webapps/webview) . A simple example of using an embedded WebView with a static URL can be seen: import Foundationimport SwiftUIimport SkipWeb struct EmbeddedWebView : View { let url: URL var body: some View { WebView(url: url) }} Customization ------------- [Section titled “Customization”](https://skip.dev/docs/modules/skip-web/#customization) The [`WebView` ↗](https://source.skip.dev/skip-web/blob/main/Sources/SkipWeb/WebView.swift) is backed by a [`WebEngine` ↗](https://source.skip.dev/skip-web/blob/main/Sources/SkipWeb/WebEngine.swift) . It can be configured with a [`WebEngineConfiguration` ↗](https://source.skip.dev/skip-web/blob/main/Sources/SkipWeb/WebEngine.swift) instance. For example: import Foundationimport SwiftUIimport SkipWeb struct ConfigurableWebView : View { let navigator: WebViewNavigator = WebViewNavigator(initialURL: URL("https://skip.dev")!) @ObservedObject var configuration: WebEngineConfiguration @Binding var state: WebViewState var body: some View { WebView(configuration: configuration, navigator: navigator, state: $state) }} JavaScript ---------- [Section titled “JavaScript”](https://skip.dev/docs/modules/skip-web/#javascript) JavaScript can be executed against the browser with: let json: String? = try await navigator.evaluateJavaScript(javaScriptInput) The JSON string that is returned may be an object, or may be fragmentary (that is, a top-level string or number, null, or array), so care should be taken when attempting to deserialize it. **Note**: since the browser’s JavaScript engines are quite different (V8 and Blink on Android versus JavaScript Core and WebKit on iOS), resuts from script execution are expected to vary somewhat depending on the different quirks of the implementations. A full example of a browser that can evaluate JavaScript and display the results in a sheet can be implemented with the following View: import SwiftUIimport SkipWeb /// This component uses the `SkipWeb` module from https://source.skip.dev/skip-webstruct WebViewPlayground: View { @State var config = WebEngineConfiguration() @State var navigator = WebViewNavigator() @State var state = WebViewState() @State var showScriptSheet = false @State var javaScriptInput = "document.body.innerText" @State var javaScriptOutput = "" var body: some View { VStack { WebView(configuration: config, navigator: navigator, url: URL(string: "https://skip.dev")!, state: $state) } .toolbar { Button { navigator.goBack() } label: { Image(systemName: "arrow.left") } .disabled(!state.canGoBack) .accessibilityLabel(Text("Back")) Button { navigator.reload() } label: { Image(systemName: "arrow.clockwise.circle") } .accessibilityLabel(Text("Reload")) Button { navigator.goForward() } label: { Image(systemName: "arrow.forward") } .disabled(!state.canGoForward) .accessibilityLabel(Text("Forward")) Button { self.showScriptSheet = true } label: { Image(systemName: "ellipsis") } .accessibilityLabel(Text("Evaluate JavaScript")) } .navigationTitle(state.pageTitle ?? "WebView") .navigationBarTitleDisplayMode(.inline) .sheet(isPresented: $showScriptSheet) { NavigationStack { VStack { TextField("JavaScript", text: $javaScriptInput) .textFieldStyle(.roundedBorder) .autocorrectionDisabled() .keyboardType(.asciiCapable) // also disables smart quotes .textInputAutocapitalization(.never) .onSubmit(of: .text) { evaluateJavaScript() } .padding() Text("Output") .font(.headline) TextEditor(text: $javaScriptOutput) .font(Font.body.monospaced()) .border(Color.secondary) .padding() } .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Evaluate Script") { evaluateJavaScript() } .disabled(javaScriptInput.isEmpty) } ToolbarItem(placement: .cancellationAction) { Button("Close", role: .cancel) { showScriptSheet = false } } } } } } /// Evaluate the script specified in the sheet func evaluateJavaScript() { let navigator = self.navigator Task { var scriptResult: String = "" do { if let resultJSON = try await navigator.evaluateJavaScript(javaScriptInput) { // top-level fragments are nicer to display as strings, so we try to deserialize them if let topLevelString = try? JSONSerialization.jsonObject(with: resultJSON.data(using: .utf8)!, options: .fragmentsAllowed) as? String { scriptResult = topLevelString } else { scriptResult = resultJSON } } } catch { scriptResult = error.localizedDescription } Task { @MainActor in self.javaScriptOutput = scriptResult } } }} Contribution ------------ [Section titled “Contribution”](https://skip.dev/docs/modules/skip-web/#contribution) Many delegates that are provided by `WKWebView` are not yet implemented in this project, and so deeper customization may require custom implementation work. To implement these, you may need to fork the repository and add it to your workspace, as described in the [Contributing guide](https://skip.dev/docs/contributing/) . Please consider creating a [Pull Request ↗](https://source.skip.dev/skip-web/pulls) with features and fixes that you create, as this benefits the entire Skip community. Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-web/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-web/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-web/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes --- # Audio | Skip [Skip to content](https://skip.dev/docs/components/audio/#_top) Audio ===== Skip support for [AVAudioRecorder ↗](https://developer.apple.com/documentation/avfaudio/avaudiorecorder) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`AudioPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/AudioPlayground.swift) import SwiftUI#if os(macOS)#elseimport AVFoundation#endif struct AudioPlayground: View { #if os(macOS) #else @State var isRecording: Bool = false @State var errorMessage: String? = nil @State var audioRecorder: AVAudioRecorder? @State var audioPlayer: AVAudioPlayer? var captureURL: URL { get { #if SKIP let activity = UIApplication.shared.androidActivity let file = java.io.File(activity.filesDir, "recording.m4a") return URL(fileURLWithPath: file.absolutePath) #else return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask) .first!.appendingPathComponent("recording.m4a") #endif } } #endif var body: some View { #if os(macOS) Text("Not supported on macOS") #else return VStack(spacing: 20) { Button(action: { self.isRecording ? self.stopRecording() : self.startRecording() }) { Text(isRecording ? "Stop Recording" : "Start Recording") .fontWeight(.bold) .frame(minWidth: 0, maxWidth: .infinity) .padding() .foregroundColor(.white) .background(isRecording ? Color.red : Color.green) .cornerRadius(10) } Button(action: { try? self.playRecording() }) { Text("Play Recording") .fontWeight(.bold) .frame(minWidth: 0, maxWidth: .infinity) .padding() .foregroundColor(.white) .background(Color.blue) .cornerRadius(10) .shadow(radius: 5) } if let errorMessage = errorMessage { Text(errorMessage) .foregroundColor(.red) } } .padding() #if SKIP .onAppear { requestAudioRecordingPermission() } #endif #endif } #if os(macOS) #else func startRecording() { do { #if !SKIP setupAudioSession() #endif self.audioRecorder = try AVAudioRecorder(url: captureURL, settings: [AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 12000, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]) } catch { print(error.localizedDescription) } audioRecorder?.record() isRecording = true } func stopRecording() { isRecording = false audioRecorder?.stop() } func playRecording() throws { do { guard FileManager.default.fileExists(atPath: captureURL.path) else { errorMessage = "Recording file does not exist." return } audioPlayer = try AVAudioPlayer(contentsOf: captureURL) audioPlayer?.play() errorMessage = "" } catch { logger.error("Could not play audio: \(error.localizedDescription)") errorMessage = "Could not play audio: \(error.localizedDescription)" } } #if SKIP func requestAudioRecordingPermission() { let activity = UIApplication.shared.androidActivity let permissions = listOf(android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) androidx.core.app.ActivityCompat.requestPermissions(activity, permissions.toTypedArray(), 1) } #else func setupAudioSession() { let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playAndRecord, mode: .default) try session.setActive(true) } catch { errorMessage = "Failed to setup audio session: \(error.localizedDescription)" } } #endif #endif} --- # ConfirmationDialog | Skip [Skip to content](https://skip.dev/docs/components/confirmationdialog/#_top) ConfirmationDialog ================== Skip support for [SwiftUI.View.confirmationDialog ↗](https://developer.apple.com/documentation/swiftui/modal-presentations#getting-confirmation-for-an-action) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ConfirmationDialogPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ConfirmationDialogPlayground.swift) ![Android screenshot for ConfirmationDialog component (light mode)](http://assets.skip.dev/showcase/ConfirmationDialog-android-light_framed.png) ![iPhone screenshot for ConfirmationDialog component (light mode)](http://assets.skip.dev/showcase/ConfirmationDialog-iphone-light_framed.png) ![iPhone screenshot for ConfirmationDialog component (dark mode)](http://assets.skip.dev/showcase/ConfirmationDialog-iphone-dark_framed.png) ![Android screenshot for ConfirmationDialog component (dark mode)](http://assets.skip.dev/showcase/ConfirmationDialog-android-dark_framed.png) import SwiftUI struct ConfirmationDialogPlayground: View { @State var value = "" @State var data: Int? = nil @State var defaultIsPresented = false @State var titleIsPresented = false @State var titleMessageIsPresented = false @State var customCancelIsPresented = false @State var dataIsPresented = false @State var scrollingIsPresented = false var body: some View { VStack(spacing: 16.0) { Text(value).bold() Button("Default") { defaultIsPresented = true } Button("Title") { titleIsPresented = true } Button("Title + Message") { titleMessageIsPresented = true } Button("Custom Cancel") { customCancelIsPresented = true } Button("Scrolling") { scrollingIsPresented = true } Divider() Text("Present with data") Button("Data: \(String(describing: data))") { if data == nil { data = 1 } else { data = data! + 1 } } Button("Nil Data") { data = nil } Button("Present") { dataIsPresented = true } } .padding() .toolbar { PlaygroundSourceLink(file: "ConfirmationDialogPlayground.swift") } .confirmationDialog("Title", isPresented: $defaultIsPresented) { Button("Destructive", role: .destructive) { value = "Destructive" } Button("Option") { value = "Option" } } .confirmationDialog("Title", isPresented: $titleIsPresented, titleVisibility: .visible) { Button("Destructive", role: .destructive) { value = "Destructive" } Button("Option") { value = "Option" } } .confirmationDialog("Title", isPresented: $titleMessageIsPresented, titleVisibility: .visible) { Button("Destructive", role: .destructive) { value = "Destructive" } Button("Option") { value = "Option" } } message: { Text("This is the message") } .confirmationDialog("Title", isPresented: $customCancelIsPresented) { Button("Custom Cancel", role: .cancel) { value = "Custom Cancel" } Button("Destructive", role: .destructive) { value = "Destructive" } Button("Option") { value = "Option" } } .confirmationDialog("Title", isPresented: $scrollingIsPresented) { Button("Destructive", role: .destructive) { value = "Destructive" } ForEach(0..<20) { i in Button("Option \(i)") { value = "Option \(i)" } } } .confirmationDialog("Title", isPresented: $dataIsPresented, presenting: data) { d in Button("Data: \(d)") { value = "\(d)" } Button("Nil Data", role: .destructive) { data = nil } } }} --- # Label | Skip [Skip to content](https://skip.dev/docs/components/label/#_top) Label ===== Skip support for [SwiftUI.Label ↗](https://developer.apple.com/documentation/swiftui/label) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`LabelPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/LabelPlayground.swift) ![Android screenshot for Label component (light mode)](http://assets.skip.dev/showcase/Label-android-light_framed.png) ![iPhone screenshot for Label component (light mode)](http://assets.skip.dev/showcase/Label-iphone-light_framed.png) ![iPhone screenshot for Label component (dark mode)](http://assets.skip.dev/showcase/Label-iphone-dark_framed.png) ![Android screenshot for Label component (dark mode)](http://assets.skip.dev/showcase/Label-android-dark_framed.png) import SwiftUI struct LabelPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { Label { Text(".init(_:icon:)") } icon: { Image(systemName: "star.fill") } Label(".init(_:systemImage:)", systemImage: "star.fill") Label(".font(.title)", systemImage: "star.fill") .font(.title) Label(".foregroundStyle(.red)", systemImage: "star.fill") .foregroundStyle(.red) Label(".tint(.red)", systemImage: "star.fill") .tint(.red) Text("Note: tint should not affect Label appearance") .font(.caption) } .padding() } .toolbar { PlaygroundSourceLink(file: "LabelPlayground.swift") } }} --- # Gradient | Skip [Skip to content](https://skip.dev/docs/components/gradient/#_top) Gradient ======== Skip support for [SwiftUI.Gradient ↗](https://developer.apple.com/documentation/swiftui/gradient) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`GradientPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/GradientPlayground.swift) ![Android screenshot for Gradient component (light mode)](http://assets.skip.dev/showcase/Gradient-android-light_framed.png) ![iPhone screenshot for Gradient component (light mode)](http://assets.skip.dev/showcase/Gradient-iphone-light_framed.png) ![iPhone screenshot for Gradient component (dark mode)](http://assets.skip.dev/showcase/Gradient-iphone-dark_framed.png) ![Android screenshot for Gradient component (dark mode)](http://assets.skip.dev/showcase/Gradient-android-dark_framed.png) import SwiftUI struct GradientPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text(".red.gradient") Spacer() Rectangle() .fill(.red.gradient) .frame(width: 100.0, height: 100.0) } HStack { Text("EllipitcalGradient") Spacer() EllipticalGradient(colors: [.red, .blue], center: UnitPoint(x: 0.5, y: 0.5), startRadiusFraction: 0.25) .frame(width: 50.0, height: 100.0) } HStack { Text("LinearGradient") Spacer() LinearGradient(colors: [.red, .blue], startPoint: UnitPoint(x: 0.0, y: 0.0), endPoint: UnitPoint(x: 1.0, y: 1.0)) .frame(width: 100.0, height: 100.0) } HStack { Text("RadialGradient") Spacer() RadialGradient(colors: [.red, .blue], center: UnitPoint(x: 0.5, y: 0.5), startRadius: 25.0, endRadius: 50.0) .frame(width: 100.0, height: 100.0) } } .padding() } .toolbar { PlaygroundSourceLink(file: "GradientPlayground.swift") } }} --- # GeometryReader | Skip [Skip to content](https://skip.dev/docs/components/geometryreader/#_top) GeometryReader ============== Skip support for [SwiftUI.GeometryReader ↗](https://developer.apple.com/documentation/swiftui/geometryreader) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`GeometryReaderPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/GeometryReaderPlayground.swift) import SwiftUI struct GeometryReaderPlayground: View { var body: some View { ScrollView { VStack(spacing: 16) { Text("Size").bold() GeometryReader { proxy in HStack(spacing: 0) { Text("1/3") .font(.largeTitle) .frame(width: proxy.size.width * 0.33) .background(.blue) Text("2/3") .font(.largeTitle) .frame(width: proxy.size.width * 0.67) .background(.red) } } .frame(height: 50) Text("Frame").bold() GeometryReader { proxy in VStack { Text("Local frame: \(string(for: proxy.frame(in: .local)))") Text("Global frame: \(string(for: proxy.frame(in: .global)))") } } .background(.yellow) .padding() .border(.blue) .frame(height: 100) } .padding() } .toolbar { PlaygroundSourceLink(file: "GeometryReaderPlayground.swift") } } private func string(for rect: CGRect) -> String { return "(\(Int(rect.minX)), \(Int(rect.minY)), \(Int(rect.width)), \(Int(rect.height)))" }} --- # Form | Skip [Skip to content](https://skip.dev/docs/components/form/#_top) Form ==== Skip support for [SwiftUI.Form ↗](https://developer.apple.com/documentation/swiftui/form) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`FormPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/FormPlayground.swift) ![Android screenshot for Form component (light mode)](http://assets.skip.dev/showcase/Form-android-light_framed.png) ![iPhone screenshot for Form component (light mode)](http://assets.skip.dev/showcase/Form-iphone-light_framed.png) ![iPhone screenshot for Form component (dark mode)](http://assets.skip.dev/showcase/Form-iphone-dark_framed.png) ![Android screenshot for Form component (dark mode)](http://assets.skip.dev/showcase/Form-android-dark_framed.png) import SwiftUI struct FormPlayground: View { @State var stringValue = "" @State var boolValue = false @State var dateValue = Date.now @State var pickerValue = "Two" let pickerValues = ["One", "Two", "Three"] var body: some View { List { Label("Label", systemImage: "star.fill") Label("Label .font(.title)", systemImage: "star.fill") .font(.title) Label("Label .foregroundStyle(.red)", systemImage: "star.fill") .foregroundStyle(.red) Label("Label .tint(.red)", systemImage: "star.fill") .tint(.red) Label("Label .listItemTint(.red)", systemImage: "star.fill") .listItemTint(.red) NavigationLink(value: "Test") { Label("Label in NavigationLink", systemImage: "star.fill") } Button("Button .automatic", action: { logger.info("Tap") }) Button("Button .bordered", action: { logger.info("Tap") }) .buttonStyle(.bordered) Button(action: { logger.info("Tap") }) { HStack { Text("Complex content button") Spacer() Button("Inner button", action: { logger.info("Tap inner") }) } } ButtonRow() DatePicker("DatePicker", selection: $dateValue) DatePicker("DatePicker .disabled", selection: $dateValue) .disabled(true) Picker("Picker", selection: $pickerValue) { ForEach(pickerValues, id: \.self) { Text($0) } } #if os(macOS) #else Picker("Picker .navigationLink", selection: $pickerValue) { ForEach(pickerValues, id: \.self) { Text($0) } } .pickerStyle(.navigationLink) #endif Picker("Picker .disabled", selection: $pickerValue) { ForEach(pickerValues, id: \.self) { Text($0) } } .disabled(true) Text("Picker .labelsHidden (below)") Picker("Label", selection: $pickerValue) { ForEach(pickerValues, id: \.self) { Text($0) } } .labelsHidden() Picker("Picker .foregroundStyle(.red)", selection: $pickerValue) { ForEach(pickerValues, id: \.self) { Text($0) } } .foregroundStyle(.red) Picker("Picker .tint(.red)", selection: $pickerValue) { ForEach(pickerValues, id: \.self) { Text($0) } } .tint(.red) Toggle(isOn: $boolValue) { Text("Toggle") } TextField("Text field", text: $stringValue) } .toolbar { PlaygroundSourceLink(file: "ListControlsPlayground.swift") } .navigationDestination(for: String.self) { value in Text(value) } } struct ButtonRow: View { var body: some View { Button("Button in custom view") { logger.info("Tap") } } }} --- # NavigationStack | Skip [Skip to content](https://skip.dev/docs/components/navigationstack/#_top) NavigationStack =============== Skip support for [SwiftUI.NavigationStack ↗](https://developer.apple.com/documentation/swiftui/navigationstack) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`NavigationStackPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/NavigationStackPlayground.swift) ![Android screenshot for NavigationStack component (light mode)](http://assets.skip.dev/showcase/NavigationStack-android-light_framed.png) ![iPhone screenshot for NavigationStack component (light mode)](http://assets.skip.dev/showcase/NavigationStack-iphone-light_framed.png) ![iPhone screenshot for NavigationStack component (dark mode)](http://assets.skip.dev/showcase/NavigationStack-iphone-dark_framed.png) ![Android screenshot for NavigationStack component (dark mode)](http://assets.skip.dev/showcase/NavigationStack-android-dark_framed.png) import SwiftUI struct NavigationStackPlayground: View { @Environment(\.dismiss) var dismiss var body: some View { Button("Pop") { dismiss() } .toolbar { PlaygroundSourceLink(file: "NavigationStackPlayground.swift") } }} --- # Modifier | Skip [Skip to content](https://skip.dev/docs/components/modifier/#_top) Modifier ======== Skip support for [custom View Modifiers ↗](https://developer.apple.com/documentation/swiftui/configuring-views) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ModifierPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ModifierPlayground.swift) ![Android screenshot for Modifier component (light mode)](http://assets.skip.dev/showcase/Modifier-android-light_framed.png) ![iPhone screenshot for Modifier component (light mode)](http://assets.skip.dev/showcase/Modifier-iphone-light_framed.png) ![iPhone screenshot for Modifier component (dark mode)](http://assets.skip.dev/showcase/Modifier-iphone-dark_framed.png) ![Android screenshot for Modifier component (dark mode)](http://assets.skip.dev/showcase/Modifier-android-dark_framed.png) import SwiftUI struct ModifierPlayground: View { var body: some View { Text("This text uses a custom modifier that adds a Dismiss button to the navigation bar above") .padding() .dismissable() .toolbar { PlaygroundSourceLink(file: "ModifierPlayground.swift") } }} extension View { public func dismissable() -> some View { modifier(DismissModifier()) }} struct DismissModifier: ViewModifier { @State var isConfirmationPresented = false @Environment(\.dismiss) var dismiss func body(content: Content) -> some View { content .toolbar { Button(action: { isConfirmationPresented = true }) { Label("Dismiss", systemImage: "trash") } } .confirmationDialog("Dismiss", isPresented: $isConfirmationPresented) { Button("Dismiss", role: .destructive, action: { dismiss() }) } }} --- # HapticFeedback | Skip [Skip to content](https://skip.dev/docs/components/hapticfeedback/#_top) HapticFeedback ============== Skip support for [SwiftUI.SensoryFeedback ↗](https://developer.apple.com/documentation/swiftui/sensoryfeedback) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`HapticFeedbackPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/HapticFeedbackPlayground.swift) import SwiftUI struct HapticFeedbackPlayground: View { var body: some View { ScrollView { VStack { #if os(macOS) #else Button("Impact: Soft") { UIImpactFeedbackGenerator(style: .light).impactOccurred() } .tint(.cyan) Button("Impact: Medium") { UIImpactFeedbackGenerator(style: .medium).impactOccurred() } .tint(.teal) Button("Impact: Heavy") { UIImpactFeedbackGenerator(style: .heavy).impactOccurred() } .tint(.indigo) Divider() Button("Impact Intensity: 20%") { UIImpactFeedbackGenerator().impactOccurred(intensity: 0.2) } Button("Impact Intensity: 40%") { UIImpactFeedbackGenerator().impactOccurred(intensity: 0.4) } Button("Impact Intensity: 60%") { UIImpactFeedbackGenerator().impactOccurred(intensity: 0.6) } Button("Impact Intensity: 80%") { UIImpactFeedbackGenerator().impactOccurred(intensity: 0.8) } Button("Impact Intensity: 100%") { UIImpactFeedbackGenerator().impactOccurred(intensity: 1.0) } Divider() Button("Selection: Changed") { UISelectionFeedbackGenerator().selectionChanged() } Divider() Button("Notification: Success") { UINotificationFeedbackGenerator().notificationOccurred(.success) } .tint(.green) Button("Notification: Warning") { UINotificationFeedbackGenerator().notificationOccurred(.warning) } .tint(.yellow) Button("Notification: Error") { UINotificationFeedbackGenerator().notificationOccurred(.error) } .tint(.red) #endif } .buttonStyle(.borderedProminent) .bold() } .toolbar { PlaygroundSourceLink(file: "HapticFeedbackPlayground.swift") } }} --- # OnSubmit | Skip [Skip to content](https://skip.dev/docs/components/onsubmit/#_top) OnSubmit ======== Skip support for [SwiftUI.View.onSubmit ↗](https://developer.apple.com/documentation/swiftui/view/onsubmit(of:_:)) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`OnSubmitPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/OnSubmitPlayground.swift) ![Android screenshot for OnSubmit component (light mode)](http://assets.skip.dev/showcase/OnSubmit-android-light_framed.png) ![iPhone screenshot for OnSubmit component (light mode)](http://assets.skip.dev/showcase/OnSubmit-iphone-light_framed.png) ![iPhone screenshot for OnSubmit component (dark mode)](http://assets.skip.dev/showcase/OnSubmit-iphone-dark_framed.png) ![Android screenshot for OnSubmit component (dark mode)](http://assets.skip.dev/showcase/OnSubmit-android-dark_framed.png) import SwiftUI struct OnSubmitPlayground: View { @State var text1 = "" @State var text2 = "" @State var submitText = "" var body: some View { Form { HStack { Text(submitText) Spacer() Button("Clear") { submitText = "" } } TextField("Text1", text: $text1) .onSubmit { submitText = submitText + "Text1 " } TextField("Text2", text: $text2) .onSubmit { submitText = submitText + "Text2 " } } .onSubmit { submitText = submitText + "Form " } .toolbar { PlaygroundSourceLink(file: "OnSubmitPlayground.swift") } }} --- # ScenePhase | Skip [Skip to content](https://skip.dev/docs/components/scenephase/#_top) ScenePhase ========== Skip support for [SwiftUI.ScenePhase ↗](https://developer.apple.com/documentation/swiftui/scenephase) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ScenePhasePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ScenePhasePlayground.swift) import SwiftUI struct ScenePhasePlayground: View { @Environment(\.scenePhase) var scenePhase @State var history: [ScenePhase] = [] var body: some View { List { Section("ScenePhase history") { ForEach(Array(history.enumerated()), id: \.offset) { phase in Text(verbatim: String(describing: phase.element)) } } } .onChange(of: scenePhase) { phase in logger.log("onChange(of: schenePhase): \(String(describing: phase))") history.append(phase) } .toolbar { PlaygroundSourceLink(file: "ScenePhasePlayground.swift") } }} --- # SecureField | Skip [Skip to content](https://skip.dev/docs/components/securefield/#_top) SecureField =========== Skip support for [SwiftUI.SecureField ↗](https://developer.apple.com/documentation/swiftui/securefield) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`SecureFieldPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/SecureFieldPlayground.swift) ![Android screenshot for SecureField component (light mode)](http://assets.skip.dev/showcase/SecureField-android-light_framed.png) ![iPhone screenshot for SecureField component (light mode)](http://assets.skip.dev/showcase/SecureField-iphone-light_framed.png) ![iPhone screenshot for SecureField component (dark mode)](http://assets.skip.dev/showcase/SecureField-iphone-dark_framed.png) ![Android screenshot for SecureField component (dark mode)](http://assets.skip.dev/showcase/SecureField-android-dark_framed.png) import SwiftUI struct SecureFieldPlayground: View { @State var text = "" var body: some View { ScrollView { VStack(spacing: 16.0) { SecureField("Default", text: $text) SecureField("With prompt", text: $text, prompt: Text("Prompt")) SecureField("Fixed width", text: $text) .frame(width: 200.0) SecureField(".disabled(true)", text: $text) .disabled(true) SecureField(".foregroundStyle(.red)", text: $text) .foregroundStyle(.red) SecureField(".tint(.red)", text: $text) .tint(.red) } .textFieldStyle(.roundedBorder) .padding() } .toolbar { PlaygroundSourceLink(file: "SecureFieldPlayground.swift") } }} --- # Frame | Skip [Skip to content](https://skip.dev/docs/components/frame/#_top) Frame ===== Skip support for [SwiftUI.View.frame ↗](https://developer.apple.com/documentation/swiftui/layout-adjustments) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`FramePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/FramePlayground.swift) ![Android screenshot for Frame component (light mode)](http://assets.skip.dev/showcase/Frame-android-light_framed.png) ![iPhone screenshot for Frame component (light mode)](http://assets.skip.dev/showcase/Frame-iphone-light_framed.png) ![iPhone screenshot for Frame component (dark mode)](http://assets.skip.dev/showcase/Frame-iphone-dark_framed.png) ![Android screenshot for Frame component (dark mode)](http://assets.skip.dev/showcase/Frame-android-dark_framed.png) import SwiftUI struct FramePlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { VStack { Text("width: 100") HStack { Text("A") Color.red .frame(width: 100.0) Text("B") } } .frame(height: 50.0) VStack { Text("height: 50") HStack { Text("A") Color.red .frame(height: 50.0) Text("B") } } VStack { Text("width: 100, height: 50") HStack { Text("A") Color.red .frame(width: 100.0, height: 50.0) Text("B") } } VStack { Text("minWidth: 0, maxWidth: .infinity,\n minHeight: 0, maxHeight: .infinity") HStack { Text("A") Color.red .frame(minWidth: 0.0, maxWidth: .infinity, minHeight: 0.0, maxHeight: .infinity) Text("B") } } VStack { Text("minWidth: 0, maxWidth: .infinity,\n minHeight: 0, maxHeight: .infinity") HStack { Text("A") Color.red .frame(minWidth: 0.0, maxWidth: .infinity, minHeight: 0.0, maxHeight: .infinity) Color.blue .frame(minWidth: 0.0, maxWidth: .infinity, minHeight: 0.0, maxHeight: .infinity) Text("B") } } VStack { Text("minWidth: 300, maxWidth: .infinity,\n minHeight: 100, maxHeight: .infinity") HStack { Text("A") Color.red .frame(minWidth: 300.0, maxWidth: .infinity, minHeight: 100.0, maxHeight: .infinity) Color.blue .frame(minWidth: 0.0, maxWidth: .infinity, minHeight: 0.0, maxHeight: .infinity) Text("B") } } VStack { Text("minWidth: 100,\n minHeight: 0, maxHeight: .infinity") HStack { Text("A") Color.red .frame(minWidth: 100.0, minHeight: 0.0, maxHeight: .infinity) Color.blue .frame(minWidth: 0.0, maxWidth: .infinity, minHeight: 0.0, maxHeight: .infinity) Text("B") } } VStack { Text("width: 100, height: 100") Text("A") .frame(width: 100.0, height: 100.0) .border(.primary) } VStack { Text("alignment: .bottomTrailing") Text("A") .frame(width: 100.0, height: 100.0, alignment: .bottomTrailing) .border(.primary) } } .padding() } .toolbar { PlaygroundSourceLink(file: "FramePlayground.swift") } }} --- # ProgressView | Skip [Skip to content](https://skip.dev/docs/components/progressview/#_top) ProgressView ============ Skip support for [SwiftUI.ProgressView ↗](https://developer.apple.com/documentation/swiftui/progressview) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ProgressViewPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ProgressViewPlayground.swift) ![Android screenshot for ProgressView component (light mode)](http://assets.skip.dev/showcase/ProgressView-android-light_framed.png) ![iPhone screenshot for ProgressView component (light mode)](http://assets.skip.dev/showcase/ProgressView-iphone-light_framed.png) ![iPhone screenshot for ProgressView component (dark mode)](http://assets.skip.dev/showcase/ProgressView-iphone-dark_framed.png) ![Android screenshot for ProgressView component (dark mode)](http://assets.skip.dev/showcase/ProgressView-android-dark_framed.png) import SwiftUI struct ProgressViewPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text("Indeterminate") Spacer() ProgressView() } HStack { Text("Progress nil") Spacer() ProgressView(value: nil, total: 1.0) } HStack { Text("Progress 0.5") Spacer() ProgressView(value: 0.5) } HStack { Text("Indeterminate linear") Spacer() ProgressView() .progressViewStyle(.linear) } HStack { Text("Progress 0.5 circular") Spacer() ProgressView(value: 0.5) .progressViewStyle(.circular) } HStack { Text("Indeterminate, .tint(.red)") Spacer() ProgressView() .tint(.red) } HStack { Text("Progress 0.5, .tint(.red)") Spacer() ProgressView(value: 0.5) .tint(.red) } } .padding() } .toolbar { PlaygroundSourceLink(file: "ProgressViewPlayground.swift") } }} --- # Offset | Skip [Skip to content](https://skip.dev/docs/components/offset/#_top) Offset ====== Skip support for [SwiftUI.View.offset ↗](https://developer.apple.com/documentation/swiftui/view/offset(x:y:)) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`OffsetPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/OffsetPlayground.swift) ![Android screenshot for Offset component (light mode)](http://assets.skip.dev/showcase/Offset-android-light_framed.png) ![iPhone screenshot for Offset component (light mode)](http://assets.skip.dev/showcase/Offset-iphone-light_framed.png) ![iPhone screenshot for Offset component (dark mode)](http://assets.skip.dev/showcase/Offset-iphone-dark_framed.png) ![Android screenshot for Offset component (dark mode)](http://assets.skip.dev/showcase/Offset-android-dark_framed.png) import SwiftUI struct OffsetPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text(".offset(0, 0)") Spacer() ZStack { Color.clear .frame(width: 100.0, height: 100.0) .border(.primary) Color.red .frame(width: 20.0, height: 20.0) .offset(x: 0.0, y: 0.0) } } HStack { Text(".offset(50, -50)") Spacer() ZStack { Color.clear .frame(width: 100.0, height: 100.0) .border(.primary) Color.red .frame(width: 20.0, height: 20.0) .offset(x: 50.0, y: -50.0) } } HStack { Text(".offset(-50, 50)") Spacer() ZStack { Color.clear .frame(width: 100.0, height: 100.0) .border(.primary) Color.red .frame(width: 20.0, height: 20.0) .offset(x: -50.0, y: 50.0) } } HStack { Text(".offset(CGSize(50, 50))") Spacer() ZStack { Color.clear .frame(width: 100.0, height: 100.0) .border(.primary) Color.red .frame(width: 20.0, height: 20.0) .offset(CGSize(width: 50.0, height: 50.0)) } } } .padding() } .toolbar { PlaygroundSourceLink(file: "OffsetPlayground.swift") } }} --- # Sheet | Skip [Skip to content](https://skip.dev/docs/components/sheet/#_top) Sheet ===== Skip support for [SwiftUI.View.sheet ↗](https://developer.apple.com/documentation/SwiftUI/View/sheet(isPresented:onDismiss:content:)) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`SheetPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/SheetPlayground.swift) ![Android screenshot for Sheet component (light mode)](http://assets.skip.dev/showcase/Sheet-android-light_framed.png) ![iPhone screenshot for Sheet component (light mode)](http://assets.skip.dev/showcase/Sheet-iphone-light_framed.png) ![iPhone screenshot for Sheet component (dark mode)](http://assets.skip.dev/showcase/Sheet-iphone-dark_framed.png) ![Android screenshot for Sheet component (dark mode)](http://assets.skip.dev/showcase/Sheet-android-dark_framed.png) import SwiftUI struct SheetPlayground: View { @State var isPresented = false var body: some View { Button("Present sheet") { isPresented = true } .sheet(isPresented: $isPresented) { SheetContentView(dismissSheet: { isPresented = false }) } .toolbar { PlaygroundSourceLink(file: "SheetPlayground.swift") } }} struct SheetContentView: View { @State var isPresented = false @State var counter = 0 @Environment(\.dismiss) var dismiss let dismissSheet: () -> Void var body: some View { NavigationStack { List { Button("Present another") { isPresented = true } Button("Dismiss via state") { dismissSheet() } Button("Dismiss via environment") { dismiss() } Button("Increment counter: \(counter)") { counter += 1 } ForEach(0..<40) { index in Text("Content row \(index)") } } .navigationTitle("Sheet") } .sheet(isPresented: $isPresented) { SheetContentView(dismissSheet: { isPresented = false }) } }} --- # Slider | Skip [Skip to content](https://skip.dev/docs/components/slider/#_top) Slider ====== Skip support for [SwiftUI.Slider ↗](https://developer.apple.com/documentation/swiftui/slider) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`SliderPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/SliderPlayground.swift) ![Android screenshot for Slider component (light mode)](http://assets.skip.dev/showcase/Slider-android-light_framed.png) ![iPhone screenshot for Slider component (light mode)](http://assets.skip.dev/showcase/Slider-iphone-light_framed.png) ![iPhone screenshot for Slider component (dark mode)](http://assets.skip.dev/showcase/Slider-iphone-dark_framed.png) ![Android screenshot for Slider component (dark mode)](http://assets.skip.dev/showcase/Slider-android-dark_framed.png) import SwiftUI struct SliderPlayground: View { @State var value = 0.5 var body: some View { ScrollView { VStack(spacing: 16.0) { Slider(value: $value) HStack { Text(".disabled(true)") Slider(value: $value) .disabled(true) } HStack { Text(".foregroundStyle(.red)") Slider(value: $value) .foregroundStyle(.red) } HStack { Text(".tint(.red)") Slider(value: $value) .tint(.red) } } .padding() } .toolbar { PlaygroundSourceLink(file: "SliderPlayground.swift") } }} --- # Spacer | Skip [Skip to content](https://skip.dev/docs/components/spacer/#_top) Spacer ====== Skip support for [SwiftUI.Spacer ↗](https://developer.apple.com/documentation/swiftui/spacer) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`SpacerPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/SpacerPlayground.swift) ![Android screenshot for Spacer component (light mode)](http://assets.skip.dev/showcase/Spacer-android-light_framed.png) ![iPhone screenshot for Spacer component (light mode)](http://assets.skip.dev/showcase/Spacer-iphone-light_framed.png) ![iPhone screenshot for Spacer component (dark mode)](http://assets.skip.dev/showcase/Spacer-iphone-dark_framed.png) ![Android screenshot for Spacer component (dark mode)](http://assets.skip.dev/showcase/Spacer-android-dark_framed.png) import SwiftUI struct SpacerPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text("Before") Spacer() Text("After") } HStack { Text("Before fixed") Spacer() .frame(width: 100.0) Text("After fixed") } VStack { Text("Before vstack") Spacer() .frame(height: 100.0) Text("After vstack") } } .padding() } .toolbar { PlaygroundSourceLink(file: "SpacerPlayground.swift") } }} --- # Timer | Skip [Skip to content](https://skip.dev/docs/components/timer/#_top) Timer ===== Skip support for [Swift Timer ↗](https://developer.apple.com/documentation/foundation/timer/timerpublisher) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`TimerPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/TimerPlayground.swift) import SwiftUI struct TimerPlayground: View { @State var count = 0 var body: some View { VStack(spacing: 16) { TimerPlaygroundTimerView(message: "Tap count: \(count)") Button("Tap to recompose in 1 sec") { DispatchQueue.main.asyncAfter(wallDeadline: .now() + 1.0) { count += 1 } } } .padding() .toolbar { PlaygroundSourceLink(file: "TimerPlayground.swift") } }} private struct TimerPlaygroundTimerView: View { let message: String let timer = Timer.publish(every: 1.0, on: .main, in: .default).autoconnect() @State var timerDate: Date? var body: some View { VStack { Text(message) Text("Timer date: \(timerDate == nil ? "nil" : timerDate!.description)") } .font(.largeTitle) .onReceive(timer) { date in timerDate = date } }} --- # Toggle | Skip [Skip to content](https://skip.dev/docs/components/toggle/#_top) Toggle ====== Skip support for [SwiftUI.Toggle ↗](https://developer.apple.com/documentation/swiftui/toggle) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`TogglePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/TogglePlayground.swift) ![Android screenshot for Toggle component (light mode)](http://assets.skip.dev/showcase/Toggle-android-light_framed.png) ![iPhone screenshot for Toggle component (light mode)](http://assets.skip.dev/showcase/Toggle-iphone-light_framed.png) ![iPhone screenshot for Toggle component (dark mode)](http://assets.skip.dev/showcase/Toggle-iphone-dark_framed.png) ![Android screenshot for Toggle component (dark mode)](http://assets.skip.dev/showcase/Toggle-android-dark_framed.png) import SwiftUI struct TogglePlayground: View { @State var isOn = false var body: some View { ScrollView { VStack(spacing: 16.0) { Toggle(isOn: $isOn) { Text("Viewbuilder init") } Toggle("String init", isOn: $isOn) Toggle("Fixed width", isOn: $isOn) .frame(width: 200.0) VStack { Text(".labelsHidden():") Toggle("Label", isOn: $isOn) } .labelsHidden() Toggle(".disabled(true)", isOn: $isOn) .disabled(true) Toggle(".foregroundStyle(.red)", isOn: $isOn) .foregroundStyle(.red) Toggle(".tint(.red)", isOn: $isOn) .tint(.red) } .padding() } .toolbar { PlaygroundSourceLink(file: "TogglePlayground.swift") } }} --- # Observable | Skip [Skip to content](https://skip.dev/docs/components/observable/#_top) Observable ========== Skip support for [Observable state ↗](https://developer.apple.com/documentation/observation/observable) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ObservablePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ObservablePlayground.swift) ![Android screenshot for Observable component (light mode)](http://assets.skip.dev/showcase/Observable-android-light_framed.png) ![iPhone screenshot for Observable component (light mode)](http://assets.skip.dev/showcase/Observable-iphone-light_framed.png) ![iPhone screenshot for Observable component (dark mode)](http://assets.skip.dev/showcase/Observable-iphone-dark_framed.png) ![Android screenshot for Observable component (dark mode)](http://assets.skip.dev/showcase/Observable-android-dark_framed.png) import SwiftUI#if canImport(Observation)import Observation#endif struct ObservablePlayground: View { var body: some View { if #available(iOS 17.0, macOS 14.0, *) { ObservablesOuterView() .environmentObject(PlaygroundEnvironmentObject(text: "initialEnvironment")) .toolbar { PlaygroundSourceLink(file: "ObservablePlayground.swift") } } else { Text("iOS 17 / macOS 14 required for Observation framework") } }} class PlaygroundEnvironmentObject: ObservableObject { @Published var text: String init(text: String) { self.text = text }} @available(iOS 17.0, macOS 14.0, *)@Observable class PlaygroundObservable { var text = "" init(text: String) { self.text = text }} @available(iOS 17.0, macOS 14.0, *)struct ObservablesOuterView: View { @State var stateObject = PlaygroundObservable(text: "initialState") @EnvironmentObject var environmentObject: PlaygroundEnvironmentObject var body: some View { VStack { Text(stateObject.text) Text(environmentObject.text) ObservablesObservableView(observable: stateObject) .border(Color.red) ObservablesBindingView(text: $stateObject.text) .border(Color.blue) } }} @available(iOS 17.0, macOS 14.0, *)struct ObservablesObservableView: View { let observable: PlaygroundObservable @EnvironmentObject var environmentObject: PlaygroundEnvironmentObject var body: some View { Text(observable.text) Text(environmentObject.text) Button("Button") { observable.text = "observableState" environmentObject.text = "observableEnvironment" } }} struct ObservablesBindingView: View { @Binding var text: String var body: some View { Button("Button") { text = "bindingState" } .accessibilityIdentifier("binding-button") }} --- # Pasteboard | Skip [Skip to content](https://skip.dev/docs/components/pasteboard/#_top) Pasteboard ========== Skip support for [SwiftUI Clipboard ↗](https://developer.apple.com/documentation/swiftui/clipboard) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`PasteboardPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/PasteboardPlayground.swift) import SwiftUI #if os(macOS)struct PasteboardPlayground: View { var body: some View { Text("UIPasteboard is not available on macOS") }}#elsestruct PasteboardPlayground: View { @State var pasteboardInfo = PasteboardInfo(UIPasteboard.general) @State var text = "" var body: some View { ScrollView { VStack(spacing: 16) { HStack { TextField("Text / URL", text: $text) Spacer() Button("Copy") { if let url = URL(string: text) { UIPasteboard.general.url = url } else { UIPasteboard.general.string = text } } } Divider() HStack { Text("Pasteboard count:") Spacer() Text(String(describing: pasteboardInfo.numberOfItems)) } HStack { Text("Has strings:") Spacer() Text(String(describing: pasteboardInfo.hasStrings)) } HStack { Text("Strings:") Spacer() Text("\(pasteboardInfo.strings == nil ? "nil" : pasteboardInfo.strings!.description)") } HStack { Text("Has URLs:") Spacer() Text(String(describing: pasteboardInfo.hasURLs)) } HStack { Text("URLs:") Spacer() Text("\(pasteboardInfo.urls == nil ? "nil" : pasteboardInfo.urls!.description)") } } .padding() } .onReceive(NotificationCenter.default.publisher(for: UIPasteboard.changedNotification), perform: { notification in pasteboardInfo = PasteboardInfo(notification.object as! UIPasteboard) }) .toolbar { PlaygroundSourceLink(file: "PasteboardPlayground.swift") } }} struct PasteboardInfo { let numberOfItems: Int let hasStrings: Bool let strings: [String]? let hasURLs: Bool let urls: [URL]? init(_ pasteboard: UIPasteboard) { self.numberOfItems = pasteboard.numberOfItems self.hasStrings = pasteboard.hasStrings self.strings = pasteboard.strings self.hasURLs = pasteboard.hasURLs self.urls = pasteboard.urls }}#endif --- # ShareLink | Skip [Skip to content](https://skip.dev/docs/components/sharelink/#_top) ShareLink ========= Skip support for [SwiftUI.ShareLink ↗](https://developer.apple.com/documentation/swiftui/sharelink) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ShareLinkPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ShareLinkPlayground.swift) ![Android screenshot for ShareLink component (light mode)](http://assets.skip.dev/showcase/ShareLink-android-light_framed.png) ![iPhone screenshot for ShareLink component (light mode)](http://assets.skip.dev/showcase/ShareLink-iphone-light_framed.png) ![iPhone screenshot for ShareLink component (dark mode)](http://assets.skip.dev/showcase/ShareLink-iphone-dark_framed.png) ![Android screenshot for ShareLink component (dark mode)](http://assets.skip.dev/showcase/ShareLink-android-dark_framed.png) import SwiftUI struct ShareLinkPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text("Default") Spacer() ShareLink(item: "My text") } HStack { Text("Default URL") Spacer() ShareLink(item: URL(string: "https://skip.dev")!) } HStack { Text("Subject & Message") Spacer() ShareLink(item: "My text", subject: Text("My subject"), message: Text("My message")) } HStack { Text("Subject & Message URL") Spacer() ShareLink(item: URL(string: "https://skip.dev")!, subject: Text("My subject"), message: Text("My message")) } HStack { Text("Title") Spacer() ShareLink("Title", item: "My text") } HStack { Text(".buttonStyle(.bordered)") Spacer() ShareLink("Title", item: "My text") .buttonStyle(.bordered) } HStack { Text("Label") Spacer() ShareLink(item: "My text") { Label("Title", systemImage: "heart.fill") } } } .padding() } .toolbar { PlaygroundSourceLink(file: "ShareLinkPlayground.swift") } }} --- # TabView | Skip [Skip to content](https://skip.dev/docs/components/tabview/#_top) TabView ======= Skip support for [SwiftUI.TabView ↗](https://developer.apple.com/documentation/swiftui/tabview) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`TabViewPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/TabViewPlayground.swift) ![Android screenshot for TabView component (light mode)](http://assets.skip.dev/showcase/TabView-android-light_framed.png) ![iPhone screenshot for TabView component (light mode)](http://assets.skip.dev/showcase/TabView-iphone-light_framed.png) ![iPhone screenshot for TabView component (dark mode)](http://assets.skip.dev/showcase/TabView-iphone-dark_framed.png) ![Android screenshot for TabView component (dark mode)](http://assets.skip.dev/showcase/TabView-android-dark_framed.png) import SwiftUI struct TabViewPlayground: View { @State var selectedTab = "Home" var body: some View { TabView(selection: $selectedTab) { TabPlaygroundContentView(label: "Home", selectedTab: $selectedTab) .tabItem { Label("Home", systemImage: "house.fill") } .tag("Home") TabPlaygroundContentView(label: "Favorites", selectedTab: $selectedTab) .tabItem { Label("Favorites", systemImage: "heart.fill") } .tag("Favorites") TabPlaygroundContentView(label: "Info", selectedTab: $selectedTab) .tabItem { Label("Info", systemImage: "info.circle.fill") } .tag("Info") } .toolbar { PlaygroundSourceLink(file: "TabViewPlayground.swift") } }} struct TabPlaygroundContentView: View { let label: String @Binding var selectedTab: String var body: some View { VStack { Text(label).bold() if label != "Home" { Button("Switch to Home") { selectedTab = "Home" } } if label != "Favorites" { Button("Switch to Favorites") { selectedTab = "Favorites" } } if label != "Info" { Button("Switch to Info") { selectedTab = "Info" } } } }} --- # Text | Skip [Skip to content](https://skip.dev/docs/components/text/#_top) Text ==== Skip support for [SwiftUI.Text ↗](https://developer.apple.com/documentation/swiftui/text) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`TextPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/TextPlayground.swift) ![Android screenshot for Text component (light mode)](http://assets.skip.dev/showcase/Text-android-light_framed.png) ![iPhone screenshot for Text component (light mode)](http://assets.skip.dev/showcase/Text-iphone-light_framed.png) ![iPhone screenshot for Text component (dark mode)](http://assets.skip.dev/showcase/Text-iphone-dark_framed.png) ![Android screenshot for Text component (dark mode)](http://assets.skip.dev/showcase/Text-android-dark_framed.png) import SwiftUI struct TextPlayground: View { var body: some View { ScrollView { VStack(spacing: 16) { Text("Plain") Text("Bold").bold() Text("Italic").italic() Text("Title bold italic").font(.title).bold().italic() VStack { Text("Thin footnote container") Text("Overridden to title font").font(.title) } .font(.footnote).fontWeight(.thin) .border(.primary) Divider() Text("Custom Font") .font(Font.custom("Protest Guerrilla", size: 30.0)) // protest_guerrilla.ttf Divider() Text("Font.largeTitle").font(.largeTitle) Text("Font.title").font(.title) Text("Font.title2").font(.title2) Text("Font.title3").font(.title3) Text("Font.headline").font(.headline) Text("Font.subheadline").font(.subheadline) Text("Font.body").font(.body) Text("Font.callout").font(.callout) Text("Font.footnote").font(.footnote) Text("Font.caption").font(.caption) Text("Font.caption2").font(.caption2) Divider() Text("Wrap: This is some long text that should wrap when it exceeds the width of its frame") .frame(width: 200) .border(Color.blue) Text(".lineLimit(1): This is some long text that should wrap when it exceeds the width of its frame") .lineLimit(1) .frame(width: 200) .border(Color.blue) } .padding() } .toolbar { PlaygroundSourceLink(file: "TextPlayground.swift") } }} --- # TextEditor | Skip [Skip to content](https://skip.dev/docs/components/texteditor/#_top) TextEditor ========== Skip support for [SwiftUI.TextEditor ↗](https://developer.apple.com/documentation/swiftui/texteditor) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`TextEditorPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/TextEditorPlayground.swift) ![Android screenshot for TextEditor component (light mode)](http://assets.skip.dev/showcase/TextEditor-android-light_framed.png) ![iPhone screenshot for TextEditor component (light mode)](http://assets.skip.dev/showcase/TextEditor-iphone-light_framed.png) ![iPhone screenshot for TextEditor component (dark mode)](http://assets.skip.dev/showcase/TextEditor-iphone-dark_framed.png) ![Android screenshot for TextEditor component (dark mode)](http://assets.skip.dev/showcase/TextEditor-android-dark_framed.png) import SwiftUI struct TextEditorPlayground: View { @State var text = """ When in the course of human events, it becomes necessary for an Oppressed People to Rise, and assert their Natural Rights, as Human Beings, as Native & mutual Citizens of a free Republic, and break that odious Yoke of oppression, which is so unjustly laid upon them by their fellow Countrymen, and to assume among the powers of Earth the same equal privileges to which the Laws of Nature, & natures God entitle them; A moderate respect for the opinions of Mankind, requires that they should declare the causes which incite them to this just & worthy action. We hold these truths to be Self Evident; That All Men are Created Equal; That they are endowed by their Creator with certain unalienable rights. That among these are Life, Liberty; & the persuit of happiness. That Nature hath freely given to all Men, a full Supply of Air. Water, & Land; for their sustinance, & mutual happiness, That No Man has any right to deprive his fellow Man, of these Inherent rights, except in punishment of Crime. That to secure these rights governments are instituted among Men, deriving their just powers from the consent of the governed. That when any form of Government, becomes destructive to these ends, It is the right of the People, to alter, Amend, or Remoddel it, Laying its foundation on Such Principles, & organizing its powers in such form as to them shall seem most likely to effect the safety, & happiness of the Human Race. """ var body: some View { TextEditor(text: $text) .toolbar { PlaygroundSourceLink(file: "TextEditorPlayground.swift") } }} --- # TextField | Skip [Skip to content](https://skip.dev/docs/components/textfield/#_top) TextField ========= Skip support for [SwiftUI.TextField ↗](https://developer.apple.com/documentation/swiftui/textfield) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`TextFieldPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/TextFieldPlayground.swift) ![Android screenshot for TextField component (light mode)](http://assets.skip.dev/showcase/TextField-android-light_framed.png) ![iPhone screenshot for TextField component (light mode)](http://assets.skip.dev/showcase/TextField-iphone-light_framed.png) ![iPhone screenshot for TextField component (dark mode)](http://assets.skip.dev/showcase/TextField-iphone-dark_framed.png) ![Android screenshot for TextField component (dark mode)](http://assets.skip.dev/showcase/TextField-android-dark_framed.png) import SwiftUI struct TextFieldPlayground: View { @State var text = "" var body: some View { ScrollView { VStack(spacing: 16.0) { TextField(text: $text) { Text(".init(text:label:)") } .textFieldStyle(.roundedBorder) TextField(".init(_:text:)", text: $text) .textFieldStyle(.roundedBorder) TextField("With prompt", text: $text, prompt: Text("Prompt")) .textFieldStyle(.roundedBorder) TextField("Fixed width", text: $text) .textFieldStyle(.roundedBorder) .frame(width: 200.0) TextField(".disabled(true)", text: $text) .textFieldStyle(.roundedBorder) .disabled(true) TextField(".foregroundStyle(.red)", text: $text) .textFieldStyle(.roundedBorder) .foregroundStyle(.red) TextField(".tint(.red)", text: $text) .textFieldStyle(.roundedBorder) .tint(.red) TextField(".plain", text: $text) .textFieldStyle(.roundedBorder) TextField(".plain .disabled(true)", text: $text) .textFieldStyle(.plain) .disabled(true) TextField(".plain .foregroundStyle(.red)", text: $text) .textFieldStyle(.plain) .foregroundStyle(.red) TextField(".plain .tint(.red)", text: $text) .textFieldStyle(.plain) .tint(.red) TextField("Custom background", text: $text) .textFieldStyle(.plain) .background { RoundedRectangle(cornerRadius: 20.0) .fill(.yellow) } } .padding() } .toolbar { PlaygroundSourceLink(file: "TextFieldPlayground.swift") } }} --- # Storage | Skip [Skip to content](https://skip.dev/docs/components/storage/#_top) Storage ======= Skip support for [SwiftUI.AppStorage ↗](https://developer.apple.com/documentation/swiftui/appstorage) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`StoragePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/StoragePlayground.swift) ![Android screenshot for Storage component (light mode)](http://assets.skip.dev/showcase/Storage-android-light_framed.png) ![iPhone screenshot for Storage component (light mode)](http://assets.skip.dev/showcase/Storage-iphone-light_framed.png) ![iPhone screenshot for Storage component (dark mode)](http://assets.skip.dev/showcase/Storage-iphone-dark_framed.png) ![Android screenshot for Storage component (dark mode)](http://assets.skip.dev/showcase/Storage-android-dark_framed.png) import SwiftUI struct StoragePlayground: View { @AppStorage("boolAppStorage") var boolAppStorage = false @AppStorage("enumAppStorage") var enumAppStorage = E.first enum E: Int { case first, second, third } var body: some View { VStack(spacing: 16.0) { HStack { Text("Enum AppStorage") Spacer() Picker("Enum AppStorage", selection: $enumAppStorage) { Text("First").tag(E.first) Text("Second").tag(E.second) Text("Third").tag(E.third) } } Toggle("Bool AppStorage", isOn: $boolAppStorage) NavigationLink("Push binding") { StoragePlaygroundBindingView(binding: $boolAppStorage) } NavigationLink("Push another", value: PlaygroundType.storage) } .padding() .toolbar { PlaygroundSourceLink(file: "StoragePlayground.swift") } }} struct StoragePlaygroundBindingView: View { @Binding var binding: Bool var body: some View { Toggle("Storage", isOn: $binding) .padding() .navigationTitle("Storage Binding") }} --- # Overlay | Skip [Skip to content](https://skip.dev/docs/components/overlay/#_top) Overlay ======= Skip support for [SwiftUI.View.overlay ↗](https://developer.apple.com/documentation/swiftui/view/overlay(alignment:content:)) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`OverlayPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/OverlayPlayground.swift) ![Android screenshot for Overlay component (light mode)](http://assets.skip.dev/showcase/Overlay-android-light_framed.png) ![iPhone screenshot for Overlay component (light mode)](http://assets.skip.dev/showcase/Overlay-iphone-light_framed.png) ![iPhone screenshot for Overlay component (dark mode)](http://assets.skip.dev/showcase/Overlay-iphone-dark_framed.png) ![Android screenshot for Overlay component (dark mode)](http://assets.skip.dev/showcase/Overlay-android-dark_framed.png) import SwiftUI struct OverlayPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text(".red.opacity(0.5)") Spacer() Text("Hello") .padding() .overlay(.red.opacity(0.5)) } HStack { Text("in: Capsule()") Spacer() Text("Hello") .padding() .overlay(.red.opacity(0.5), in: Capsule()) } HStack { Text("Circles") Spacer() Text("Hello") .padding() .overlay { HStack { Circle().fill(.red.opacity(0.5)) Circle().fill(.green.opacity(0.5)) } } .border(.blue) } HStack { Text("Large circle") Spacer() Text("Hello") .padding() .overlay { Circle() .fill(.red.opacity(0.5)) .frame(width: 100.0, height: 100.0) } .border(.blue) } HStack { Text(".clipped()") Spacer() Text("Hello") .padding() .overlay { HStack { Circle().fill(.red.opacity(0.5)) Circle().fill(.red.opacity(0.5)) } .frame(width: 200.0, height: 100.0) } .clipped() .border(.blue) } HStack { Text("Small circle") Spacer() Text("Hello") .padding() .overlay { Circle() .fill(.red.opacity(0.5)) .frame(width: 20.0, height: 20.0) } .border(.blue) } HStack { Text("alignment: .topLeading") Spacer() Text("Hello") .padding() .overlay(alignment: .topLeading) { Circle() .fill(.red.opacity(0.5)) .frame(width: 20.0, height: 20.0) } .border(.blue) } HStack { Text("alignment: .bottomTrailing") Spacer() Text("Hello") .padding() .overlay(alignment: .bottomTrailing) { Circle() .fill(.red.opacity(0.5)) .frame(width: 20.0, height: 20.0) } .border(.blue) } } .padding() } .toolbar { PlaygroundSourceLink(file: "OverlayPlayground.swift") } }} --- # OffsetPosition | Skip [Skip to content](https://skip.dev/docs/components/offsetposition/#_top) OffsetPosition ============== Skip support for [SwiftUI.offset ↗](https://developer.apple.com/documentation/swiftui/view/offset(_:)) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`OffsetPositionPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/OffsetPositionPlayground.swift) import SwiftUI struct OffsetPositionPlayground: View { var body: some View { ScrollView { VStack(spacing: 16) { HStack { Text(".offset(0, 0)") Spacer() ZStack { Color.clear .frame(width: 100, height: 100) .border(.primary) Color.red .frame(width: 20, height: 20) .offset(x: 0, y: 0) } } HStack { Text(".offset(50, -50)") Spacer() ZStack { Color.clear .frame(width: 100, height: 100) .border(.primary) Color.red .frame(width: 20, height: 20) .offset(x: 50, y: -50) } } HStack { Text(".offset(-50, 50)") Spacer() ZStack { Color.clear .frame(width: 100, height: 100) .border(.primary) Color.red .frame(width: 20, height: 20) .offset(x: -50, y: 50) } } HStack { Text(".offset(CGSize(50, 50))") Spacer() ZStack { Color.clear .frame(width: 100, height: 100) .border(.primary) Color.red .frame(width: 20, height: 20) .offset(CGSize(width: 50, height: 50)) } } HStack { Text(".position(0, 0)") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .position(x: 0, y: 0) } .frame(width: 100, height: 100) .border(.primary) } HStack { Text(".position(50, 50)") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .position(x: 50, y: 50) } .frame(width: 100, height: 100) .border(.primary) } HStack { Text(".position(CGPoint(75, 75))") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .position(CGPoint(x: 75, y: 75)) } .frame(width: 100, height: 100) .border(.primary) } NavigationLink("Push Text.position(100, 100)") { Text("Over here!") .background(.yellow) .position(x: 100, y: 100) } } .padding() } .toolbar { PlaygroundSourceLink(file: "OffsetPositionPlayground.swift") } }} --- # Keyboard | Skip [Skip to content](https://skip.dev/docs/components/keyboard/#_top) Keyboard ======== Skip support for [SwiftUI.View.keyboardType ↗](https://developer.apple.com/documentation/swiftui/text-input-and-output#managing-text-entry) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`KeyboardPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/KeyboardPlayground.swift) ![Android screenshot for Keyboard component (light mode)](http://assets.skip.dev/showcase/Keyboard-android-light_framed.png) ![iPhone screenshot for Keyboard component (light mode)](http://assets.skip.dev/showcase/Keyboard-iphone-light_framed.png) ![iPhone screenshot for Keyboard component (dark mode)](http://assets.skip.dev/showcase/Keyboard-iphone-dark_framed.png) ![Android screenshot for Keyboard component (dark mode)](http://assets.skip.dev/showcase/Keyboard-android-dark_framed.png) import SwiftUI struct KeyboardPlayground: View { @State var text = "" var body: some View { ScrollView { VStack(spacing: 16.0) { TextField("Default", text: $text) TextField(".autocorrectionDisabled()", text: $text) .autocorrectionDisabled() #if os(macOS) #else ForEach(keyboardTypes, id: \.0) { TextField(".keyboardType(.\($0.0))", text: $text) .keyboardType($0.1) } #endif ForEach(submitLabels, id: \.0) { TextField(".submitLabel(.\($0.0))", text: $text) .submitLabel($0.1) } #if os(macOS) #else ForEach(textInputAutocapitalizations, id: \.0) { TextField(".textInputAutocapitalization(.\($0.0))", text: $text) .textInputAutocapitalization($0.1) } TextField("Combination", text: $text) .submitLabel(.next) .autocorrectionDisabled() .textInputAutocapitalization(.words) #endif } .textFieldStyle(.roundedBorder) .padding() } .toolbar { PlaygroundSourceLink(file: "KeyboardPlayground.swift") } } #if os(macOS) #else private var keyboardTypes: [(String, UIKeyboardType)] { return [ ("default", UIKeyboardType.default), ("asciiCapable", UIKeyboardType.asciiCapable), ("numbersAndPunctuation", UIKeyboardType.numbersAndPunctuation), ("URL", UIKeyboardType.URL), ("numberPad", UIKeyboardType.numberPad), ("phonePad", UIKeyboardType.phonePad), ("namePhonePad", UIKeyboardType.namePhonePad), ("emailAddress", UIKeyboardType.emailAddress), ("decimalPad", UIKeyboardType.decimalPad), ("twitter", UIKeyboardType.twitter), ("webSearch", UIKeyboardType.webSearch), ("asciiCapableNumberPad", UIKeyboardType.asciiCapableNumberPad), ("alphabet", UIKeyboardType.alphabet) ] } #endif private var submitLabels: [(String, SubmitLabel)] { return [ ("done", SubmitLabel.done), ("go", SubmitLabel.done), ("send", SubmitLabel.send), ("join", SubmitLabel.join), ("route", SubmitLabel.route), ("search", SubmitLabel.search), ("return", SubmitLabel.return), ("next", SubmitLabel.next), ("continue", SubmitLabel.continue) ] } #if os(macOS) #else private var textInputAutocapitalizations: [(String, TextInputAutocapitalization)] { return [ ("never", TextInputAutocapitalization.never), ("words", TextInputAutocapitalization.words), ("sentences", TextInputAutocapitalization.sentences), ("characters", TextInputAutocapitalization.characters) ] } #endif} --- # Picker | Skip [Skip to content](https://skip.dev/docs/components/picker/#_top) Picker ====== Skip support for [SwiftUI.Picker ↗](https://developer.apple.com/documentation/swiftui/picker) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`PickerPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/PickerPlayground.swift) ![Android screenshot for Picker component (light mode)](http://assets.skip.dev/showcase/Picker-android-light_framed.png) ![iPhone screenshot for Picker component (light mode)](http://assets.skip.dev/showcase/Picker-iphone-light_framed.png) ![iPhone screenshot for Picker component (dark mode)](http://assets.skip.dev/showcase/Picker-iphone-dark_framed.png) ![Android screenshot for Picker component (dark mode)](http://assets.skip.dev/showcase/Picker-android-dark_framed.png) import SwiftUI struct PickerPlayground: View { let values = ["One", "Two", "Three", "Four", "Five"] @State var selectedValue = "Two" var body: some View { ScrollView { VStack(spacing: 16.0) { Text("Note: Picker displays differently when in a Form. See the Form playground") .font(.caption) VStack { Text("Viewbuilder init") Picker(selection: $selectedValue) { ForEach(values, id: \.self) { Text($0) } } label: { Text("Label") } } VStack { Text("String init") Picker("Label", selection: $selectedValue) { ForEach(values, id: \.self) { Text($0) } } } VStack { Text(".disabled(true)") Picker("Label", selection: $selectedValue) { ForEach(values, id: \.self) { Text($0) } } .disabled(true) } VStack { Text(".foregroundStyle(.red)") Picker("Label", selection: $selectedValue) { ForEach(values, id: \.self) { Text($0) } } .foregroundStyle(.red) } VStack { Text(".tint(.red)") Picker("Label", selection: $selectedValue) { ForEach(values, id: \.self) { Text($0) } } .tint(.red) } VStack { Text("Label") Picker("Label", selection: $selectedValue) { ForEach(values, id: \.self) { Label($0, systemImage: "heart.fill") } } } VStack { Text("Fixed content") Picker("Label", selection: $selectedValue) { Text(verbatim: values[0]).tag(values[0]) Text(verbatim: values[1]).tag(values[1]) Text(verbatim: values[2]).tag(values[2]) Text(verbatim: values[3]).tag(values[3]) Text(verbatim: values[4]).tag(values[4]) } } VStack { Text("Indexed ForEach") Picker("Label", selection: $selectedValue) { ForEach(0..<5) { index in Text(verbatim: values[index]).tag(values[index]) } } } #if os(macOS) #else Picker(".pickerStyle(.navigationLink)", selection: $selectedValue) { ForEach(values, id: \.self) { Text($0) } } .pickerStyle(.navigationLink) Picker("Label .navigationLink", selection: $selectedValue) { ForEach(values, id: \.self) { Label($0, systemImage: "heart.fill") } } .pickerStyle(.navigationLink) #endif } .padding() } .toolbar { PlaygroundSourceLink(file: "TogglePlayground.swift") } }} --- # Link | Skip [Skip to content](https://skip.dev/docs/components/link/#_top) Link ==== Skip support for [SwiftUI.Link ↗](https://developer.apple.com/documentation/swiftui/link) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`LinkPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/LinkPlayground.swift) ![Android screenshot for Link component (light mode)](http://assets.skip.dev/showcase/Link-android-light_framed.png) ![iPhone screenshot for Link component (light mode)](http://assets.skip.dev/showcase/Link-iphone-light_framed.png) ![iPhone screenshot for Link component (dark mode)](http://assets.skip.dev/showcase/Link-iphone-dark_framed.png) ![Android screenshot for Link component (dark mode)](http://assets.skip.dev/showcase/Link-android-dark_framed.png) import SwiftUI struct LinkPlayground: View { @Environment(\.openURL) var openURL let destination = URL(string: "https://skip.dev")! var body: some View { ScrollView { VStack(spacing: 16.0) { Link(destination: destination) { Text(".init(destination:label:)") } Link(".init(_:destination:)", destination: destination) Link(destination: destination) { Image(systemName: "heart.fill") } .border(.blue) Link(".buttonStyle(.bordered)", destination: destination) .buttonStyle(.bordered) Link(".foregroundStyle(.red)", destination: destination) .foregroundStyle(.red) Link(".tint(.red)", destination: destination) .tint(.red) Button("@Environment(\\.openURL)") { openURL(destination) } } .padding() } .toolbar { PlaygroundSourceLink(file: "LinkPlayground.swift") } }} --- # VideoPlayer | Skip [Skip to content](https://skip.dev/docs/components/videoplayer/#_top) VideoPlayer =========== Skip support for [AVKit.VideoPlayer ↗](https://developer.apple.com/documentation/avkit/videoplayer) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`VideoPlayerPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/VideoPlayerPlayground.swift) ![Android screenshot for VideoPlayer component (light mode)](http://assets.skip.dev/showcase/VideoPlayer-android-light_framed.png) ![iPhone screenshot for VideoPlayer component (light mode)](http://assets.skip.dev/showcase/VideoPlayer-iphone-light_framed.png) ![iPhone screenshot for VideoPlayer component (dark mode)](http://assets.skip.dev/showcase/VideoPlayer-iphone-dark_framed.png) ![Android screenshot for VideoPlayer component (dark mode)](http://assets.skip.dev/showcase/VideoPlayer-android-dark_framed.png) import AVKitimport SwiftUI struct VideoPlayerPlayground: View { @State var player = AVPlayer(playerItem: AVPlayerItem(url: URL(string: "https://skip.tools/assets/introduction.mov")!)) @State var isPlaying: Bool = false var body: some View { VStack { Button { isPlaying ? player.pause() : player.play() isPlaying = !isPlaying player.seek(to: .zero) } label: { Image(systemName: isPlaying ? "stop" : "play") .padding() } VideoPlayer(player: player) } .toolbar { PlaygroundSourceLink(file: "VideoPlayerPlayground.swift") } }} --- # Glossary | Skip [Skip to content](https://skip.dev/docs/glossary/#_top) Glossary ======== This page is a reference of terms used in the Skip documentation, covering Skip-specific concepts, iOS development, and Android development. * * * Skip-Specific Terms ------------------- [Section titled “Skip-Specific Terms”](https://skip.dev/docs/glossary/#skip-specific-terms) ### Skip [Section titled “Skip”](https://skip.dev/docs/glossary/#skip) A technology that enables iOS developers to build native iOS and Android apps from a single [Swift](https://skip.dev/docs/glossary/#swift) codebase using [SwiftUI](https://skip.dev/docs/glossary/#swiftui) . Skip can operate in [Lite](https://skip.dev/docs/glossary/#skip-lite) mode, where it transpiles Swift code to [Kotlin](https://skip.dev/docs/glossary/#kotlin) , or it can operate in [Fuse](https://skip.dev/docs/glossary/#skip-fuse) mode, where it uses the [Swift SDK for Android](https://skip.dev/docs/glossary/#ssdk4a) to compile Swift code natively for Android. In both cases, Skip utilizes a SwiftUI to [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) translation process that produces genuinely native apps for both platforms. **Resources:** * [Skip Documentation](https://skip.dev/docs/) * [Quick Start](https://skip.dev/docs/gettingstarted/) **External Resources:** * [Skip on GitHub ↗](https://github.com/skiptools/) ### Skip Lite [Section titled “Skip Lite”](https://skip.dev/docs/glossary/#skip-lite) Skip’s transpilation mode where Swift code is converted (or [_transpiled_](https://skip.dev/docs/glossary/#transpiler) ) to [Kotlin](https://skip.dev/docs/glossary/#kotlin) for Android. Contrast with [Skip Fuse](https://skip.dev/docs/glossary/#skip-fuse) , which uses the [Swift SDK for Android](https://skip.dev/docs/glossary/#ssdk4a) to facilitate using compiled Swift for the Android platform. Both modes produce fully native apps. **Related Terms:** [Skip Fuse](https://skip.dev/docs/glossary/#skip-fuse) , [Transpiler](https://skip.dev/docs/glossary/#transpiler) , [Skip Zero](https://skip.dev/docs/glossary/#skip-zero) **Learn More:** [Skip Lite vs. Fuse Documentation](https://skip.dev/docs/status/) ### Skip Fuse [Section titled “Skip Fuse”](https://skip.dev/docs/glossary/#skip-fuse) Skip’s modern app development mode that uses native Swift built with the [Swift SDK for Android](https://skip.dev/docs/glossary/#ssdk4a) . Skip Fuse does not have any limitations on Swift language support, but it does lead to larger app sizes. **Related Terms:** [Skip Lite](https://skip.dev/docs/glossary/#skip-lite) **Learn More:** [Native and Transpiled Mode](https://skip.dev/docs/modes/) **See Also:** * [Fully Native Cross-Platform Swift Apps (Skip Blog)](https://skip.dev/blog/fully-native-android-swift-apps/) ### Skip Plugin [Section titled “Skip Plugin”](https://skip.dev/docs/glossary/#skipstone) An [Xcode](https://skip.dev/docs/glossary/#xcode) and [SwiftPM](https://skip.dev/docs/glossary/#swiftpm) plugin — also known as _skipstone_ — that handles the conversion of an iOS project into an Android project. The plugin handles resource conversion and packaging, localization processing, building bridges between Swift and Kotlin in [Skip Fuse](https://skip.dev/docs/glossary/#skip-fuse) mode, and performing Swift to Kotlin [transpilation](https://skip.dev/docs/glossary/#transpiler) in [Skip Lite](https://skip.dev/docs/glossary/#skip-lite) mode. In both modes, the plugin automatically translates [SwiftUI](https://skip.dev/docs/glossary/#swiftui) into [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) as you develop. **Related Terms:** [Xcode](https://skip.dev/docs/glossary/#xcode) , [Transpiler](https://skip.dev/docs/glossary/#transpiler) , [SwiftPM](https://skip.dev/docs/glossary/#swiftpm) , [Skip Lite](https://skip.dev/docs/glossary/#skip-lite) , [Skip Fuse](https://skip.dev/docs/glossary/#skip-fuse) , [Skip Bridge](https://skip.dev/docs/glossary/#skip-bridge) **External Resources:** * [Skip Plugin repository (GitHub) ↗](https://github.com/skiptools/skip/) ### Skip CLI [Section titled “Skip CLI”](https://skip.dev/docs/glossary/#skip-cli) The `skip` command-line tool that is used to create and manage Skip projects, handle interaction with gradle and the Android emulator, and perform system checkups and project validation. The Skip CLI is installed and managed with the Homebrew package management system. **Related Terms:** [Xcode](https://skip.dev/docs/glossary/#xcode) , [SwiftPM](https://skip.dev/docs/glossary/#swiftpm) , [Transpiler](https://skip.dev/docs/glossary/#transpiler) **Reference:** [Installation](https://skip.dev/docs/gettingstarted/) , [Skip CLI References](https://skip.dev/docs/skip-cli/) ### SkipUI [Section titled “SkipUI”](https://skip.dev/docs/glossary/#skip-ui) A Skip framework that bridges [SwiftUI](https://skip.dev/docs/glossary/#swiftui) to [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) , allowing SwiftUI views to render natively on Android using Compose. Part of the core Skip framework stack. **Related Terms:** [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) **Reference:** [SkipUI](https://skip.dev/docs/modules/skip-ui/) ### SkipFoundation [Section titled “SkipFoundation”](https://skip.dev/docs/glossary/#skip-foundation) A Skip framework that provides Android implementations of [Swift Foundation](https://skip.dev/docs/glossary/#swift-foundation) APIs, enabling common functionality like date handling, networking, and data structures to work across both platforms. **Related Terms:** [Swift Foundation](https://skip.dev/docs/glossary/#swift-foundation) , [Transpiler](https://skip.dev/docs/glossary/#transpiler) **Reference:** [SkipFoundation](https://skip.dev/docs/modules/skip-foundation/) ### SkipLib [Section titled “SkipLib”](https://skip.dev/docs/glossary/#skip-lib) The core Skip library that provides fundamental transpilation support and runtime utilities for converting Swift code to [Kotlin](https://skip.dev/docs/glossary/#kotlin) . **Reference:** [SkipLib](https://skip.dev/docs/modules/skip-lib/) ### SkipBridge [Section titled “SkipBridge”](https://skip.dev/docs/glossary/#skip-bridge) A framework that facilitates seamless integration between [Swift](https://skip.dev/docs/glossary/#swift) and [Kotlin](https://skip.dev/docs/glossary/#kotlin) code, enabling direct calls between iOS and Android platform-specific implementations. The bridging is handled automatically and transparently by the [Skip Plugin](https://skip.dev/docs/glossary/#skipstone) , and is implemented through a combination of source processing by [Skip Lite](https://skip.dev/docs/glossary/#skip-lite) and [JNI](https://skip.dev/docs/glossary/#jni) interfaces generated by [Skip Fuse](https://skip.dev/docs/glossary/#skip-fuse) . **Related Terms:** [Platform Customization](https://skip.dev/docs/glossary/#platform-customization) **Reference:** [SkipBridge](https://skip.dev/docs/modules/skip-bridge/) ### SkipModel [Section titled “SkipModel”](https://skip.dev/docs/glossary/#skip-model) A Skip framework that provides cross-platform support for Swift’s [Observation](https://skip.dev/docs/glossary/#observation) framework by bridging it to the [Compose Runtime](https://skip.dev/docs/glossary/#compose-runtime) . SkipModel enables reactive state management patterns to work seamlessly across iOS and Android, allowing `@Observable` classes to trigger UI updates in both [SwiftUI](https://skip.dev/docs/glossary/#swiftui) and [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) . **Related Terms:** [Observation](https://skip.dev/docs/glossary/#observation) , [Compose Runtime](https://skip.dev/docs/glossary/#compose-runtime) , [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) **Reference:** [SkipModel](https://skip.dev/docs/modules/skip-model/) ### SkipUnit [Section titled “SkipUnit”](https://skip.dev/docs/glossary/#skip-unit) A Skip framework that bridges Apple’s [XCTest](https://skip.dev/docs/glossary/#xctest) framework to Android’s [JUnit](https://skip.dev/docs/glossary/#junit) testing framework, enabling developers to write tests once in Swift and have them automatically run on both platforms. SkipUnit is essential for [parity testing](https://skip.dev/docs/glossary/#parity-testing) in Skip projects. **Related Terms:** [XCTest](https://skip.dev/docs/glossary/#xctest) , [JUnit](https://skip.dev/docs/glossary/#junit) , [Parity Testing](https://skip.dev/docs/glossary/#parity-testing) , [Unit Testing](https://skip.dev/docs/glossary/#unit-testing) **Reference:** [SkipUnit](https://skip.dev/docs/modules/skip-unit/) ### Skip Zero [Section titled “Skip Zero”](https://skip.dev/docs/glossary/#skip-zero) A mode in [Skip Lite](https://skip.dev/docs/glossary/#skip-lite) that enables the iOS side of the app to strip out all Skip dependencies, meaning your iOS app has no runtime dependencies on any Skip frameworks. This results in minimal app size and memory footprint and eliminates any reliance on Skip for the iOS side of the app. **Related Terms:** [Skip Lite](https://skip.dev/docs/glossary/#skip-lite) ### Ejectability [Section titled “Ejectability”](https://skip.dev/docs/glossary/#ejectability) When using [Skip Zero](https://skip.dev/docs/glossary/#skip-zero) with [Skip Lite](https://skip.dev/docs/glossary/#skip-lite) , _ejectability_ refers to the potential to remove Skip completely from a project and still have fully-functional iOS and Kotlin apps that can be iterated on separately. It de-risks the adoption of Skip by providing an insurance policy for technology developed with Skip that it will continue to be viable going forward even if the project decides against continuing with Skip in the future. **Related Terms:** [Skip Lite](https://skip.dev/docs/glossary/#skip-lite) ### Transpiler [Section titled “Transpiler”](https://skip.dev/docs/glossary/#transpiler) A source-to-source compiler that translates code from one programming language to another. Skip Lite’s transpiler converts [Swift](https://skip.dev/docs/glossary/#swift) code to [Kotlin](https://skip.dev/docs/glossary/#kotlin) and [SwiftUI](https://skip.dev/docs/glossary/#swiftui) views to [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) components. Transpilation is one of the functions of the [Skip Plugin](https://skip.dev/docs/glossary/#skipstone) . **Related Terms:** [Swift](https://skip.dev/docs/glossary/#swift) , [Kotlin](https://skip.dev/docs/glossary/#kotlin) , [Compiler](https://skip.dev/docs/glossary/#compiler) **External Resources:** * [Transpiler Definition (Wikipedia) ↗](https://en.wikipedia.org/wiki/Source-to-source_compiler) ### Platform Customization [Section titled “Platform Customization”](https://skip.dev/docs/glossary/#platform-customization) The ability to write platform-specific code for iOS and Android within a Skip project, allowing developers to leverage native APIs and frameworks when shared code isn’t sufficient. **Related Terms:** [SkipBridge](https://skip.dev/docs/glossary/#skip-bridge) , [Conditional Compilation](https://skip.dev/docs/glossary/#conditional-compilation) **Learn More:** [Cross-Platform Topics Guide](https://skip.dev/docs/platformcustomization/) ### Dual-Platform Development [Section titled “Dual-Platform Development”](https://skip.dev/docs/glossary/#dual-platform-development) The practice of developing for both iOS and Android simultaneously from a single codebase, which is Skip’s core value proposition. Skip facilitates using a single language ([Swift](https://skip.dev/docs/glossary/#swift) ) and a single [IDE](https://skip.dev/docs/glossary/#ide) ([Xcode](https://skip.dev/docs/glossary/#xcode) ) to iteratively develop an app for both [iOS](https://skip.dev/docs/glossary/#ios) and [Android](https://skip.dev/docs/glossary/#android) simultaneously. **Related Terms:** [Cross-Platform Development](https://skip.dev/docs/glossary/#cross-platform-development) ### Parity Testing [Section titled “Parity Testing”](https://skip.dev/docs/glossary/#parity-testing) A testing methodology unique to Skip where the same test cases are executed on both iOS (via [XCTest](https://skip.dev/docs/glossary/#xctest) ) and Android (via [JUnit](https://skip.dev/docs/glossary/#junit) ) to verify identical behavior across platforms. Parity testing ensures that shared business logic produces consistent results on both platforms and helps catch platform-specific bugs early in development. The [Skip CLI](https://skip.dev/docs/glossary/#skip-cli) includes a `skip test` command that runs parity tests and generates a comparison report. **Related Terms:** [Unit Testing](https://skip.dev/docs/glossary/#unit-testing) , [XCTest](https://skip.dev/docs/glossary/#xctest) , [JUnit](https://skip.dev/docs/glossary/#junit) , [Skip CLI](https://skip.dev/docs/glossary/#skip-cli) **Learn More:** [Testing Documentation](https://skip.dev/docs/testing/) * * * iOS Development Terms --------------------- [Section titled “iOS Development Terms”](https://skip.dev/docs/glossary/#ios-development-terms) ### Xcode [Section titled “Xcode”](https://skip.dev/docs/glossary/#xcode) Apple’s integrated development environment (IDE) for macOS, used to develop software for iOS, macOS, watchOS, and tvOS. Xcode is the primary IDE for Skip development, which relies on the [Skip Build Plugin](https://skip.dev/docs/glossary/#skipstone) to automatically translate iOS projects in Android projects whenever the project is built. **Related Terms:** [Swift](https://skip.dev/docs/glossary/#swift) , [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [IDE](https://skip.dev/docs/glossary/#ide) **External Resources:** * [Xcode Official Page ↗](https://developer.apple.com/xcode/) * [Xcode Documentation ↗](https://developer.apple.com/documentation/xcode) ### Swift [Section titled “Swift”](https://skip.dev/docs/glossary/#swift) A modern, type-safe programming language developed by Apple for iOS, macOS, and other Apple platforms. Swift is the primary language used in Skip development for writing code that runs on both iOS and Android. **Related Terms:** [Xcode](https://skip.dev/docs/glossary/#xcode) , [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [Kotlin](https://skip.dev/docs/glossary/#kotlin) **External Resources:** * [Swift Official Website ↗](https://swift.org/) * [Swift Documentation ↗](https://docs.swift.org/) * [Swift Language Guide ↗](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/) ### Swift SDK for Android [Section titled “Swift SDK for Android”](https://skip.dev/docs/glossary/#ssdk4a) The officially-supported cross-compilation SDK that can compile Swift code to the Android platform and is distributed by swift.org. The Swift SDK for Android was jointly developed by the Skip team alongside Swift community members and was officially released in 2025. **Related Terms:** [Skip Fuse](https://skip.dev/docs/glossary/#skip-fuse) , [Swift](https://skip.dev/docs/glossary/#swift) **See Also:** * [An official Swift SDK for Android (Skip Blog)](https://skip.dev/blog/official-swift-sdk-for-android/) **External Resources:** * [Getting started with the Swift SDK for Android ↗](https://www.swift.org/documentation/articles/swift-sdk-for-android-getting-started.html) * [Announcing the Swift SDK for Android ↗](https://www.swift.org/blog/nightly-swift-sdk-for-android/) ### SwiftUI [Section titled “SwiftUI”](https://skip.dev/docs/glossary/#swiftui) Apple’s declarative UI framework for building user interfaces across all Apple platforms. In Skip, SwiftUI code is used to create UIs that render natively as SwiftUI on iOS and [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) on Android. **Related Terms:** [Swift](https://skip.dev/docs/glossary/#swift) , [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) , [Declarative UI](https://skip.dev/docs/glossary/#declarative-ui) **External Resources:** * [SwiftUI Official Page ↗](https://developer.apple.com/xcode/swiftui/) * [SwiftUI Documentation ↗](https://developer.apple.com/documentation/swiftui/) * [SwiftUI Tutorials ↗](https://developer.apple.com/tutorials/swiftui) ### Swift Foundation [Section titled “Swift Foundation”](https://skip.dev/docs/glossary/#swift-foundation) Apple’s fundamental framework providing essential data types, collections, and operating system services. [Skip Foundation](https://skip.dev/docs/glossary/#skip-foundation) provides Android implementations of many Foundation APIs. **Related Terms:** [Swift](https://skip.dev/docs/glossary/#swift) , [SkipFoundation](https://skip.dev/docs/glossary/#skip-foundation) **External Resources:** * [Foundation Documentation ↗](https://developer.apple.com/documentation/foundation) * [Swift Foundation on GitHub ↗](https://github.com/apple/swift-foundation) ### Observation [Section titled “Observation”](https://skip.dev/docs/glossary/#observation) A Swift framework introduced in iOS 17 and macOS 14 that provides a modern approach to observable state management using the `@Observable` macro. [SkipModel](https://skip.dev/docs/glossary/#skip-model) bridges Observation to Android’s [Compose Runtime](https://skip.dev/docs/glossary/#compose-runtime) , allowing observable patterns to work across both platforms in Skip apps. **Related Terms:** [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [SkipModel](https://skip.dev/docs/glossary/#skip-model) , [Compose Runtime](https://skip.dev/docs/glossary/#compose-runtime) **External Resources:** * [Observation Framework Documentation ↗](https://developer.apple.com/documentation/observation) ### SwiftPM (Swift Package Manager) [Section titled “SwiftPM (Swift Package Manager)”](https://skip.dev/docs/glossary/#swiftpm) Apple’s official dependency manager for Swift projects, integrated into [Xcode](https://skip.dev/docs/glossary/#xcode) . Skip projects use SwiftPM to manage both iOS and Android dependencies. **Related Terms:** [Swift](https://skip.dev/docs/glossary/#swift) , [Xcode](https://skip.dev/docs/glossary/#xcode) , [Package Manager](https://skip.dev/docs/glossary/#package-manager) **External Resources:** * [Swift Package Manager Documentation ↗](https://www.swift.org/documentation/package-manager/) * [Swift Package Index ↗](https://swiftpackageindex.com/) ### UIKit [Section titled “UIKit”](https://skip.dev/docs/glossary/#uikit) Apple’s older imperative UI framework for iOS development, predating [SwiftUI](https://skip.dev/docs/glossary/#swiftui) . Skip focuses on SwiftUI rather than UIKit. **Related Terms:** [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [Declarative UI](https://skip.dev/docs/glossary/#declarative-ui) , [Imperative UI](https://skip.dev/docs/glossary/#imperative-ui) **External Resources:** * [UIKit Documentation ↗](https://developer.apple.com/documentation/uikit) ### iOS [Section titled “iOS”](https://skip.dev/docs/glossary/#ios) Apple’s mobile operating system that powers iPhones and iPads. Skip enables developers to build iOS apps that also run natively on [Android](https://skip.dev/docs/glossary/#android) . **Related Terms:** [Android](https://skip.dev/docs/glossary/#android) , [Xcode](https://skip.dev/docs/glossary/#xcode) **External Resources:** * [iOS Developer Page ↗](https://developer.apple.com/ios/) ### Liquid Glass [Section titled “Liquid Glass”](https://skip.dev/docs/glossary/#liquid-glass) Apple’s next-generation user interface released for iOS 26. Apple’s system-wide visual design language introduced in iOS 26, representing an aesthetic overhaul that replaces flat elements with a “digital meta-material” characterized by dynamic translucency, real-time light refraction, and fluid motion. Skip apps fully support Liquid Glass. **See Also:** * [Skip and the next generation of mobile user interfaces (Skip Blog)](https://skip.dev/blog/skip-next-gen-mobile-ui/) **External Resources:** * [Leverage Liquid Glass (Apple Developer) ↗](https://developer.apple.com/documentation/TechnologyOverviews/liquid-glass) * [Adopting Liquid Glass (Apple Developer) ↗](https://developer.apple.com/documentation/technologyoverviews/adopting-liquid-glass) ### Apple App Store [Section titled “Apple App Store”](https://skip.dev/docs/glossary/#apple-app-store) Apple’s digital distribution platform for iOS apps. Skip-built apps can be published to the App Store alongside [Google Play Store](https://skip.dev/docs/glossary/#google-play-store) distribution. **Related Terms:** [Google Play Store](https://skip.dev/docs/glossary/#google-play-store) **External Resources:** * [App Store Connect ↗](https://appstoreconnect.apple.com/) * [App Store Review Guidelines ↗](https://developer.apple.com/app-store/review/guidelines/) ### XCTest [Section titled “XCTest”](https://skip.dev/docs/glossary/#xctest) Apple’s testing framework for writing unit tests and UI tests in Swift and Objective-C. In Skip projects, XCTest cases are automatically transpiled or bridged to [JUnit](https://skip.dev/docs/glossary/#junit) tests via [SkipUnit](https://skip.dev/docs/glossary/#skip-unit) , enabling [parity testing](https://skip.dev/docs/glossary/#parity-testing) across both platforms. **Related Terms:** [Unit Testing](https://skip.dev/docs/glossary/#unit-testing) , [JUnit](https://skip.dev/docs/glossary/#junit) , [SkipUnit](https://skip.dev/docs/glossary/#skip-unit) , [Swift](https://skip.dev/docs/glossary/#swift) **External Resources:** * [XCTest Documentation ↗](https://developer.apple.com/documentation/xctest) * * * Android Development Terms ------------------------- [Section titled “Android Development Terms”](https://skip.dev/docs/glossary/#android-development-terms) ### Android [Section titled “Android”](https://skip.dev/docs/glossary/#android) Google’s open-source mobile operating system powering billions of devices worldwide. Skip transpiles iOS apps to run natively on Android using [Kotlin](https://skip.dev/docs/glossary/#kotlin) and [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) . **Related Terms:** [iOS](https://skip.dev/docs/glossary/#ios) , [Kotlin](https://skip.dev/docs/glossary/#kotlin) , [Android Studio](https://skip.dev/docs/glossary/#android-studio) **External Resources:** * [Android Official Website ↗](https://www.android.com/) * [Android Developers ↗](https://developer.android.com/) ### Android Studio [Section titled “Android Studio”](https://skip.dev/docs/glossary/#android-studio) The official integrated development environment (IDE) for Android development, based on IntelliJ IDEA. While Skip’s primary IDE is [Xcode](https://skip.dev/docs/glossary/#xcode) , Android Studio can be used for advanced Android customization. **Related Terms:** [Xcode](https://skip.dev/docs/glossary/#xcode) , [IDE](https://skip.dev/docs/glossary/#ide) , [Kotlin](https://skip.dev/docs/glossary/#kotlin) **External Resources:** * [Android Studio Official Page ↗](https://developer.android.com/studio) * [Android Studio User Guide ↗](https://developer.android.com/studio/intro) ### Kotlin [Section titled “Kotlin”](https://skip.dev/docs/glossary/#kotlin) A modern, statically-typed programming language that runs on the JVM and is Google’s preferred language for Android development. Skip’s [transpiler](https://skip.dev/docs/glossary/#transpiler) converts [Swift](https://skip.dev/docs/glossary/#swift) code to Kotlin for Android. **Related Terms:** [Swift](https://skip.dev/docs/glossary/#swift) , [Java](https://skip.dev/docs/glossary/#java) , [Transpiler](https://skip.dev/docs/glossary/#transpiler) **External Resources:** * [Kotlin Official Website ↗](https://kotlinlang.org/) * [Kotlin Documentation ↗](https://kotlinlang.org/docs/home.html) * [Kotlin for Android ↗](https://developer.android.com/kotlin) ### Jetpack Compose [Section titled “Jetpack Compose”](https://skip.dev/docs/glossary/#jetpack-compose) Android’s modern declarative UI toolkit, similar to [SwiftUI](https://skip.dev/docs/glossary/#swiftui) . Skip translates SwiftUI views into Jetpack Compose components, producing native Android UIs. **Related Terms:** [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [Kotlin](https://skip.dev/docs/glossary/#kotlin) , [Declarative UI](https://skip.dev/docs/glossary/#declarative-ui) **External Resources:** * [Jetpack Compose Official Page ↗](https://developer.android.com/jetpack/compose) * [Compose Documentation ↗](https://developer.android.com/jetpack/compose/documentation) * [Compose Tutorial ↗](https://developer.android.com/jetpack/compose/tutorial) ### Android Views [Section titled “Android Views”](https://skip.dev/docs/glossary/#android-views) Android’s original imperative UI toolkit, similar to [UIKit](https://skip.dev/docs/glossary/#uikit) on iOS. Android Views are sometimes known as _XML Views_ and have been supplanted by Jetpack Compose as the recommended technology for building modern Android apps. **Related Terms:** [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) , [Java](https://skip.dev/docs/glossary/#java) , [Imperative UI](https://skip.dev/docs/glossary/#imperative-ui) ### Gradle [Section titled “Gradle”](https://skip.dev/docs/glossary/#gradle) A build automation system used for Android projects. Skip-generated Android projects use Gradle for building, testing, and dependency management for the Android side of the app. Gradle can be thought of as the Java/Kotlin equivalent for the [Swift Package Manager](https://skip.dev/docs/glossary/#swiftpm) . **Related Terms:** [Android Studio](https://skip.dev/docs/glossary/#android-studio) , [Build System](https://skip.dev/docs/glossary/#build-system) , [SwiftPM](https://skip.dev/docs/glossary/#swiftpm) **External Resources:** * [Gradle Official Website ↗](https://gradle.org/) * [Gradle User Manual ↗](https://docs.gradle.org/) * [Android Gradle Plugin ↗](https://developer.android.com/build) ### Fastlane [Section titled “Fastlane”](https://skip.dev/docs/glossary/#fastlane) A suite of open source tools designed to automate the process of building and distributing mobile apps for both the Apple App Store and the Google Play Store. It streamlines tasks such as generating screenshots, handling code signing, managing app metadata, and uploading builds to app stores. Skip will automatically generate fastlane project templates when creating an app project with `skip create` or `skip init`. **Reference:** [Deployment](https://skip.dev/docs/deployment/) , [Skip CLI References](https://skip.dev/docs/skip-cli/) **External Resources:** * [Fastlane Documentation ↗](https://docs.fastlane.tools/) **See Also:** * [Going the last mile with Skip and Fastlane (Skip Blog)](https://skip.dev/blog/skip-and-fastlane/) ### Android SDK (Software Development Kit) [Section titled “Android SDK (Software Development Kit)”](https://skip.dev/docs/glossary/#android-sdk) A collection of tools, libraries, and APIs required for developing Android applications. Skip developers need the Android SDK installed for building Android versions of their apps. **Related Terms:** [Android Studio](https://skip.dev/docs/glossary/#android-studio) , [Gradle](https://skip.dev/docs/glossary/#gradle) **External Resources:** * [Android SDK Documentation ↗](https://developer.android.com/studio/releases/platforms) * [SDK Manager Guide ↗](https://developer.android.com/studio/intro/update#sdk-manager) ### JVM (Java Virtual Machine) [Section titled “JVM (Java Virtual Machine)”](https://skip.dev/docs/glossary/#jvm) The runtime environment that executes Java and [Kotlin](https://skip.dev/docs/glossary/#kotlin) bytecode. Android apps written in Kotlin run on the Android Runtime (ART), which is based on JVM principles. **Related Terms:** [Kotlin](https://skip.dev/docs/glossary/#kotlin) , [Java](https://skip.dev/docs/glossary/#java) , [Android Runtime](https://skip.dev/docs/glossary/#art) **External Resources:** * [JVM Specification ↗](https://docs.oracle.com/javase/specs/jvms/se17/html/index.html) ### JNI (Java Native Interface) [Section titled “JNI (Java Native Interface)”](https://skip.dev/docs/glossary/#jni) A foreign function interface designed for non-Java programming languages (like [Swift](https://skip.dev/docs/glossary/#swift) ) to interface with [Java](https://skip.dev/docs/glossary/#java) libraries. JNI is a necessary part of [Skip Fuse](https://skip.dev/docs/glossary/#skip-fuse) ’s ability to create bridges to the [Android SDK](https://skip.dev/docs/glossary/#android-sdk) and thereby enable the creation of useful apps on the [Android](https://skip.dev/docs/glossary/#android) platform. **Related Terms:** [Kotlin](https://skip.dev/docs/glossary/#kotlin) , [Java](https://skip.dev/docs/glossary/#java) , [Android Runtime](https://skip.dev/docs/glossary/#art) **External Resources:** * [Java Native Interface (Wikipedia) ↗](https://en.wikipedia.org/wiki/Java_Native_Interface) ### Android Runtime (ART) [Section titled “Android Runtime (ART)”](https://skip.dev/docs/glossary/#art) Android’s managed runtime environment that executes app code. ART replaced Dalvik and provides improved performance through ahead-of-time (AOT) compilation. **Related Terms:** [JVM](https://skip.dev/docs/glossary/#jvm) , [Kotlin](https://skip.dev/docs/glossary/#kotlin) **External Resources:** * [ART and Dalvik ↗](https://source.android.com/docs/core/runtime) ### Google Play Store [Section titled “Google Play Store”](https://skip.dev/docs/glossary/#google-play-store) Google’s official app distribution platform for Android. Skip-built apps can be published to Google Play alongside [App Store](https://skip.dev/docs/glossary/#apple-app-store) distribution. **Related Terms:** [Apple App Store](https://skip.dev/docs/glossary/#apple-app-store) , [Android](https://skip.dev/docs/glossary/#android) **External Resources:** * [Google Play Console ↗](https://play.google.com/console) * [Google Play Policies ↗](https://play.google.com/about/developer-content-policy/) ### Java [Section titled “Java”](https://skip.dev/docs/glossary/#java) A widely-used programming language for Android development before [Kotlin](https://skip.dev/docs/glossary/#kotlin) became Google’s preferred language. Both Kotlin and Java target the [JVM](https://skip.dev/docs/glossary/#jvm) and [Android Runtime](https://skip.dev/docs/glossary/#art) . Skip’s transpilation and bridging targets Kotlin rather than Java. **Related Terms:** [Kotlin](https://skip.dev/docs/glossary/#kotlin) , [JVM](https://skip.dev/docs/glossary/#jvm) , [Android Runtime](https://skip.dev/docs/glossary/#art) **External Resources:** * [Java Official Website ↗](https://www.java.com/) * [Java for Android ↗](https://developer.android.com/guide/platform) ### Robolectric [Section titled “Robolectric”](https://skip.dev/docs/glossary/#robolectric) A mode of unit testing Android frameworks where tests execute locally on a JVM rather then connecting to an Android emulator or device. Rests run with Robolectric are more lightweight and run faster than connected tests, but do not cover the complete Android API surface and do not support testing user-interface components. **Related Terms:** [Unit Testing](https://skip.dev/docs/glossary/#unit-testing) , [JVM](https://skip.dev/docs/glossary/#jvm) , [Android Runtime](https://skip.dev/docs/glossary/#art) **External Resources:** * [Robolectric Website ↗](https://robolectric.org/) ### JUnit [Section titled “JUnit”](https://skip.dev/docs/glossary/#junit) A widely-used testing framework for Java and [Kotlin](https://skip.dev/docs/glossary/#kotlin) applications. In Skip projects, [XCTest](https://skip.dev/docs/glossary/#xctest) cases are automatically converted to JUnit tests via [SkipUnit](https://skip.dev/docs/glossary/#skip-unit) , enabling the same tests to run on both iOS and Android. **Related Terms:** [Unit Testing](https://skip.dev/docs/glossary/#unit-testing) , [XCTest](https://skip.dev/docs/glossary/#xctest) , [SkipUnit](https://skip.dev/docs/glossary/#skip-unit) , [Kotlin](https://skip.dev/docs/glossary/#kotlin) , [Robolectric](https://skip.dev/docs/glossary/#robolectric) **External Resources:** * [JUnit Official Website ↗](https://junit.org/) * [JUnit 4 Documentation ↗](https://junit.org/junit4/) ### Compose Runtime [Section titled “Compose Runtime”](https://skip.dev/docs/glossary/#compose-runtime) The reactive state management system that underlies [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) . Compose Runtime handles state observation, recomposition, and UI updates. [SkipModel](https://skip.dev/docs/glossary/#skip-model) bridges Swift’s [Observation](https://skip.dev/docs/glossary/#observation) framework to Compose Runtime, enabling reactive patterns to work across both platforms. **Related Terms:** [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) , [SkipModel](https://skip.dev/docs/glossary/#skip-model) , [Observation](https://skip.dev/docs/glossary/#observation) **External Resources:** * [Compose Runtime Documentation ↗](https://developer.android.com/jetpack/androidx/releases/compose-runtime) ### ADB (Android Debug Bridge) [Section titled “ADB (Android Debug Bridge)”](https://skip.dev/docs/glossary/#adb) A command-line tool that facilitates communication between a development computer and Android devices or emulators. Skip developers use ADB for tasks like viewing logs (`adb logcat`), installing apps, and managing connected devices. The `ANDROID_SERIAL` environment variable can be set to target specific devices when running Skip tests. **Related Terms:** [Android SDK](https://skip.dev/docs/glossary/#android-sdk) , [Android Studio](https://skip.dev/docs/glossary/#android-studio) , [Gradle](https://skip.dev/docs/glossary/#gradle) **External Resources:** * [ADB Documentation ↗](https://developer.android.com/tools/adb) ### APK (Android Package Kit) [Section titled “APK (Android Package Kit)”](https://skip.dev/docs/glossary/#apk) The package file format used to distribute and install applications on Android devices. Skip’s `skip export` command generates an APK file containing the compiled Android version of your app. APK files are analogous to iOS’s `.ipa` files. **Related Terms:** [Android](https://skip.dev/docs/glossary/#android) , [Google Play Store](https://skip.dev/docs/glossary/#google-play-store) , [Gradle](https://skip.dev/docs/glossary/#gradle) **External Resources:** * [APK Format Documentation ↗](https://developer.android.com/guide/components/fundamentals) * * * General Development Concepts ---------------------------- [Section titled “General Development Concepts”](https://skip.dev/docs/glossary/#general-development-concepts) ### Cross-Platform Development [Section titled “Cross-Platform Development”](https://skip.dev/docs/glossary/#cross-platform-development) The practice of building applications that run on multiple operating systems or platforms from a single codebase. Skip is a cross-platform solution specifically designed for iOS and Android. **Related Terms:** [Dual-Platform Development](https://skip.dev/docs/glossary/#dual-platform-development) , [Native Development](https://skip.dev/docs/glossary/#native-development) ### Native Development [Section titled “Native Development”](https://skip.dev/docs/glossary/#native-development) Building applications using platform-specific languages, frameworks, and tools designed for a particular operating system. Skip produces truly native apps for both iOS and Android. **Related Terms:** [Cross-Platform Development](https://skip.dev/docs/glossary/#cross-platform-development) ### Declarative UI [Section titled “Declarative UI”](https://skip.dev/docs/glossary/#declarative-ui) A programming paradigm where UI is described as a function of state, focusing on _what_ the UI should look like rather than _how_ to construct it. Both [SwiftUI](https://skip.dev/docs/glossary/#swiftui) and [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) are declarative UI frameworks. **Related Terms:** [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) , [Imperative UI](https://skip.dev/docs/glossary/#imperative-ui) **External Resources:** * [Declarative Programming (Wikipedia) ↗](https://en.wikipedia.org/wiki/Declarative_programming) ### Imperative UI [Section titled “Imperative UI”](https://skip.dev/docs/glossary/#imperative-ui) A programming paradigm where UI is constructed through explicit step-by-step instructions. [UIKit](https://skip.dev/docs/glossary/#uikit) on iOS is an example of imperative UI. **Related Terms:** [Declarative UI](https://skip.dev/docs/glossary/#declarative-ui) , [UIKit](https://skip.dev/docs/glossary/#uikit) , [Android Views](https://skip.dev/docs/glossary/#android-views) ### IDE (Integrated Development Environment) [Section titled “IDE (Integrated Development Environment)”](https://skip.dev/docs/glossary/#ide) A software application providing comprehensive facilities for software development, typically including a code editor, debugger, and build tools. [Xcode](https://skip.dev/docs/glossary/#xcode) and [Android Studio](https://skip.dev/docs/glossary/#android-studio) are IDEs. **Related Terms:** [Xcode](https://skip.dev/docs/glossary/#xcode) , [Android Studio](https://skip.dev/docs/glossary/#android-studio) ### Compiler [Section titled “Compiler”](https://skip.dev/docs/glossary/#compiler) A program that translates source code from a high-level programming language to machine code or intermediate code. Unlike a [transpiler](https://skip.dev/docs/glossary/#transpiler) , which translates to another high-level language. **Related Terms:** [Transpiler](https://skip.dev/docs/glossary/#transpiler) , [Build System](https://skip.dev/docs/glossary/#build-system) ### Build System [Section titled “Build System”](https://skip.dev/docs/glossary/#build-system) Software that automates the process of compiling source code into executable programs. [Gradle](https://skip.dev/docs/glossary/#gradle) is Android’s build system, while [SwiftPM](https://skip.dev/docs/glossary/#swiftpm) and Xcode’s build system handle iOS builds. **Related Terms:** [Gradle](https://skip.dev/docs/glossary/#gradle) , [SwiftPM](https://skip.dev/docs/glossary/#swiftpm) , [Compiler](https://skip.dev/docs/glossary/#compiler) ### Package Manager [Section titled “Package Manager”](https://skip.dev/docs/glossary/#package-manager) A tool that automates the process of installing, upgrading, and managing software dependencies. [SwiftPM](https://skip.dev/docs/glossary/#swiftpm) manages Swift dependencies, while [Gradle](https://skip.dev/docs/glossary/#gradle) handles Android dependencies. **Related Terms:** [SwiftPM](https://skip.dev/docs/glossary/#swiftpm) , [Gradle](https://skip.dev/docs/glossary/#gradle) ### Homebrew [Section titled “Homebrew”](https://skip.dev/docs/glossary/#homebrew) A package manager for macOS (and Linux) used to install and manage software packages. The [Skip CLI](https://skip.dev/docs/glossary/#skip-cli) and its prerequisites are distributed via Homebrew using the command `brew install skiptools/skip/skip`. **Related Terms:** [Skip CLI](https://skip.dev/docs/glossary/#skip-cli) , [Package Manager](https://skip.dev/docs/glossary/#package-manager) **External Resources:** * [Homebrew Official Website ↗](https://brew.sh/) * [Homebrew Documentation ↗](https://docs.brew.sh/) ### Conditional Compilation [Section titled “Conditional Compilation”](https://skip.dev/docs/glossary/#conditional-compilation) A preprocessor feature that allows including or excluding code based on compile-time conditions. Skip developers use conditional compilation to write platform-specific code for iOS vs. Android. **Related Terms:** [Platform Customization](https://skip.dev/docs/glossary/#platform-customization) **External Resources:** * [Swift Conditional Compilation ↗](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Conditional-Compilation-Block) ### API (Application Programming Interface) [Section titled “API (Application Programming Interface)”](https://skip.dev/docs/glossary/#api) A set of rules and protocols that allows different software components to communicate. Skip bridges iOS and Android APIs for [dual-platform development](https://skip.dev/docs/glossary/#dual-platform-development) . **Related Terms:** [Framework](https://skip.dev/docs/glossary/#framework) , [SDK](https://skip.dev/docs/glossary/#sdk) ### Framework [Section titled “Framework”](https://skip.dev/docs/glossary/#framework) A reusable software platform providing core functionality that can be extended by developers. [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) , and Skip’s own frameworks are examples. **Related Terms:** [API](https://skip.dev/docs/glossary/#api) , [Library](https://skip.dev/docs/glossary/#library) ### Library [Section titled “Library”](https://skip.dev/docs/glossary/#library) A collection of pre-written code that developers can use to optimize tasks. Unlike [frameworks](https://skip.dev/docs/glossary/#framework) , libraries are called by your code rather than calling your code. **Related Terms:** [Framework](https://skip.dev/docs/glossary/#framework) , [Package Manager](https://skip.dev/docs/glossary/#package-manager) ### SDK (Software Development Kit) [Section titled “SDK (Software Development Kit)”](https://skip.dev/docs/glossary/#sdk) A collection of tools, libraries, documentation, and samples that enable development for a specific platform. The [Android SDK](https://skip.dev/docs/glossary/#android-sdk) is required for Skip development. **Related Terms:** [API](https://skip.dev/docs/glossary/#api) , [Framework](https://skip.dev/docs/glossary/#framework) ### Runtime [Section titled “Runtime”](https://skip.dev/docs/glossary/#runtime) The period during which a program is executing, or the software environment that executes a program. Skip’s [Skip Zero](https://skip.dev/docs/glossary/#skip-zero) approach means no additional runtime is added to iOS apps. **Related Terms:** [JVM](https://skip.dev/docs/glossary/#jvm) , [Android Runtime](https://skip.dev/docs/glossary/#art) ### Source Code [Section titled “Source Code”](https://skip.dev/docs/glossary/#source-code) Human-readable program instructions written in a programming language. Skip’s [transpiler](https://skip.dev/docs/glossary/#transpiler) converts Swift source code to Kotlin source code. **Related Terms:** [Compiler](https://skip.dev/docs/glossary/#compiler) , [Transpiler](https://skip.dev/docs/glossary/#transpiler) ### Unit Testing [Section titled “Unit Testing”](https://skip.dev/docs/glossary/#unit-testing) A software testing technique in which individual units of code—such as functions, methods, or classes—are tested in isolation to verify that they behave as expected. Unit tests are typically automated and run frequently during development to catch regressions early. In Skip projects, unit testing is especially valuable because shared business logic written in Swift can be tested once and validated across both iOS and Android targets, increasing confidence in cross-platform correctness. **Related Terms:** [Integration Testing](https://skip.dev/docs/glossary/#integration-testing) , [Source Code](https://skip.dev/docs/glossary/#source-code) , [API](https://skip.dev/docs/glossary/#api) **External Resources:** * [Unit Testing (Wikipedia) ↗](https://en.wikipedia.org/wiki/Unit_testing) * [XCTest Documentation (Apple) ↗](https://developer.apple.com/documentation/xctest) * [JUnit Testing Framework (Android) ↗](https://developer.android.com/training/testing/local-tests) ### Integration Testing [Section titled “Integration Testing”](https://skip.dev/docs/glossary/#integration-testing) A software testing practice where multiple components or modules are tested together to verify their interactions work correctly. Unlike [unit testing](https://skip.dev/docs/glossary/#unit-testing) , which tests components in isolation, integration testing validates that different parts of the system integrate properly. In Skip projects, integration tests can verify that iOS and Android implementations interact correctly with platform-specific APIs and services. **Related Terms:** [Unit Testing](https://skip.dev/docs/glossary/#unit-testing) , [XCTest](https://skip.dev/docs/glossary/#xctest) , [JUnit](https://skip.dev/docs/glossary/#junit) **External Resources:** * [Integration Testing (Wikipedia) ↗](https://en.wikipedia.org/wiki/Integration_testing) ### Continuous Integration [Section titled “Continuous Integration”](https://skip.dev/docs/glossary/#continuous-integration) A development practice where code changes are automatically built and tested frequently, typically on every commit to version control. Skip projects support CI through standard tools like GitHub Actions, using the [Skip CLI](https://skip.dev/docs/glossary/#skip-cli) to run [parity tests](https://skip.dev/docs/glossary/#parity-testing) on both iOS and Android in automated workflows. **Related Terms:** [Unit Testing](https://skip.dev/docs/glossary/#unit-testing) , [Parity Testing](https://skip.dev/docs/glossary/#parity-testing) , [Build System](https://skip.dev/docs/glossary/#build-system) **External Resources:** * [Continuous Integration (Wikipedia) ↗](https://en.wikipedia.org/wiki/Continuous_integration) ### Localization [Section titled “Localization”](https://skip.dev/docs/glossary/#localization) The process of adapting an application to support multiple languages and regions. Skip supports localization through [SwiftUI](https://skip.dev/docs/glossary/#swiftui) ’s string catalog system (`.xcstrings` files), which are automatically processed for both iOS and Android. This enables a single translation source to work across both platforms. **Related Terms:** [SwiftUI](https://skip.dev/docs/glossary/#swiftui) , [Skip Plugin](https://skip.dev/docs/glossary/#skipstone) , [Platform Customization](https://skip.dev/docs/glossary/#platform-customization) **Learn More:** [Localization Documentation](https://skip.dev/docs/development-topics/#localization) **External Resources:** * [String Catalogs (Apple) ↗](https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog) Comparison Terms ---------------- [Section titled “Comparison Terms”](https://skip.dev/docs/glossary/#comparison-terms) ### Flutter [Section titled “Flutter”](https://skip.dev/docs/glossary/#flutter) An open-source UI framework developed by Google for building cross-platform applications from a single codebase written in Dart. Flutter renders its UI using a custom graphics engine rather than native platform UI frameworks. Compared to [Skip](https://skip.dev/docs/glossary/#skip) , Flutter has several notable disadvantages: * **Non-native UI rendering:** Flutter does not use [SwiftUI](https://skip.dev/docs/glossary/#swiftui) or [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) , which can lead to subtle differences in platform behavior, accessibility, and visual fidelity. * **Additional runtime and engine:** Flutter apps ship with a large runtime and rendering engine, increasing app size and memory usage compared to Skip’s native output. * **Limited platform integration:** Deep integration with platform-specific APIs often requires writing and maintaining platform “channels” in Swift/Objective-C and Kotlin/Java. * **Language fragmentation:** Flutter introduces Dart as a third language, whereas Skip allows iOS developers to remain entirely within the Swift ecosystem. Skip, by contrast, produces genuinely native apps using platform-standard UI frameworks and tooling, with no custom rendering engine and no mandatory additional runtime. **Related Terms:** [Skip](https://skip.dev/docs/glossary/#skip) , [Cross-Platform Development](https://skip.dev/docs/glossary/#cross-platform-development) , [Native Development](https://skip.dev/docs/glossary/#native-development) , [Runtime](https://skip.dev/docs/glossary/#runtime) **External Resources:** * [Flutter Official Website ↗](https://flutter.dev/) * [Flutter Documentation ↗](https://docs.flutter.dev/) * [Flutter Architecture Overview ↗](https://docs.flutter.dev/resources/architectural-overview) ### React Native [Section titled “React Native”](https://skip.dev/docs/glossary/#react-native) A cross-platform mobile development framework created by Meta (Facebook) that allows developers to build apps using JavaScript or TypeScript with a React-based UI model. Compared to [Skip](https://skip.dev/docs/glossary/#skip) , React Native presents several drawbacks: * **JavaScript bridge overhead:** React Native relies on a bridge (or JSI-based runtime) between JavaScript and native code, which can introduce performance bottlenecks and debugging complexity. * **Non-native development model:** While React Native renders native views, application logic runs in a JavaScript runtime rather than using platform-native languages like [Swift](https://skip.dev/docs/glossary/#swift) or [Kotlin](https://skip.dev/docs/glossary/#kotlin) . * **Toolchain fragmentation:** Developers must manage Node.js, npm/yarn, Metro, and native build systems in parallel, increasing project complexity. * **Weaker platform parity:** Platform-specific behaviors, APIs, and UI conventions often require custom native modules and duplicated logic. Skip avoids these issues by enabling development directly in Swift, integrating tightly with [Xcode](https://skip.dev/docs/glossary/#xcode) , producing native SwiftUI and Jetpack Compose UIs, and eliminating any JavaScript runtime or bridge. **Related Terms:** [Skip](https://skip.dev/docs/glossary/#skip) , [Cross-Platform Development](https://skip.dev/docs/glossary/#cross-platform-development) , [Native Development](https://skip.dev/docs/glossary/#native-development) , [Runtime](https://skip.dev/docs/glossary/#runtime) **External Resources:** * [React Native Official Website ↗](https://reactnative.dev/) * [React Native Documentation ↗](https://reactnative.dev/docs/getting-started) * [React Native Architecture ↗](https://reactnative.dev/docs/architecture-overview) ### Xamarin [Section titled “Xamarin”](https://skip.dev/docs/glossary/#xamarin) A cross-platform development framework (now part of .NET MAUI) that allows building iOS and Android apps using C# and .NET. Unlike [Skip](https://skip.dev/docs/glossary/#skip) , which uses native UI frameworks ([SwiftUI](https://skip.dev/docs/glossary/#swiftui) and [Jetpack Compose](https://skip.dev/docs/glossary/#jetpack-compose) ), Xamarin historically used either native UI bindings (Xamarin.iOS/Android) or a cross-platform UI abstraction (Xamarin.Forms, now .NET MAUI). Xamarin requires developers to learn C# rather than leveraging existing [Swift](https://skip.dev/docs/glossary/#swift) knowledge. **Contrast With:** [Skip](https://skip.dev/docs/glossary/#skip) , [Flutter](https://skip.dev/docs/glossary/#flutter) , [React Native](https://skip.dev/docs/glossary/#react-native) **Related Terms:** [Cross-Platform Development](https://skip.dev/docs/glossary/#cross-platform-development) **External Resources:** * [.NET MAUI Documentation ↗](https://dotnet.microsoft.com/apps/maui) ### Cordova [Section titled “Cordova”](https://skip.dev/docs/glossary/#cordova) A mobile application development framework (also known as Apache Cordova or PhoneGap) that enables building apps using HTML, CSS, and JavaScript running in a WebView. Unlike [Skip](https://skip.dev/docs/glossary/#skip) , which produces genuinely [native](https://skip.dev/docs/glossary/#native-development) apps, Cordova apps are essentially web applications wrapped in a native container, resulting in non-native UI, performance limitations, and restricted access to platform capabilities. **Contrast With:** [Skip](https://skip.dev/docs/glossary/#skip) , [Native Development](https://skip.dev/docs/glossary/#native-development) **Related Terms:** [Cross-Platform Development](https://skip.dev/docs/glossary/#cross-platform-development) , [Runtime](https://skip.dev/docs/glossary/#runtime) **External Resources:** * [Apache Cordova Website ↗](https://cordova.apache.org/) * * * Android and Apple Platforms --------------------------- [Section titled “Android and Apple Platforms”](https://skip.dev/docs/glossary/#android-and-apple-platforms) Developers familiar with iOS and other Apple platforms use a number of different tools and technologies, such as Swift, Xcode, and SwiftPM. When beginning to work with the Android platform, much of the terminology around Android development may seem alien at first. But in general, each component of iOS development has an analogue in the world of Android development. Skip handles translating some of these elements between Apple platforms and Android. This table outlines the various Apple-specific terms and maps them to their equivalent Android term, as well as highlighting whether Skip helps in the translation process. | Platform | Apple | \[Skip\] | Android | | --- | --- | --- | --- | | Language (modern) | Swift[1](https://skip.dev/docs/glossary/#user-content-fn-1) | → | Kotlin[2](https://skip.dev/docs/glossary/#user-content-fn-2) | | Language (legacy) | Objective-C[3](https://skip.dev/docs/glossary/#user-content-fn-3) | | Java[4](https://skip.dev/docs/glossary/#user-content-fn-4) | | UI Framework (modern) | SwiftUI[5](https://skip.dev/docs/glossary/#user-content-fn-5) | → | Jetpack Compose[6](https://skip.dev/docs/glossary/#user-content-fn-6) | | UI Framework (legacy) | UIKit[7](https://skip.dev/docs/glossary/#user-content-fn-7) | | Android Views[8](https://skip.dev/docs/glossary/#user-content-fn-8) | | Build Tool | SwiftPM[9](https://skip.dev/docs/glossary/#user-content-fn-9) | → | Gradle[10](https://skip.dev/docs/glossary/#user-content-fn-10) | | IDE | Xcode[11](https://skip.dev/docs/glossary/#user-content-fn-11) | | Android Studio[12](https://skip.dev/docs/glossary/#user-content-fn-12) | | Debugger | lldb[13](https://skip.dev/docs/glossary/#user-content-fn-13) | | adb[14](https://skip.dev/docs/glossary/#user-content-fn-14) | | Unit Testing | XCTest[15](https://skip.dev/docs/glossary/#user-content-fn-15) | → | JUnit[16](https://skip.dev/docs/glossary/#user-content-fn-16) | | UI Testing | XCUITest[17](https://skip.dev/docs/glossary/#user-content-fn-17) | | Espresso[18](https://skip.dev/docs/glossary/#user-content-fn-18) | | Local Testing | macOS[19](https://skip.dev/docs/glossary/#user-content-fn-19) | → | JVM w/ Robolectric[20](https://skip.dev/docs/glossary/#user-content-fn-20) | | Local App Runner | iOS Simulator[21](https://skip.dev/docs/glossary/#user-content-fn-21) | → | Android Emulator[22](https://skip.dev/docs/glossary/#user-content-fn-22) | | Design Language | HIG[23](https://skip.dev/docs/glossary/#user-content-fn-23) | | Material Design[24](https://skip.dev/docs/glossary/#user-content-fn-24) | Footnotes --------- [Section titled “Footnotes”](https://skip.dev/docs/glossary/#footnote-label) 1. Swift: [https://www.swift.org ↗](https://www.swift.org/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-1) 2. Kotlin: [https://kotlinlang.org ↗](https://kotlinlang.org/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-2) 3. Objective-C: [https://developer.apple.com/​library/​archive/​documentation/​Cocoa/​Conceptual/​ObjectiveC/​Introduction/​introObjectiveC.html ↗](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html) [↩](https://skip.dev/docs/glossary/#user-content-fnref-3) 4. Java: [https://www.java.com/en/download/help/whatis\_java.html ↗](https://www.java.com/en/download/help/whatis_java.html) [↩](https://skip.dev/docs/glossary/#user-content-fnref-4) 5. SwiftUI: [https://developer.apple.com/xcode/swiftui/ ↗](https://developer.apple.com/xcode/swiftui/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-5) 6. Jetpack Compose: [https://developer.android.com/develop/ui/compose ↗](https://developer.android.com/develop/ui/compose) [↩](https://skip.dev/docs/glossary/#user-content-fnref-6) 7. UIKit: [https://developer.apple.com/documentation/uikit ↗](https://developer.apple.com/documentation/uikit) [↩](https://skip.dev/docs/glossary/#user-content-fnref-7) 8. Android Views: [https://developer.android.com/develop/ui/views/layout/declaring-layout ↗](https://developer.android.com/develop/ui/views/layout/declaring-layout) [↩](https://skip.dev/docs/glossary/#user-content-fnref-8) 9. Swift Package Manager (SwiftPM): [https://www.swift.org/documentation/package-manager/ ↗](https://www.swift.org/documentation/package-manager/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-9) 10. Gradle: [https://gradle.org ↗](https://gradle.org/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-10) 11. Xcode: [https://developer.apple.com/xcode/ ↗](https://developer.apple.com/xcode/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-11) 12. Android Studio: [https://developer.android.com/studio ↗](https://developer.android.com/studio) [↩](https://skip.dev/docs/glossary/#user-content-fnref-12) 13. The LLDB Debugger: [https://lldb.llvm.org ↗](https://lldb.llvm.org/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-13) 14. Android Debug Bridge (adb): [https://developer.android.com/tools/adb ↗](https://developer.android.com/tools/adb) [↩](https://skip.dev/docs/glossary/#user-content-fnref-14) 15. XCTest: [https://developer.apple.com/documentation/xctest ↗](https://developer.apple.com/documentation/xctest) [↩](https://skip.dev/docs/glossary/#user-content-fnref-15) 16. JUnit: [https://junit.org/junit4/ ↗](https://junit.org/junit4/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-16) 17. User Interface Tests (XCUITest): [https://developer.apple.com/documentation/xctest/user\_interface\_tests ↗](https://developer.apple.com/documentation/xctest/user_interface_tests) [↩](https://skip.dev/docs/glossary/#user-content-fnref-17) 18. Espresso: [https://developer.android.com/training/testing/espresso/ ↗](https://developer.android.com/training/testing/espresso/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-18) 19. Cross-platform testing: [https://developer.apple.com/documentation/xcode/creating-a-standalone-swift-package-with-xcode#Make-your-Swift-package-cross-platform-compatible ↗](https://developer.apple.com/documentation/xcode/creating-a-standalone-swift-package-with-xcode#Make-your-Swift-package-cross-platform-compatible) [↩](https://skip.dev/docs/glossary/#user-content-fnref-19) 20. Robolectric: [https://robolectric.org ↗](https://robolectric.org/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-20) 21. iOS Simulator: [https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device/ ↗](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-21) 22. Android Emulator: [https://developer.android.com/studio/run/emulator ↗](https://developer.android.com/studio/run/emulator) [↩](https://skip.dev/docs/glossary/#user-content-fnref-22) 23. HIG: [https://developer.apple.com/design/human-interface-guidelines/ ↗](https://developer.apple.com/design/human-interface-guidelines/) [↩](https://skip.dev/docs/glossary/#user-content-fnref-23) 24. Material Design: [https://m3.material.io/develop/android/jetpack-compose ↗](https://m3.material.io/develop/android/jetpack-compose) [↩](https://skip.dev/docs/glossary/#user-content-fnref-24) --- # Shadow | Skip [Skip to content](https://skip.dev/docs/components/shadow/#_top) Shadow ====== Skip support for [SwiftUI.View.shadow ↗](https://developer.apple.com/documentation/swiftui/view/shadow(color:radius:x:y:)) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ShadowPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ShadowPlayground.swift) ![Android screenshot for Shadow component (light mode)](http://assets.skip.dev/showcase/Shadow-android-light_framed.png) ![iPhone screenshot for Shadow component (light mode)](http://assets.skip.dev/showcase/Shadow-iphone-light_framed.png) ![iPhone screenshot for Shadow component (dark mode)](http://assets.skip.dev/showcase/Shadow-iphone-dark_framed.png) ![Android screenshot for Shadow component (dark mode)](http://assets.skip.dev/showcase/Shadow-android-dark_framed.png) import SwiftUI struct ShadowPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text("Radius") Spacer() Color.red .frame(width: 100.0, height: 100.0) .shadow(radius: 10.0) .border(.blue) } HStack { Text("Color + Offset") Spacer() Color.red .frame(width: 100.0, height: 100.0) .shadow(color: .green, radius: 4.0, x: 10.0, y: 10.0) .border(.blue) } HStack { Text(".clipShape") Spacer() Color.red .frame(width: 100.0, height: 100.0) .clipShape(RoundedRectangle(cornerRadius: 40.0)) .shadow(color: .black, radius: 4.0) .border(.blue) } HStack { Text("Shape") Spacer() Circle() .fill(.red) .frame(width: 100.0, height: 100.0) .shadow(color: .black, radius: 4.0) .border(.blue) } HStack { Text("Text") Spacer() Text("Text").font(.largeTitle).bold() .foregroundStyle(.red) .shadow(color: .black, radius: 4.0) .border(.blue) } HStack { Text("Shape with background") Spacer() Circle() .fill(.red) .frame(width: 100.0, height: 100.0) .shadow(color: .black, radius: 4.0) .background { Color.green.shadow(color: .black, radius: 4.0) } .border(.blue) } HStack { Text("Text with background") Spacer() Text("Text").font(.largeTitle).bold() .foregroundStyle(.red) .padding(8.0) .shadow(color: .black, radius: 4.0) .background { Color.green.shadow(color: .black, radius: 4.0) } .border(.blue) } HStack { Text("Shape with overlay") Spacer() Circle() .fill(.red) .frame(width: 100.0, height: 100.0) .shadow(color: .black, radius: 4.0) .overlay { Text("Overlay").font(.largeTitle) .foregroundStyle(.green) .shadow(color: .black, radius: 4.0) } .border(.blue) } HStack { Text("Container") Spacer() VStack { Text("Top") Text("Bottom") } .padding(8.0) .shadow(color: .black, radius: 4.0) .border(.blue) } HStack { Text("Button") Spacer() Button("Tap") { logger.log("Tap") } .buttonStyle(.bordered) .shadow(color: .black, radius: 4.0) .border(.blue) } Toggle("Toggle", isOn: .constant(true)) .shadow(color: .black, radius: 4.0) .border(.blue) HStack { Text("Label") Spacer() Label("Title", systemImage: "heart.fill") .foregroundStyle(.red) .shadow(color: .black, radius: 4.0) .border(.blue) } HStack { Text("Image") Spacer() Image(systemName: "heart.fill") .foregroundStyle(.red) .shadow(color: .black, radius: 4.0) .border(.blue) } } .padding() } .toolbar { PlaygroundSourceLink(file: "ShadowPlayground.swift") } }} --- # Localization | Skip [Skip to content](https://skip.dev/docs/components/localization/#_top) Localization ============ Skip support for [SwiftUI Localization ↗](https://developer.apple.com/documentation/swiftui/preparing-views-for-localization) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`LocalizationPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/LocalizationPlayground.swift) ![Android screenshot for Localization component (light mode)](http://assets.skip.dev/showcase/Localization-android-light_framed.png) ![iPhone screenshot for Localization component (light mode)](http://assets.skip.dev/showcase/Localization-iphone-light_framed.png) ![iPhone screenshot for Localization component (dark mode)](http://assets.skip.dev/showcase/Localization-iphone-dark_framed.png) ![Android screenshot for Localization component (dark mode)](http://assets.skip.dev/showcase/Localization-android-dark_framed.png) import SwiftUIimport Foundation struct DemoPreviewView : View { var body: some View { VStack(spacing: 16.0) { Text("XYZ") Text("XYZ") Text("XYZ") Text("XYZ") Text("XYZ") } }} struct LocalizationPlayground: View { @Environment(\.locale) var currentLocale /// The list of available localizations in the current bundle static let bundleLocalizations: [Locale] = Bundle.module.localizations.map({ Locale(identifier: $0) }) var body: some View { List(Self.bundleLocalizations.sorted(by: { $0.identifier < $1.identifier }), id: \.self) { type in NavigationLink(type.localizedNavigationTitle, value: type) } .navigationDestination(for: Locale.self) { locale in LocalizationPreview().environment(\.locale, locale) } }} struct LocalizationPreview: View { @Environment(\.locale) var currentLocale @State var date = Date.now func formatter(dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style) -> DateFormatter { let fmt = DateFormatter() fmt.dateStyle = dateStyle fmt.timeStyle = timeStyle fmt.locale = self.currentLocale return fmt } var body: some View { VStack { Text("Welcome", bundle: .module) .font(.largeTitle) Text(verbatim: currentLocale.localizedString(forLanguageCode: currentLocale.languageCode ?? currentLocale.identifier) ?? "") .font(.title) Text(verbatim: currentLocale.localizedString(forRegionCode: currentLocale.regionCode ?? currentLocale.identifier) ?? "") .font(.title2) Text(verbatim: currentLocale.localizedString(forScriptCode: currentLocale.scriptCode ?? currentLocale.identifier) ?? "") .font(.title2) Divider() Text(verbatim: formatter(dateStyle: .full, timeStyle: .short).string(from: date)) Text(verbatim: formatter(dateStyle: .none, timeStyle: .full).string(from: date)) Text(verbatim: formatter(dateStyle: .long, timeStyle: .none).string(from: date)) Text(verbatim: formatter(dateStyle: .none, timeStyle: .long).string(from: date)) Text(verbatim: formatter(dateStyle: .medium, timeStyle: .none).string(from: date)) Text(verbatim: formatter(dateStyle: .none, timeStyle: .medium).string(from: date)) Text(verbatim: formatter(dateStyle: .short, timeStyle: .none).string(from: date)) Text(verbatim: formatter(dateStyle: .none, timeStyle: .short).string(from: date)) DatePicker("", selection: $date) } .navigationTitle(currentLocale.localizedString(forIdentifier: currentLocale.identifier) ?? "???") }} extension Locale { /// The title of the language as the current locale language's name for the locale followed by the language name in the language itself. E.g., `French: français` var localizedNavigationTitle: String { (Locale.current.localizedString(forIdentifier: identifier) ?? "") + ": " + (localizedString(forIdentifier: identifier) ?? "") }} --- # Menu | Skip [Skip to content](https://skip.dev/docs/components/menu/#_top) Menu ==== Skip support for [SwiftUI.Menu ↗](https://developer.apple.com/documentation/swiftui/menu) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`MenuPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/MenuPlayground.swift) ![Android screenshot for Menu component (light mode)](http://assets.skip.dev/showcase/Menu-android-light_framed.png) ![iPhone screenshot for Menu component (light mode)](http://assets.skip.dev/showcase/Menu-iphone-light_framed.png) ![iPhone screenshot for Menu component (dark mode)](http://assets.skip.dev/showcase/Menu-iphone-dark_framed.png) ![Android screenshot for Menu component (dark mode)](http://assets.skip.dev/showcase/Menu-android-dark_framed.png) import SwiftUI struct MenuPlayground: View { @State var primaryActionCount = 0 var body: some View { ScrollView { VStack(spacing: 16.0) { Menu("Default") { Button("Option 1") { logger.log("Option 1") } Button("Option 2") { logger.log("Option 2") } Button("Option 3") { logger.log("Option 3") } } Menu("PrimaryAction: \(primaryActionCount)") { Button("Option 1") { logger.log("Option 1") } Button("Option 2") { logger.log("Option 2") } Button("Option 3") { logger.log("Option 3") } } primaryAction: { primaryActionCount += 1 } Menu(".buttonStyle(.bordered)") { Button("Option 1") { logger.log("Option 1") } Button("Option 2") { logger.log("Option 2") } Button("Option 3") { logger.log("Option 3") } } .buttonStyle(.bordered) Menu { Button("Option 1") { logger.log("Option 1") } Button("Option 2") { logger.log("Option 2") } Button("Option 3") { logger.log("Option 3") } } label: { Label("Label", systemImage: "heart.fill") } Menu("Label items") { Button(action: { logger.log("Option 1") }) { Label("Option 1", systemImage: "heart.fill") } Button(action: { logger.log("Option 2") }) { Label("Option 2", systemImage: "heart.fill") } Button(action: { logger.log("Option 3") }) { Label("Option 3", systemImage: "heart.fill") } } Menu("Text & Divider") { Text("Text") Button("Option 1") { logger.log("Option 1") } Divider() Button("Option 2") { logger.log("Option 2") } Button("Option 3") { logger.log("Option 3") } } Menu("Section") { Section("Section") { Button("Option 1") { logger.log("Option 1") } } Button("Option 2") { logger.log("Option 2") } Button("Option 3") { logger.log("Option 3") } } Menu("Nested menu") { Menu("Nested") { Button("Option 1") { logger.log("Option 1") } Button("Option 2") { logger.log("Option 2") } Menu("Nested Again") { Button("Option 3") { logger.log("Option 3") } } } Button("Option 3") { logger.log("Option 3") } Button("Option 4") { logger.log("Option 4") } } } .toolbar { Menu("Menu") { Button("Option 1") { logger.log("Option 1") } Button("Option 2") { logger.log("Option 2") } Button("Option 3") { logger.log("Option 3") } } } .padding() } .toolbar { PlaygroundSourceLink(file: "LabelPlayground.swift") } }} --- # Gesture | Skip [Skip to content](https://skip.dev/docs/components/gesture/#_top) Gesture ======= Skip support for [SwiftUI.Gesture ↗](https://developer.apple.com/documentation/swiftui/gesture) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`GesturePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/GesturePlayground.swift) ![Android screenshot for Gesture component (light mode)](http://assets.skip.dev/showcase/Gesture-android-light_framed.png) ![iPhone screenshot for Gesture component (light mode)](http://assets.skip.dev/showcase/Gesture-iphone-light_framed.png) ![iPhone screenshot for Gesture component (dark mode)](http://assets.skip.dev/showcase/Gesture-iphone-dark_framed.png) ![Android screenshot for Gesture component (dark mode)](http://assets.skip.dev/showcase/Gesture-android-dark_framed.png) import SwiftUI struct GesturePlayground: View { @State var tapPosition: CGPoint = .zero @State var doubleTapPosition: CGPoint = .zero @State var longPressCount = 0 @State var dragOffset: CGSize = .zero @State var combinedTapPosition: CGPoint = .zero @State var combinedDoubleTapPosition: CGPoint = .zero @State var combinedLongPressCount = 0 @State var combinedDragOffset: CGSize = .zero var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text("onTapGesture: (\(Int(tapPosition.x)), \(Int(tapPosition.y)))") Spacer() Color.red .frame(width: 150.0, height: 150.0) .onTapGesture { tapPosition = $0 } } HStack { Text("TapGesture: (\(Int(tapPosition.x)), \(Int(tapPosition.y)))") Spacer() Color.red .frame(width: 150.0, height: 150.0) .gesture( TapGesture() .onEnded { _ in tapPosition = CGPoint(x: -1.0, y: -1.0) } ) } HStack { Text(".onTapGesture(2): (\(Int(doubleTapPosition.x)), \(Int(doubleTapPosition.y)))") Spacer() Color.red .frame(width: 150.0, height: 150.0) .onTapGesture(count: 2) { doubleTapPosition = $0 } } HStack { Text("Tap(2): (\(Int(doubleTapPosition.x)), \(Int(doubleTapPosition.y)))") Spacer() Color.red .frame(width: 150.0, height: 150.0) .gesture( TapGesture(count: 2) .onEnded { _ in doubleTapPosition = CGPoint(x: -1.0, y: -1.0) } ) } HStack { Text(".onLongPressGesture: \(longPressCount)") Spacer() Color.red .frame(width: 150.0, height: 150.0) .onLongPressGesture { longPressCount += 1 } onPressingChanged: { val in logger.log("LongPress onChanged: \(val)") } } HStack { Text("LongPress: \(longPressCount)") Spacer() Color.red .frame(width: 150.0, height: 150.0) .gesture( LongPressGesture() .onChanged { val in logger.log("LongPress onChanged: \(val)") } .onEnded { _ in longPressCount += 1 } ) } HStack { Text("Drag") Spacer() Color.red .frame(width: 150.0, height: 150.0) .offset(dragOffset) .gesture( DragGesture() .onChanged { val in dragOffset = val.translation } .onEnded { _ in dragOffset = .zero } ) } HStack { VStack(alignment: .leading) { Text("Tap: (\(Int(combinedTapPosition.x)), \(Int(combinedTapPosition.y)))") Text("Double tap: (\(Int(combinedDoubleTapPosition.x)), \(Int(combinedDoubleTapPosition.y)))") Text("Long press: \(combinedLongPressCount)") Text("Drag") } Spacer() Color.red .frame(width: 150.0, height: 150.0) .offset(combinedDragOffset) .gesture( DragGesture() .onChanged { val in combinedDragOffset = val.translation } .onEnded { _ in combinedDragOffset = .zero } ) .onTapGesture(count: 2) { combinedDoubleTapPosition = $0 } .onTapGesture { combinedTapPosition = $0 } .onLongPressGesture { combinedLongPressCount += 1 } } HStack { Text("Disabled tap: (\(Int(tapPosition.x)), \(Int(tapPosition.y)))") Spacer() Color.red .frame(width: 150.0, height: 150.0) .onTapGesture { tapPosition = $0 } .disabled(true) } } .padding() } .toolbar { PlaygroundSourceLink(file: "GesturePlayground.swift") } }} --- # Stack | Skip [Skip to content](https://skip.dev/docs/components/stack/#_top) Stack ===== Skip support for [SwiftUI Stacks ↗](https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`StackPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/StackPlayground.swift) ![Android screenshot for Stack component (light mode)](http://assets.skip.dev/showcase/Stack-android-light_framed.png) ![iPhone screenshot for Stack component (light mode)](http://assets.skip.dev/showcase/Stack-iphone-light_framed.png) ![iPhone screenshot for Stack component (dark mode)](http://assets.skip.dev/showcase/Stack-iphone-dark_framed.png) ![Android screenshot for Stack component (dark mode)](http://assets.skip.dev/showcase/Stack-android-dark_framed.png) import SwiftUI struct StackPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text("Before fixed") .border(Color.blue) HStack { Spacer() Text("After expanding") } .border(Color.red) Text("After fixed") .border(Color.blue) } VStack { Text("Text1") Text("Text2") } .border(Color.blue) Text("Sized to content:") VStack(spacing: 0.0) { Color.red.frame(width: 50.0, height: 50.0) Color.green.frame(width: 50.0, height: 50.0) } .border(Color.blue) Text("Content sizes to stack:") VStack(spacing: 0.0) { Color.red Color.green } .frame(width: 50.0, height: 100.0) .border(Color.blue) VStack { Text("Text1") Color.green.frame(width: 50.0, height: 50.0) } .border(Color.blue) VStack { Color.red.frame(width: 50.0, height: 50.0) Text("Text2") } .border(Color.blue) VStack(content: horizontalStripes) .background(.yellow) .frame(width: 100.0, height: 100.0) HStack(content: verticalStripes) .background(.yellow) .frame(width: 100.0, height: 100.0) ZStack { Color.yellow Color.red.padding(25.0) } .frame(width: 100.0, height: 100.0) } .padding() } .toolbar { PlaygroundSourceLink(file: "StackPlayground.swift") } } // Note: these functions are also a test that we can pass functions to SwiftUI content view builders. @ViewBuilder private func horizontalStripes() -> some View { Spacer() Color.red.frame(height: 20.0) Spacer() Color.red.frame(height: 20.0) Spacer() } @ViewBuilder private func verticalStripes() -> some View { Spacer() Color.red.frame(width: 20.0) Spacer() Color.red.frame(width: 20.0) Spacer() }} --- # Symbol | Skip [Skip to content](https://skip.dev/docs/components/symbol/#_top) Symbol ====== Skip support for [SwiftUI.Image Symbols ↗](https://developer.apple.com/documentation/swiftui/image/init(systemname:)) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`SymbolPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/SymbolPlayground.swift) ![Android screenshot for Symbol component (light mode)](http://assets.skip.dev/showcase/Symbol-android-light_framed.png) ![iPhone screenshot for Symbol component (light mode)](http://assets.skip.dev/showcase/Symbol-iphone-light_framed.png) ![iPhone screenshot for Symbol component (dark mode)](http://assets.skip.dev/showcase/Symbol-iphone-dark_framed.png) ![Android screenshot for Symbol component (dark mode)](http://assets.skip.dev/showcase/Symbol-android-dark_framed.png) import SwiftUI struct SymbolPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { symbolRow("person.crop.square") symbolRow("person.crop.circle") symbolRow("plus.circle.fill") symbolRow("plus") symbolRow("arrow.left") symbolRow("arrowtriangle.down.fill") symbolRow("arrow.forward") symbolRow("wrench") symbolRow("phone") symbolRow("checkmark.circle") symbolRow("checkmark") symbolRow("xmark") symbolRow("pencil") symbolRow("calendar") symbolRow("trash") symbolRow("envelope") symbolRow("arrow.forward.square") symbolRow("face.smiling") symbolRow("heart") symbolRow("heart.fill") symbolRow("house") symbolRow("info.circle") symbolRow("chevron.down") symbolRow("chevron.left") symbolRow("chevron.right") symbolRow("chevron.up") symbolRow("list.bullet") symbolRow("location") symbolRow("lock") symbolRow("line.3.horizontal") symbolRow("ellipsis") symbolRow("bell") symbolRow("person") symbolRow("mappin.circle") symbolRow("play") symbolRow("arrow.clockwise.circle") symbolRow("magnifyingglass") symbolRow("paperplane") symbolRow("gearshape") symbolRow("square.and.arrow.up") symbolRow("cart") symbolRow("star") symbolRow("hand.thumbsup") symbolRow("exclamationmark.triangle") symbolRow("person.crop.square.fill") symbolRow("person.crop.circle.fill") symbolRow("wrench.fill") symbolRow("phone.fill") symbolRow("checkmark.circle.fill") symbolRow("trash.fill") symbolRow("envelope.fill") symbolRow("house.fill") symbolRow("info.circle.fill") symbolRow("location.fill") symbolRow("lock.fill") symbolRow("bell.fill") symbolRow("person.fill") symbolRow("mappin.circle.fill") symbolRow("play.fill") symbolRow("paperplane.fill") symbolRow("gearshape.fill") symbolRow("square.and.arrow.up.fill") symbolRow("cart.fill") symbolRow("star.fill") symbolRow("hand.thumbsup.fill") symbolRow("exclamationmark.triangle.fill") HStack { Text(".foregroundStyle(.red)") Spacer() Image(systemName: "star.fill") .foregroundStyle(.red) } HStack { Text(".tint(.red)") Spacer() Image(systemName: "star.fill") .tint(.red) } HStack { Text(".font(.title)") Spacer() Image(systemName: "star.fill") .font(.title) } } .padding() } .toolbar { PlaygroundSourceLink(file: "SymbolPlayground.swift") } } func symbolRow(_ systemName: String) -> some View { HStack { Text(systemName) Spacer() Image(systemName: systemName) } }} --- # SafeArea | Skip [Skip to content](https://skip.dev/docs/components/safearea/#_top) SafeArea ======== Skip support for [SwiftUI safe area ↗](https://developer.apple.com/documentation/swiftui/layout-adjustments) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`SafeAreaPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/SafeAreaPlayground.swift) import SwiftUI enum SafeAreaPlaygroundType: String, CaseIterable { case fullscreenContent case fullscreenBackground case plainList case plainListNoNavStack case list case bottomBar var title: String { switch self { case .fullscreenContent: return "Ignore safe area" case .fullscreenBackground: return "Background ignores safe area" case .plainList: return "Plain list" case .plainListNoNavStack: return "Plain list outside nav stack" case .list: return "List" case .bottomBar: return "Bottom toolbar" } } var coverId: String { rawValue + "Cover" } var sheetId: String { rawValue + "Sheet" }} struct SafeAreaPlayground: View { @State var isCoverPresented = false @State var isSheetPresented = false @State var playgroundType: SafeAreaPlaygroundType = .fullscreenContent var body: some View { List { Section("Fullscreen") { ForEach(SafeAreaPlaygroundType.allCases, id: \.coverId) { playgroundType in Button(playgroundType.title) { self.playgroundType = playgroundType isCoverPresented = true } } } Section("Sheet") { ForEach(SafeAreaPlaygroundType.allCases, id: \.sheetId) { playgroundType in Button(playgroundType.title) { self.playgroundType = playgroundType isSheetPresented = true } } } } #if os(macOS) .sheet(isPresented: $isSheetPresented) { playground(for: playgroundType) } #else .sheet(isPresented: $isSheetPresented) { playground(for: playgroundType) } .fullScreenCover(isPresented: $isCoverPresented) { playground(for: playgroundType) } #endif } @ViewBuilder private func playground(for playgroundType: SafeAreaPlaygroundType) -> some View { switch playgroundType { case .fullscreenContent: SafeAreaFullscreenContent() case .fullscreenBackground: SafeAreaFullscreenBackground() case .plainList: SafeAreaPlainList() case .plainListNoNavStack: SafeAreaPlainListNoNavStack() case .list: SafeAreaList() case .bottomBar: #if os(macOS) SafeAreaList() #else SafeAreaBottomBar() #endif } }} struct SafeAreaFullscreenContent: View { @Environment(\.dismiss) var dismiss var body: some View { ZStack { Color.yellow Button("Dimiss") { dismiss() } } .border(.blue, width: 20.0) .ignoresSafeArea() }} struct SafeAreaFullscreenBackground: View { @Environment(\.dismiss) var dismiss var body: some View { ZStack { Color.yellow .ignoresSafeArea() Button("Dimiss") { dismiss() } } .border(.blue, width: 20.0) }} struct SafeAreaPlainList: View { @Environment(\.dismiss) var dismiss var body: some View { NavigationStack { List(0..<40) { index in Text("Row: \(index)") } .listStyle(.plain) .navigationTitle(SafeAreaPlaygroundType.plainList.title) .toolbar { Button("Dismiss") { dismiss() } } } }} struct SafeAreaPlainListNoNavStack: View { @Environment(\.dismiss) var dismiss var body: some View { List { Button("Dismiss") { dismiss() } ForEach(0..<40) { index in Text("Row: \(index)") } } .listStyle(.plain) }} struct SafeAreaList: View { @Environment(\.dismiss) var dismiss var body: some View { NavigationStack { List(0..<40) { index in Text("Row: \(index)") } .navigationTitle(SafeAreaPlaygroundType.list.title) .toolbar { Button("Dismiss") { dismiss() } } } }} #if os(macOS)#elsestruct SafeAreaBottomBar: View { @Environment(\.dismiss) var dismiss var body: some View { NavigationStack { List(0..<40) { index in Text("Row: \(index)") } .navigationTitle(SafeAreaPlaygroundType.bottomBar.title) .toolbar { ToolbarItem(placement: .bottomBar) { Button("Dismiss") { dismiss() } } } } }}#endif --- # Searchable | Skip [Skip to content](https://skip.dev/docs/components/searchable/#_top) Searchable ========== Skip support for [SwiftUI.View.searchable ↗](https://developer.apple.com/documentation/swiftui/adding-a-search-interface-to-your-app) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`SearchablePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/SearchablePlayground.swift) ![Android screenshot for Searchable component (light mode)](http://assets.skip.dev/showcase/Searchable-android-light_framed.png) ![iPhone screenshot for Searchable component (light mode)](http://assets.skip.dev/showcase/Searchable-iphone-light_framed.png) ![iPhone screenshot for Searchable component (dark mode)](http://assets.skip.dev/showcase/Searchable-iphone-dark_framed.png) ![Android screenshot for Searchable component (dark mode)](http://assets.skip.dev/showcase/Searchable-android-dark_framed.png) import Foundationimport SwiftUI enum SearchablePlaygroundType: String, CaseIterable { case list case plainList case submit case isSearching var title: String { switch self { case .list: return "List" case .plainList: return "Plain style" case .submit: return "Submit" case .isSearching: return "isSearching" } }} struct SearchablePlayground: View { var body: some View { List(SearchablePlaygroundType.allCases, id: \.self) { type in NavigationLink(type.title, value: type) } .toolbar { PlaygroundSourceLink(file: "SearchablePlayground.swift") } .navigationDestination(for: SearchablePlaygroundType.self) { switch $0 { case .list: ListSearchablePlayground() .navigationTitle($0.title) case .plainList: PlainListSearchablePlayground() .navigationTitle($0.title) case .submit: SubmitSearchablePlayground() .navigationTitle($0.title) case .isSearching: IsSearchingSearchablePlayground() .navigationTitle($0.title) } } }} struct ListSearchablePlayground: View { @State var searchText = "" var body: some View { List { ForEach(matchingAnimals(), id: \.self) { Text($0) } } .searchable(text: $searchText) } func matchingAnimals() -> [String] { return animals().filter { $0.lowercased().starts(with: searchText.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)) } }} struct PlainListSearchablePlayground: View { @State var searchText = "" var body: some View { List { ForEach(matchingAnimals(), id: \.self) { Text($0) } } .listStyle(.plain) .searchable(text: $searchText) } func matchingAnimals() -> [String] { return animals().filter { $0.lowercased().starts(with: searchText.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)) } }} struct SubmitSearchablePlayground: View { @State var searchText = "" @State var matchingAnimals = animals() var body: some View { List { ForEach(matchingAnimals, id: \.self) { Text($0) } } .searchable(text: $searchText) .onSubmit(of: .search) { matchingAnimals = animals().filter { $0.lowercased().starts(with: searchText.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)) } } }} struct IsSearchingSearchablePlayground: View { @State var searchText = "" var body: some View { IsSearchingView() .searchable(text: $searchText) } private struct IsSearchingView: View { @Environment(\.isSearching) var isSearching var body: some View { List { Text("isSearching:") if isSearching { Text("YES").foregroundStyle(.green) } else { Text("NO").foregroundStyle(.red) } } } }} func animals() -> [String] { return [ "Alligator", "Ant", "Bat", "Bear", "Cat", "Cheetah", "Dog", "Dolphin", "Eagle", "Elephant", "Flamingo", "Fox", "Goat", "Gorilla", "Hippo", "Horse", "Ibis", "Insect", "Jackrabbit", "Jellyfish", "Kangaroo", "Kingfisher", "Lampfish", "Llama", "Manitee", "Monkey", "Narwhal", "Nightingale", "Octopus", "Ocelot", "Penguin", "Pufferfish", "Quahog", "Quail", "Rat", "Rhinocerous", "Snake", "Squirrel", "Tarantula", "Turtle", "Unicorn", "Vole", "Vulture", "Walrus", "Whale", "Yak", "Zebra" ]} --- # ZIndex | Skip [Skip to content](https://skip.dev/docs/components/zindex/#_top) ZIndex ====== Skip support for [SwiftUI.View.zindex ↗](https://developer.apple.com/documentation/swiftui/view/zindex(_:)) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ZIndexPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ZIndexPlayground.swift) ![Android screenshot for ZIndex component (light mode)](http://assets.skip.dev/showcase/ZIndex-android-light_framed.png) ![iPhone screenshot for ZIndex component (light mode)](http://assets.skip.dev/showcase/ZIndex-iphone-light_framed.png) ![iPhone screenshot for ZIndex component (dark mode)](http://assets.skip.dev/showcase/ZIndex-iphone-dark_framed.png) ![Android screenshot for ZIndex component (dark mode)](http://assets.skip.dev/showcase/ZIndex-android-dark_framed.png) import SwiftUI struct ZIndexPlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text("Without zIndex") Spacer() ZStack { Color.blue.opacity(0.5) .frame(width: 20.0, height: 20.0) Color.green.opacity(0.5) .frame(width: 60.0, height: 60.0) Color.red.opacity(0.5) .frame(width: 100.0, height: 100.0) } } HStack { Text("With zIndex") Spacer() ZStack { Color.blue.opacity(0.5) .frame(width: 20.0, height: 20.0) .zIndex(2.0) Color.green.opacity(0.5) .frame(width: 60.0, height: 60.0) .zIndex(1.0) Color.red.opacity(0.5) .frame(width: 100.0, height: 100.0) } } HStack { Text("With zIndex before frame") Spacer() ZStack { Color.blue.opacity(0.5) .zIndex(2.0) .frame(width: 20.0, height: 20.0) Color.green.opacity(0.5) .zIndex(1.0) .frame(width: 60.0, height: 60.0) Color.red.opacity(0.5) .frame(width: 100.0, height: 100.0) } } } .padding() } }} --- # Table | Skip [Skip to content](https://skip.dev/docs/components/table/#_top) Table ===== Skip support for [SwiftUI.Table ↗](https://developer.apple.com/documentation/swiftui/table) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`TablePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/TablePlayground.swift) import SwiftUI enum TablePlaygroundType: String, CaseIterable { case defaultColumns case fixedWidthColumns case rangeWidthColumns case selection case selectionSet var title: String { switch self { case .defaultColumns: return "Default Columns" case .fixedWidthColumns: return "Fixed Width Columns" case .rangeWidthColumns: return "Range Width Columns" case .selection: return "Selection" case .selectionSet: return "Selection Set" } }} struct TablePlayground: View { var body: some View { List(TablePlaygroundType.allCases, id: \.self) { type in NavigationLink(type.title, value: type) } .toolbar { PlaygroundSourceLink(file: "TablePlayground.swift") } .navigationDestination(for: TablePlaygroundType.self) { switch $0 { case .defaultColumns: DefaultColumnsTablePlayground() .navigationTitle($0.title) case .fixedWidthColumns: FixedWidthColumnsTablePlayground() .navigationTitle($0.title) case .rangeWidthColumns: RangeWidthColumnsTablePlayground() .navigationTitle($0.title) case .selection: SelectionTablePlayground() .navigationTitle($0.title) case .selectionSet: SelectionSetTablePlayground() .navigationTitle($0.title) } } }} private struct TableData: Identifiable { let id = UUID() let name: String let value: Int static var initialData: [TableData] { return (1...20).map { TableData(name: "Item \($0)", value: $0) } }} private struct DefaultColumnsTablePlayground: View { @State var data = TableData.initialData var body: some View { Table(data) { TableColumn("UUID", content: { data in // SKIP NOWARN Text(String(data.id.uuidString.prefix(8))) }) TableColumn("Name", value: \.name) TableColumn("Value", content: { data in Text("\(data.value)") }) } }} private struct FixedWidthColumnsTablePlayground: View { @State var data = TableData.initialData var body: some View { Table(data) { TableColumn("UUID", content: { data in // SKIP NOWARN Text(String(data.id.uuidString.prefix(8))) }) .width(100) TableColumn("Name", value: \.name) .width(100) TableColumn("Value", content: { data in Text("\(data.value)") }) } }} private struct RangeWidthColumnsTablePlayground: View { @State var data = TableData.initialData var body: some View { Table(data) { TableColumn("UUID", content: { data in // SKIP NOWARN Text(String(data.id.uuidString.prefix(8))) }) .width(min: 100, max: 200) TableColumn("Name", value: \.name) .width(ideal: 100) TableColumn("Value", content: { data in Text("\(data.value)") }) } }} private struct SelectionTablePlayground: View { @State var data = TableData.initialData @State var selection: UUID? var body: some View { Table(data, selection: $selection) { TableColumn("UUID", content: { data in // SKIP NOWARN Text(String(data.id.uuidString.prefix(8))) }) TableColumn("Name", value: \.name) TableColumn("Value", content: { data in Text("\(data.value)") }) } }} private struct SelectionSetTablePlayground: View { @State var data = TableData.initialData @State var selection: Set = [] var body: some View { Table(data, selection: $selection) { TableColumn("UUID", content: { data in // SKIP NOWARN Text(String(data.id.uuidString.prefix(8))) }) TableColumn("Name", value: \.name) TableColumn("Value", content: { data in Text("\(data.value)") }) } }} --- # Animation | Skip [Skip to content](https://skip.dev/docs/components/animation/#_top) Animation ========= Skip support for [SwiftUI.Animation ↗](https://developer.apple.com/documentation/swiftui/animation) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`AnimationPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/AnimationPlayground.swift) import SwiftUI struct AnimationPlayground: View { @State var isOn = false @State var unrelatedIsOn = false @State var isRepeatOn = false var body: some View { ScrollView { VStack(spacing: 16) { HStack { Text(".opacity") Spacer() Color.red .frame(width: 100, height: 100) .opacity(isOn ? 0.2 : 1.0) .onTapGesture { isOn = !isOn } } HStack { Text(".opacity.animation") Spacer() Color.red .frame(width: 100, height: 100) .opacity(isOn ? 0.2 : 1.0) .animation(.default, value: isOn) .onTapGesture { isOn = !isOn } } HStack { Text(".opacity.animation\n(different value)") Spacer() Color.red .frame(width: 100, height: 100) .opacity(isOn || unrelatedIsOn ? 0.2 : 1.0) .animation(.default, value: isOn) .onTapGesture { unrelatedIsOn = !unrelatedIsOn } } Button("withAnimation") { withAnimation { isOn = !isOn } } .buttonStyle(.bordered) HStack { Text(".foreground/.background") Spacer() Text("Text") .font(.largeTitle) .bold() .frame(width: 100, height: 100) .foregroundStyle(isOn ? Color.white : Color.black) .background(isOn ? Color.black : Color.white) .border(Color.blue) .onTapGesture { isOn = !isOn } } HStack { Text(".foreground/.background.animation") Spacer() Text("Text") .font(.largeTitle) .bold() .frame(width: 100, height: 100) .foregroundStyle(isOn ? Color.white : Color.black) .background(isOn ? Color.black : Color.white) .border(Color.blue) .animation(.default, value: isOn) .onTapGesture { isOn = !isOn } } Button("withAnimation") { withAnimation { isOn = !isOn } } .buttonStyle(.bordered) HStack { Text(".fill") Spacer() Circle() .fill(isOn ? Color.blue : Color.red) .frame(width: 100, height: 100) .onTapGesture { isOn = !isOn } } HStack { Text(".fill.animation") Spacer() Circle() .fill(isOn ? Color.blue : Color.red) .frame(width: 100, height: 100) .animation(.default, value: isOn) .onTapGesture { isOn = !isOn } } Button("withAnimation") { withAnimation { isOn = !isOn } } .buttonStyle(.bordered) HStack { Text(".offset") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 50, height: 10) : CGSize(width: 0, height: 0)) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { isOn = !isOn } } HStack { Text(".offset.animation") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 50, height: 10) : CGSize(width: 0, height: 0)) .animation(.default, value: isOn) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { isOn = !isOn } } Button("withAnimation") { withAnimation { isOn = !isOn } } .buttonStyle(.bordered) HStack { Text(".frame") Spacer() ZStack { Color.red .frame(width: isOn ? 100.0 : 40.0, height: isOn ? 60.0 : 40.0) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { isOn = !isOn } } HStack { Text(".frame.animation") Spacer() ZStack { Color.red .frame(width: isOn ? 100.0 : 40.0, height: isOn ? 60.0 : 40.0) .animation(.default, value: isOn) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { isOn = !isOn } } Button("withAnimation") { withAnimation { isOn = !isOn } } .buttonStyle(.bordered) HStack { Text(".rotationEffect") Spacer() Color.red .frame(width: 100, height: 100) .rotationEffect(isOn ? .degrees(45) : .degrees(0)) .onTapGesture { isOn = !isOn } } HStack { Text(".rotationEffect.animation") Spacer() Color.red .frame(width: 100, height: 100) .rotationEffect(isOn ? .degrees(45) : .degrees(0)) .animation(.default, value: isOn) .onTapGesture { isOn = !isOn } } Button("withAnimation") { withAnimation { isOn = !isOn } } .buttonStyle(.bordered) HStack { Text(".scaleEffect") Spacer() Color.red .frame(width: 100, height: 100) .scaleEffect(isOn ? 0.5 : 1.0) .onTapGesture { isOn = !isOn } } HStack { Text(".scaleEffect.animation") Spacer() Color.red .frame(width: 100, height: 100) .scaleEffect(isOn ? 0.5 : 1.0) .animation(.default, value: isOn) .onTapGesture { isOn = !isOn } } Button("withAnimation") { withAnimation { isOn = !isOn } } .buttonStyle(.bordered) HStack { Text(".font") Spacer() Text("Hello") .font(.system(size: isOn ? 30.0 : 20.0)) .frame(width: 100, height: 100) .border(Color.blue) .onTapGesture { isOn = !isOn } } HStack { Text(".font.animation") Spacer() Text("Hello") .font(.system(size: isOn ? 30.0 : 20.0)) .frame(width: 100, height: 100) .border(Color.blue) .animation(.default, value: isOn) .onTapGesture { isOn = !isOn } } Button("withAnimation") { withAnimation { isOn = !isOn } } .buttonStyle(.bordered) HStack { Text(".animation(.spring)") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 0, height: 50) : CGSize(width: 0, height: -50)) .animation(.spring, value: isOn) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { isOn = !isOn } } HStack { Text("withAnimation(.spring)") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 0, height: 50) : CGSize(width: 0, height: -50)) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { withAnimation(.spring) { isOn = !isOn } } } HStack { Text(".animation(.easeIn(duration: 1))") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 0, height: 50) : CGSize(width: 0, height: -50)) .animation(.easeIn(duration: 1), value: isOn) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { isOn = !isOn } } HStack { Text("withAnimation(.easeIn(duration: 1))") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 0, height: 50) : CGSize(width: 0, height: -50)) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { withAnimation(.easeIn(duration: 1)) { isOn = !isOn } } } HStack { Text(".animation(repeatCount(3))") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 0, height: 50) : CGSize(width: 0, height: -50)) .animation(.default.repeatCount(3), value: isOn) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { isOn = !isOn } } HStack { Text("withAnimation(repeatCount(3))") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 0, height: 50) : CGSize(width: 0, height: -50)) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { withAnimation(.default.repeatCount(3)) { isOn = !isOn } } } HStack { Text(".animation(autoreverses: false))") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isOn ? CGSize(width: 0, height: 50) : CGSize(width: 0, height: -50)) .animation(.default.repeatCount(3, autoreverses: false), value: isOn) } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { isOn = !isOn } } HStack { Text(".repeatForever()") Spacer() ZStack { Color.red .frame(width: 20, height: 20) .offset(isRepeatOn ? CGSize(width: 0, height: 50) : CGSize(width: 0, height: -50)) .animation(.default.repeatForever(), value: isRepeatOn) } .frame(width: 100, height: 100) .background(.gray) .onAppear { isRepeatOn = true } .onTapGesture { isOn = !isOn } } NavigationLink("Push") { Text("Pushed") } .buttonStyle(.bordered) } .padding() } .toolbar { PlaygroundSourceLink(file: "AnimationPlayground.swift") } }} --- # Image | Skip [Skip to content](https://skip.dev/docs/components/image/#_top) Image ===== Skip support for [SwiftUI.Image ↗](https://developer.apple.com/documentation/swiftui/image) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ImagePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ImagePlayground.swift) ![Android screenshot for Image component (light mode)](http://assets.skip.dev/showcase/Image-android-light_framed.png) ![iPhone screenshot for Image component (light mode)](http://assets.skip.dev/showcase/Image-iphone-light_framed.png) ![iPhone screenshot for Image component (dark mode)](http://assets.skip.dev/showcase/Image-iphone-dark_framed.png) ![Android screenshot for Image component (dark mode)](http://assets.skip.dev/showcase/Image-android-dark_framed.png) import Foundationimport SwiftUI struct ImagePlayground: View { let systemNameSample = "heart.fill" let remoteImageResourceURL: URL? = URL(string: "https://picsum.photos/id/237/200/300") // iOS: file://…/Application/…/Showcase.app/skipapp-showcase_Showcase.bundle/skip-logo.png // Android: asset:/showcase/module/Resources/skip-logo.png let localImageResourceURL: URL? = Bundle.module.url(forResource: "skip-logo", withExtension: "png") var body: some View { ScrollView { VStack(spacing: 16.0) { Text("Bundled Image").font(.title).bold() HStack { Spacer() AsyncImage(url: localImageResourceURL) .border(.blue) Spacer() } Text("systemName").font(.title).bold() HStack { Text(".frame(100, 100)") Spacer() Image(systemName: systemNameSample) .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text(".resizable\n.frame(100, 100)") Spacer() Image(systemName: systemNameSample) .resizable() .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text(".resizable()\n.scaleToFill\n.frame(100, 100)\n.clipped") Spacer() Image(systemName: systemNameSample) .resizable() .scaledToFill() .frame(width: 100.0, height: 100.0) .clipped() .border(Color.blue) } HStack { Text(".resizable()\n.scaleToFit\n.frame(100, 100)") Spacer() Image(systemName: systemNameSample) .resizable() .scaledToFit() .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text(".resizable()\n.aspectRatio(0.33, .fill)\n.frame(100, 100)\n.clipped") Spacer() Image(systemName: systemNameSample) .resizable() .aspectRatio(0.33, contentMode: .fill) .frame(width: 100.0, height: 100.0) .clipped() .border(Color.blue) } HStack { Text(".resizable()\n.aspectRatio(0.33, .fit)\n.frame(100, 100)") Spacer() Image(systemName: systemNameSample) .resizable() .aspectRatio(0.33, contentMode: .fit) .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text(".resizable()\n.aspectRatio(3, .fit)\n.frame(100, 100)\n.foregroundStyle(.red)") Spacer() Image(systemName: systemNameSample) .resizable() .aspectRatio(3.0, contentMode: .fit) .frame(width: 100.0, height: 100.0) .foregroundStyle(.red) .border(Color.blue) } Text("AsyncImage").font(.title).bold() HStack { Spacer() AsyncImage(url: remoteImageResourceURL) .border(Color.blue) } HStack { Text("scale: 2.0") Spacer() AsyncImage(url: remoteImageResourceURL, scale: 2.0) .border(Color.blue) } HStack { Text(".frame(100, 100)") Spacer() AsyncImage(url: remoteImageResourceURL) .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text(".frame(100, 100)\nclipped") Spacer() AsyncImage(url: remoteImageResourceURL) .frame(width: 100.0, height: 100.0) .clipped() .border(Color.blue) } HStack { Text(".resizable()\n.frame(100, 100)") Spacer() AsyncImage(url: remoteImageResourceURL) { image in image.resizable() } placeholder: { } .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text(".resizable()\n.scaleToFill\n.frame(100, 100)\n.clipped") Spacer() AsyncImage(url: remoteImageResourceURL) { image in image.resizable() } placeholder: { } .scaledToFill() .frame(width: 100.0, height: 100.0) .clipped() .border(Color.blue) } HStack { Text(".resizable()\n.scaleToFit\n.frame(100, 100)") Spacer() AsyncImage(url: remoteImageResourceURL) { image in image.resizable() } placeholder: { } .scaledToFit() .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text(".resizable()\n.aspectRatio(0.33, .fill)\n.frame(100, 100)\n.clipped") Spacer() AsyncImage(url: remoteImageResourceURL) { image in image.resizable() } placeholder: { } .aspectRatio(0.33, contentMode: .fill) .frame(width: 100.0, height: 100.0) .clipped() .border(Color.blue) } HStack { Text(".resizable()\n.aspectRatio(0.33, .fit)\n.frame(100, 100)") Spacer() AsyncImage(url: remoteImageResourceURL) { image in image.resizable() } placeholder: { } .aspectRatio(0.33, contentMode: .fit) .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text(".resizable()\n.aspectRatio(3, .fit)\n.frame(100, 100)") Spacer() AsyncImage(url: remoteImageResourceURL) { image in image.resizable() } placeholder: { } .aspectRatio(3.0, contentMode: .fit) .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text("No URL") Spacer() AsyncImage(url: nil) .border(Color.blue) } HStack { Text("No URL\n.frame(100, 100)") Spacer() AsyncImage(url: nil) .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text("Custom placeholder") Spacer() AsyncImage(url: nil) { image in EmptyView() } placeholder: { ProgressView() } .frame(width: 100.0, height: 100.0) .border(Color.blue) } HStack { Text("Custom closure") Spacer() AsyncImage(url: nil) { phase in switch phase { case .empty: ProgressView() case .failure: Color.red case .success: EmptyView() @unknown default: EmptyView() } } .frame(width: 100.0, height: 100.0) .border(Color.blue) } } .padding() } .toolbar { PlaygroundSourceLink(file: "ImagePlayground.swift") } }} --- # Grid | Skip [Skip to content](https://skip.dev/docs/components/grid/#_top) Grid ==== Skip support for [SwiftUI.Grid ↗](https://developer.apple.com/documentation/swiftui/grid) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`GridPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/GridPlayground.swift) import SwiftUI struct GridPlayground: View { var body: some View { ScrollView { VStack(spacing: 16) { NavigationLink("LazyVGrid .adaptive") { LazyVGridAdaptiveView() .navigationTitle("LazyVGridView") } NavigationLink("LazyVGrid .flexible") { LazyVGridFlexibleView() .navigationTitle("LazyVGridView") } NavigationLink("LazyVGrid .fixed") { LazyVGridFixedView() .navigationTitle("LazyVGridView") } NavigationLink("LazyVGrid .trailing") { LazyVGridTrailingView() .navigationTitle("LazyVGridView") } NavigationLink("LazyVGrid sectioned") { LazyVGridSectionedView() .navigationTitle("LazyVGridView") } NavigationLink("LazyVGrid refreshable") { LazyVGridRefreshableView() .navigationTitle("Refreshable") } NavigationLink("LazyHGrid .adaptive") { LazyHGridAdaptiveView() .navigationTitle("LazyHGridView") } NavigationLink("LazyHGrid .flexible") { LazyHGridFlexibleView() .navigationTitle("LazyHGridView") } NavigationLink("LazyHGrid .fixed") { LazyHGridFixedView() .navigationTitle("LazyHGridView") } NavigationLink("LazyHGrid .bottom") { LazyHGridBottomView() .navigationTitle("LazyHGridView") } NavigationLink("LazyHGrid sectioned") { LazyHGridSectionedView() .navigationTitle("LazyHGridView") } NavigationLink("LazyVGrid .padding") { LazyVGridPaddingView() .navigationTitle("LazyVGridView") } } .padding() } .toolbar { PlaygroundSourceLink(file: "GridPlayground.swift") } }} private struct LazyVGridAdaptiveView: View { var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) { ForEach(0..<100) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(height: 80) } } } }} private struct LazyVGridFlexibleView: View { var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]) { ForEach(0..<50) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(height: 80) ZStack { Color.green Text(String(describing: index)) } .frame(height: 80) } } } }} private struct LazyVGridFixedView: View { var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.fixed(80)), GridItem(.fixed(80)), GridItem(.fixed(80)), GridItem(.fixed(80))]) { ForEach(0..<25) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(height: 80) ZStack { Color.green Text(String(describing: index)) } .frame(height: 80) ZStack { Color.pink Text(String(describing: index)) } .frame(height: 80) ZStack { Color.orange Text(String(describing: index)) } .frame(height: 80) } } } }} private struct LazyVGridTrailingView: View { var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 80), alignment: .trailing)]) { ForEach(0..<100) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(width: 40, height: 40) } } } }} private struct LazyVGridSectionedView: View { var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) { ForEach(0..<5) { index in Section { ForEach(0..<10) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(height: 80) } } header: { Text("Section \(index) Header") } footer: { Text("Section footer") } } } } }} private struct LazyVGridRefreshableView: View { var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) { ForEach(0..<100) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(height: 80) } } } .refreshable { do { try await Task.sleep(nanoseconds: 3_000_000_000) } catch { } } }} private struct LazyHGridAdaptiveView: View { var body: some View { ScrollView(.horizontal) { LazyHGrid(rows: [GridItem(.adaptive(minimum: 80))]) { ForEach(0..<100) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(width: 80) } } } }} private struct LazyHGridFlexibleView: View { var body: some View { ScrollView(.horizontal) { LazyHGrid(rows: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]) { ForEach(0..<50) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(width: 80) ZStack { Color.green Text(String(describing: index)) } .frame(width: 80) } } } }} private struct LazyHGridFixedView: View { var body: some View { ScrollView(.horizontal) { LazyHGrid(rows: [GridItem(.fixed(80)), GridItem(.fixed(80)), GridItem(.fixed(80)), GridItem(.fixed(80))]) { ForEach(0..<25) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(width: 80) ZStack { Color.green Text(String(describing: index)) } .frame(width: 80) ZStack { Color.pink Text(String(describing: index)) } .frame(width: 80) ZStack { Color.orange Text(String(describing: index)) } .frame(width: 80) } } .frame(height: 400) } }} private struct LazyHGridBottomView: View { var body: some View { ScrollView(.horizontal) { LazyHGrid(rows: [GridItem(.adaptive(minimum: 80), alignment: .bottom)]) { ForEach(0..<100) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(width: 40, height: 40) } } } }} private struct LazyHGridSectionedView: View { var body: some View { ScrollView(.horizontal) { LazyHGrid(rows: [GridItem(.adaptive(minimum: 80))]) { ForEach(0..<5) { index in Section { ForEach(0..<10) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(width: 80) } } header: { Text("Section \(index) Header") } footer: { Text("Section footer") } } } } }} private struct LazyVGridPaddingView: View { var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) { ForEach(0..<100) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(height: 80) } } .padding(32) } }} --- # State | Skip [Skip to content](https://skip.dev/docs/components/state/#_top) State ===== Skip support for [SwiftUI State ↗](https://developer.apple.com/documentation/swiftui/state) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`StatePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/StatePlayground.swift) ![Android screenshot for State component (light mode)](http://assets.skip.dev/showcase/State-android-light_framed.png) ![iPhone screenshot for State component (light mode)](http://assets.skip.dev/showcase/State-iphone-light_framed.png) ![iPhone screenshot for State component (dark mode)](http://assets.skip.dev/showcase/State-iphone-dark_framed.png) ![Android screenshot for State component (dark mode)](http://assets.skip.dev/showcase/State-android-dark_framed.png) import SwiftUI class TapCountObservable: ObservableObject { @Published var tapCount = 0} struct TapCountStruct : Identifiable { var id = 0 var tapCount = 0} class TapCountRepository: ObservableObject { @Published var items: [TapCountStruct] = [] func add() { items.append(TapCountStruct(id: items.count)) } func increment() { if !items.isEmpty { items[items.count - 1].tapCount += 1 } }} struct StatePlayground: View { @State var tapCount = 0 @State var hasStateTapped: Bool? // Test optional vars @StateObject var tapCountObservable: TapCountObservable @State var tapCountStruct: TapCountStruct @StateObject var tapCountRepository = TapCountRepository() // Test ForEach observable init() { // Test that we can initialze state property wrappers _tapCountObservable = StateObject(wrappedValue: TapCountObservable()) _tapCountStruct = State(wrappedValue: TapCountStruct()) } var body: some View { List { Section { NavigationLink("Push another", value: PlaygroundType.state) NavigationLink("Push binding view") { StatePlaygroundDestinationBindingView(tapCount: $tapCount) } } Section { if hasStateTapped == true { Text("State tap count: \(tapCount)") } else { Text("Tap below to increment state tap count") } Button("State") { tapCount += 1 hasStateTapped = true } StatePlaygroundBindingView(tapCount: $tapCount) } Section { Text("Observable tap count: \(tapCountObservable.tapCount)") Button("Observable") { tapCountObservable.tapCount += 1 } StatePlaygroundBindingView(tapCount: $tapCountObservable.tapCount) } Section { Text("Struct tap count: \(tapCountStruct.tapCount)") Button("Struct") { tapCountStruct.tapCount += 1 } StatePlaygroundStructBindingView(tapCountStruct: $tapCountStruct) } Section { Text("Struct tap count (field): \(tapCountStruct.tapCount)") Button("Struct (field)") { tapCountStruct.tapCount += 1 } StatePlaygroundBindingView(tapCount: $tapCountStruct.tapCount) } Section { ForEach(tapCountRepository.items) { item in Text("Repository item tap count: \(item.tapCount)") } Button("Add item") { tapCountRepository.add() } Button("Increment last") { tapCountRepository.increment() } } } .onChange(of: tapCount) { logger.log("onChange(of: tapCount): \($0)") } .onChange(of: tapCountObservable.tapCount) { logger.log("onChange(of: tapCountObservable.tapCount): \($0)") } .onChange(of: tapCountStruct.tapCount) { logger.log("onChange(of: tapCountStruct.tapCount): \($0)") } .toolbar { PlaygroundSourceLink(file: "StatePlayground.swift") } }} struct StatePlaygroundBindingView: View { @Binding var tapCount: Int var body: some View { Button("Binding") { tapCount += 1 } }} struct StatePlaygroundStructBindingView: View { @Binding var tapCountStruct: TapCountStruct var body: some View { Button("Binding") { tapCountStruct.tapCount += 1 } }} struct StatePlaygroundDestinationBindingView: View { @Binding var tapCount: Int var body: some View { VStack { SubView1(tapCount: _tapCount) SubView2(tapCount: _tapCount) } .navigationTitle("Binding View") } struct SubView1: View { @Binding var tapCount: Int var body: some View { Button("Bound tap count 1: \(tapCount)") { tapCount += 1 } .buttonStyle(.bordered) } } struct SubView2: View { @Binding var tapCount: Int var body: some View { Button("Bound tap count 2: \(tapCount)") { tapCount += 1 } .buttonStyle(.bordered) } }} --- # ScrollView | Skip [Skip to content](https://skip.dev/docs/components/scrollview/#_top) ScrollView ========== Skip support for [SwiftUI.ScrollView ↗](https://developer.apple.com/documentation/swiftui/scrollview) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ScrollViewPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ScrollViewPlayground.swift) import SwiftUI enum ScrollViewPlaygroundType: String, CaseIterable { case vertical case horizontal case readerLazyVStack case readerLazyHStack case readerList case readerStaticList case readerLazyVGrid case readerLazyHGrid var title: String { switch self { case .vertical: return "Vertical" case .horizontal: return "Horizontal" case .readerLazyVStack: return "ScrollViewReader: LazyVStack" case .readerLazyHStack: return "ScrollViewReader: LazyHStack" case .readerList: return "ScrollViewReader: ForEach List" case .readerStaticList: return "ScrollViewReader: Static List" case .readerLazyVGrid: return "ScrollViewReader: LazyVGrid" case .readerLazyHGrid: return "ScrollViewReader: LazyHGrid" } }} struct ScrollViewPlayground: View { var body: some View { List(ScrollViewPlaygroundType.allCases, id: \.self) { type in NavigationLink(type.title, value: type) } .toolbar { PlaygroundSourceLink(file: "ScrollViewPlayground.swift") } .navigationDestination(for: ScrollViewPlaygroundType.self) { switch $0 { case .vertical: VerticalScrollViewPlayground() .navigationTitle($0.title) case .horizontal: HorizontalScrollViewPlayground() .navigationTitle($0.title) case .readerLazyVStack: ScrollViewReaderLazyVStackPlayground() .navigationTitle($0.title) case .readerLazyHStack: ScrollViewReaderLazyHStackPlayground() .navigationTitle($0.title) case .readerList: ScrollViewReaderListPlayground() .navigationTitle($0.title) case .readerStaticList: ScrollViewReaderStaticListPlayground() .navigationTitle($0.title) case .readerLazyVGrid: ScrollViewReaderLazyVGridPlayground() .navigationTitle($0.title) case .readerLazyHGrid: ScrollViewReaderLazyHGridPlayground() .navigationTitle($0.title) } } }} private struct VerticalScrollViewPlayground: View { var body: some View { ScrollView { VStack { ForEach(0..<30) { i in Text("View: \(i)") .padding() } } } }} private struct HorizontalScrollViewPlayground: View { var body: some View { ScrollView(.horizontal) { HStack { ForEach(0..<30) { i in Text("View: \(i)") .padding() } } } }} private struct ScrollViewReaderLazyVStackPlayground: View { var body: some View { ScrollViewReader { proxy in VStack(spacing: 16) { ScrollViewReaderJumpButtons(proxy: proxy) .padding([.top, .bottom]) ScrollView { LazyVStack { ForEach(0..<30, id: \.self) { i in Text("View: \(i)") .padding() } } } .border(.primary, width: 1) } } }} private struct ScrollViewReaderLazyHStackPlayground: View { var body: some View { ScrollViewReader { proxy in VStack(spacing: 16) { ScrollViewReaderJumpButtons(proxy: proxy) .padding([.top, .bottom]) ScrollView(.horizontal) { LazyHStack { ForEach(0..<30, id: \.self) { i in Text("View: \(i)") .padding() } } } .border(.primary, width: 1) } } }} private struct ScrollViewReaderListPlayground: View { var body: some View { ScrollViewReader { proxy in VStack(spacing: 16) { ScrollViewReaderJumpButtons(proxy: proxy) .padding([.top, .bottom]) List { Section("Section 0") { ForEach(0..<10, id: \.self) { i in Text("View: \(i)") } } Section("Section 1") { ForEach(10..<20, id: \.self) { i in Text("View: \(i)") } } Section("Section 2") { ForEach(20..<30, id: \.self) { i in Text("View: \(i)") } } } .border(.primary, width: 1) } } }} private struct ScrollViewReaderStaticListPlayground: View { var body: some View { ScrollViewReader { proxy in VStack(spacing: 16) { ScrollViewReaderJumpButtons(proxy: proxy) .padding([.top, .bottom]) List { Section("Section 0") { Text("View 0") .id(0) Text("View 1") .id(1) Text("View 2") .id(2) Text("View 3") .id(3) Text("View 4") .id(4) Text("View 5") .id(5) Text("View 6") .id(6) Text("View 7") .id(7) Text("View 8") .id(8) Text("View 9") .id(9) } Section("Section 1") { Text("View 10") .id(10) Text("View 11") .id(11) Text("View 12") .id(12) Text("View 13") .id(13) Text("View 14") .id(14) Text("View 15") .id(15) Text("View 16") .id(16) Text("View 17") .id(17) Text("View 18") .id(18) Text("View 19") .id(19) } Section("Section 2") { Text("View 20") .id(20) Text("View 21") .id(21) Text("View 22") .id(22) Text("View 23") .id(23) Text("View 24") .id(24) Text("View 25") .id(25) Text("View 26") .id(26) Text("View 27") .id(27) Text("View 28") .id(28) Text("View 29") .id(29) } } .border(.primary, width: 1) } } }} private struct ScrollViewReaderLazyVGridPlayground: View { var body: some View { ScrollViewReader { proxy in VStack(spacing: 16) { ScrollViewReaderJumpButtons(proxy: proxy) .padding([.top, .bottom]) ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 200))]) { ForEach(0..<30) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(height: 200) } } } .border(.primary, width: 1) } } }} private struct ScrollViewReaderLazyHGridPlayground: View { var body: some View { ScrollViewReader { proxy in VStack(spacing: 16) { ScrollViewReaderJumpButtons(proxy: proxy) .padding([.top, .bottom]) ScrollView(.horizontal) { LazyHGrid(rows: [GridItem(.adaptive(minimum: 200))]) { ForEach(0..<30) { index in ZStack { Color.yellow Text(String(describing: index)) } .frame(width: 200) } } } .border(.primary, width: 1) } } }} private struct ScrollViewReaderJumpButtons: View { let proxy: ScrollViewProxy var body: some View { VStack(spacing: 16) { HStack(spacing: 16) { Button("Scroll to 0") { proxy.scrollTo(0) } Button("Animated") { withAnimation { proxy.scrollTo(0) } } } .padding(.top) HStack(spacing: 16) { Button("Scroll to 15") { proxy.scrollTo(15) } Button("Animated") { withAnimation { proxy.scrollTo(15) } } } HStack(spacing: 16) { Button("Scroll to 29") { proxy.scrollTo(29) } Button("Animated") { withAnimation { proxy.scrollTo(29) } } } } }} --- # Icon | Skip [Skip to content](https://skip.dev/docs/components/icon/#_top) Icon ==== Skip support for [Icon images ↗](https://developer.apple.com/documentation/uikit/uiimage/configuring_and_displaying_symbol_images_in_your_ui/) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`IconPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/IconPlayground.swift) import SwiftUI struct IconPlayground: View { var body: some View { ScrollView { LazyVStack(spacing: 16) { ForEach(Array(iconNames.enumerated()), id: \.offset) { iconIndexName in iconRow(iconIndexName.element, index: iconIndexName.offset) } } .padding() } .toolbar { PlaygroundSourceLink(file: "IconPlayground.swift") } } func iconRow(_ imageName: String, index: Int) -> some View { HStack { Text(imageName) Spacer() Image(imageName, bundle: .module) .resizable() .aspectRatio(contentMode: .fit) .foregroundStyle(iconColors[index % iconColors.count]) .frame(width: 30, height: 30) } }} private let iconColors = [Color.blue, Color.red, Color.green, Color.yellow, Color.orange, Color.brown, Color.cyan, Color.indigo, Color.mint, Color.yellow, Color.pink, Color.purple, Color.teal] fileprivate let iconNames: [String] = [ "accessibility", "add-circle", "add", "airplane", "alarm", "albums", "alert-circle", "alert", "american-football", "analytics", "aperture", "apps", "archive", "arrow-back-circle", "arrow-back", "arrow-down-circle", "arrow-down-left-box", "arrow-down-right-box", "arrow-down", "arrow-forward-circle", "arrow-forward", "arrow-redo-circle", "arrow-redo", "arrow-undo-circle", "arrow-undo", "arrow-up-circle", "arrow-up-left-box", "arrow-up-right-box", "arrow-up", "at-circle", "at", "attach", "backspace", "bag-add", "bag-check", "bag-handle", "bag-remove", "bag", "balloon", "ban", "bandage", "bar-chart", "barbell", "barcode", "baseball", "basket", "basketball", "battery-charging", "battery-dead", "battery-full", "battery-half", "beaker", "bed", "beer", "bicycle", "binoculars", "bluetooth", "boat", "body", "bonfire", "book", "bookmark", "bookmarks", "bowling-ball", "briefcase", "browsers", "brush", "bug", "build", "bulb", "bus", "business", "cafe", "calculator", "calendar-clear", "calendar-number", "calendar", "call", "camera-reverse", "camera", "car-sport", "car", "card", "caret-back-circle", "caret-back", "caret-down-circle", "caret-down", "caret-forward-circle", "caret-forward", "caret-up-circle", "caret-up", "cart", "cash", "cellular", "chatbox-ellipses", "chatbox", "chatbubble-ellipses", "chatbubble", "chatbubbles", "checkbox", "checkmark-circle", "checkmark-done-circle", "checkmark-done", "checkmark", "chevron-back-circle", "chevron-back", "chevron-collapse", "chevron-down-circle", "chevron-down", "chevron-expand", "chevron-forward-circle", "chevron-forward", "chevron-up-circle", "chevron-up", "clipboard", "close-circle", "close", "cloud-circle", "cloud-done", "cloud-download", "cloud-offline", "cloud-upload", "cloud", "cloudy-night", "cloudy", "code-download", "code-slash", "code-working", "code", "cog", "color-fill", "color-filter", "color-palette", "color-wand", "compass", "construct", "contract", "contrast", "copy", "create", "crop", "cube", "cut", "desktop", "diamond", "dice", "disc", "document-attach", "document-lock", "document-text", "document", "documents", "download", "duplicate", "ear", "earth", "easel", "egg", "ellipse", "ellipsis-horizontal-circle", "ellipsis-horizontal", "ellipsis-vertical-circle", "ellipsis-vertical", "enter", "exit", "expand", "extension-puzzle", "eye-off", "eye", "eyedrop", "fast-food", "female", "file-tray-full", "file-tray-stacked", "file-tray", "film", "filter-circle", "filter", "finger-print", "fish", "fitness", "flag", "flame", "flash-off", "flash", "flashlight", "flask", "flower", "folder-open", "folder", "football", "footsteps", "funnel", "game-controller", "gift", "git-branch", "git-commit", "git-compare", "git-merge", "git-network", "git-pull-request", "glasses", "globe", "golf", "grid", "hammer", "hand-left", "hand-right", "happy", "hardware-chip", "headset", "heart-circle", "heart-dislike-circle", "heart-dislike", "heart-half", "heart", "help-buoy", "help-circle", "help", "home", "hourglass", "ice-cream", "id-card", "image", "images", "infinite", "information-circle", "information", "invert-mode", "journal", "key", "keypad", "language", "laptop", "layers", "leaf", "library", "link", "list-circle", "list", "locate", "location", "lock-closed", "lock-open", "log-in", "log-out", "logo-android", "magnet", "mail-open", "mail-unread", "mail", "male-female", "male", "man", "map", "medal", "medical", "medkit", "megaphone", "menu", "mic-circle", "mic-off-circle", "mic-off", "mic", "moon", "move", "musical-note", "musical-notes", "navigate-circle", "navigate", "newspaper", "notifications-circle", "notifications-off-circle", "notifications-off", "notifications", "nuclear", "nutrition", "open", "options", "paper-plane", "partly-sunny", "pause-circle", "pause", "paw", "pencil", "people-circle", "people", "person-add", "person-circle", "person-remove", "person", "phone-landscape", "phone-portrait", "pie-chart", "pin", "pint", "pizza", "planet", "play-back-circle", "play-back", "play-circle", "play-forward-circle", "play-forward", "play-skip-back-circle", "play-skip-back", "play-skip-forward-circle", "play-skip-forward", "play", "podium", "power", "pricetag", "pricetags", "print", "prism", "pulse", "push", "qr-code", "radio-button-off", "radio-button-on", "radio", "rainy", "reader", "receipt", "recording", "refresh-circle", "refresh", "reload-circle", "reload", "remove-circle", "remove", "reorder-four", "reorder-three", "reorder-two", "repeat", "resize", "restaurant", "return-down-back", "return-down-forward", "return-up-back", "return-up-forward", "ribbon", "rocket", "rose", "sad", "save", "scale", "scan-circle", "scan", "school", "search-circle", "search", "send", "server", "settings", "shapes", "share-social", "share", "shield-checkmark", "shield-half", "shield", "shirt", "shuffle", "skull", "snow", "sparkles", "speedometer", "square", "star-half", "star", "stats-chart", "stop-circle", "stop", "stopwatch", "storefront", "subway", "sunny", "swap-horizontal", "swap-vertical", "sync-circle", "sync", "tablet-landscape", "tablet-portrait", "telescope", "tennisball", "terminal", "text", "thermometer", "thumbs-down", "thumbs-up", "thunderstorm", "ticket", "time", "timer", "today", "toggle", "trail-sign", "train", "transgender", "trash-bin", "trash", "trending-down", "trending-up", "triangle", "trophy", "tv", "umbrella", "unlink", "videocam-off", "videocam", "volume-high", "volume-low", "volume-medium", "volume-mute", "volume-off", "walk", "wallet", "warning", "watch", "water", "wifi", "wine", "woman",] --- # Transition | Skip [Skip to content](https://skip.dev/docs/components/transition/#_top) Transition ========== Skip support for [SwiftUI.Transition ↗](https://developer.apple.com/documentation/swiftui/transition) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`TransitionPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/TransitionPlayground.swift) import SwiftUI struct TransitionPlayground: View { @State var count = 0 var body: some View { ScrollView { VStack(spacing: 16) { HStack { Text("Default") Spacer() ZStack { if count % 2 == 1 { Color.red .frame(width: 20, height: 20) .id(1) } } .frame(width: 100, height: 100) .background(.gray) .onTapGesture { updateCount() } } HStack { Text(".animation") Spacer() ZStack { if count % 2 == 1 { Color.red .frame(width: 20, height: 20) .id(1) } } .frame(width: 100, height: 100) .background(.gray) .animation(.default, value: count) .onTapGesture { updateCount() } } Button("withAnimation") { withAnimation { updateCount() } } .buttonStyle(.bordered) HStack { Text("HStack.animation") Spacer() HStack { ForEach(0.. 0 { Color.red .frame(width: 60, height: 60) .id(1) } if count > 1 { Color.green .frame(width: 40, height: 40) .id(2) } if count > 2 { Color.blue .frame(width: 20, height: 20) .id(3) } } .frame(width: 100, height: 100) .background(.gray) .animation(.default, value: count) .onTapGesture { updateCount() } } HStack { Text(".transition(.move(edge: .top))") Spacer() HStack { ForEach(0.. 3 { count = 0 } }} --- # SQLite | Skip [Skip to content](https://skip.dev/docs/modules/skip-sql/#_top) SQLite ====== The SkipSQL module is a dual-platform Skip Lite framework that provides access to sqlite database in Darwin and Android systems. Usage ----- [Section titled “Usage”](https://skip.dev/docs/modules/skip-sql/#usage) ### Connection example [Section titled “Connection example”](https://skip.dev/docs/modules/skip-sql/#connection-example) To connect import SkipSQLCoreimport SkipSQL let dbpath = URL.applicationSupportDirectory.appendingPathComponent("db.sqlite") let ctx = try SQLContext(path: dbpath, flags: [.create, .readWrite], configuration: .platform)defer { ctx.close() } try sqlite.exec(sql: "CREATE TABLE IF NOT EXISTS SOME_TABLE (STRING TEXT)") try sqlite.exec(sql: "INSERT INTO SOME_TABLE (STRING) VALUES (?)", parameters: [SQLValue.text("ABC")]) let rows: [[SQLValue]] = ctx.selectAll(sql: "SELECT STRING FROM SOME_TABLE")assert(rows[0][0] == SQLValue.text("ABC")) ### In-memory databases [Section titled “In-memory databases”](https://skip.dev/docs/modules/skip-sql/#in-memory-databases) When passing `nil` as the path, the `SQLContext` will reside entirely in memory, and will not persist once the context is closed. This can be useful for unit testing and performing in-memory calculations, or as a temporary engine for calculations and sorting. import SkipSQLCoreimport SkipSQL let ctx = try SQLContext(path: nil, configuration: .platform)defer { ctx.close() } let rows: [[SQLValue]] = ctx.selectAll(sql: "SELECT 1, 1.1+2.2, 'AB'||'C'") assert(rows[0][0] == SQLValue.long(1))assert(rows[0][1] == SQLValue.real(3.3))assert(rows[0][2] == SQLValue.text("ABC")) ### Transactions [Section titled “Transactions”](https://skip.dev/docs/modules/skip-sql/#transactions) Performing multiple operations in the context of a transaction will ensure that either all the operations succeed (`COMMIT`) or fail (`ROLLBACK`) together. try ctx.transaction { try ctx.exec(sql: "INSERT INTO TABLE_NAME VALUES(1)") try ctx.exec(sql: "INSERT INTO TABLE_NAME VALUES(2)")} The default transaction type is `.deferred`, but it can be specified as a parameter to `transaction` to override the default, or `nil` to perform the operation without a transaction. ### Bound parameters [Section titled “Bound parameters”](https://skip.dev/docs/modules/skip-sql/#bound-parameters) Retaining a prepared `SQLStatement` will mean that the SQL doesn’t need to be re-parsed each time a query or insert/update statement is performed. SQL statements with a `?` symbol will expect those parameters to be applied with the `bind` function before the statement is executed. let insert = try sqlite.prepare(sql: "INSERT INTO TABLE_NAME (NUM, STR) VALUES (?, ?)")defer { insert.close() } // insert 1,000 rows in a single transaction, re-using the insert statementtry sqlite.transaction { for i in 1...1_000 { let params: [SQLValue] = [ SQLValue.long(Int64(i)), SQLValue.text("Row #\(i)") ] try insert.update(parameters: params) }} ### Schema Migration [Section titled “Schema Migration”](https://skip.dev/docs/modules/skip-sql/#schema-migration) There is no built-in support for schema migrations. Following is a part of a sample of how you might perform migrations in your own app. // track the version of the schema with the `userVersion` pragma, which can be used for schema migrationfunc migrateSchema(v version: Int64, ddl: String) throws { if ctx.userVersion < version { let startTime = Date.now try ctx.transaction { try ctx.exec(sql: ddl) // perform the DDL operation // then update the schema version ctx.userVersion = version } logger.log("updated database schema to \(version) in \(startTime.durationToNow)") }} // the initial creation script for a new databasetry migrateSchema(v: 1, ddl: """CREATE TABLE DATA_ITEM (ID INTEGER PRIMARY KEY AUTOINCREMENT)""")// migrate records to have new description columntry migrateSchema(v: 2, ddl: """ALTER TABLE DATA_ITEM ADD COLUMN DESCRIPTION TEXT""") ### Concurrency [Section titled “Concurrency”](https://skip.dev/docs/modules/skip-sql/#concurrency) As a thin layer over a SQLite connection, SkipSQL itself performs no locking or manages threads in any way. It is up to the application layer to set up reader/writer locks, or else just perform all the operations in an isolated context (e.g., using an actor). The SQLite guide on [Locking And Concurrency ↗](https://www.sqlite.org/lockingv3.html) can provide additional guidance. SQLCodable ---------- [Section titled “SQLCodable”](https://skip.dev/docs/modules/skip-sql/#sqlcodable) SkipSQL includes a simple mechanism for mapping Swift types to tables through the `SQLCodable` protocol and `SQLTable` and `SQLColumn` types. An example of a type that implements this is: /// A struct that can read and write its values to the `DEMO_TABLE` table.public struct DemoTable : SQLCodable, Equatable { public var id: Int64 static let id = SQLColumn(name: "ID", type: .long, primaryKey: true, autoincrement: true) public var txt: String? static let txt = SQLColumn(name: "TXT", type: .text, unique: true, nullable: false, index: SQLIndex(name: "IDX_TXT")) public var num: Double? static let num = SQLColumn(name: "NUM", type: .real) public var int: Int static let int = SQLColumn(name: "INT", type: .long, nullable: false) public var dbl: Double? static let dbl = SQLColumn(name: "DBL", type: .real, defaultValue: SQLValue(Double.pi), index: SQLIndex(name: "IDX_DBL", unique: false)) public var blb: Data? static let blb = SQLColumn(name: "BLB", type: .blob) public static let table = SQLTable(name: "DEMO_TABLE", columns: [id, txt, num, int, dbl, blb]) public init(id: Int64 = 0, txt: String? = nil, num: Double? = nil, int: Int, dbl: Double? = nil, blb: Data? = nil) { self.id = id self.txt = txt self.num = num self.int = int self.dbl = dbl self.blb = blb } /// Required initializer to create an instance from the given `SQLRow = [SQLColumn: SQLValue]` public init(row: SQLRow, context: SQLContext) throws { self.id = try Self.id.longValueRequired(in: row) self.txt = try Self.txt.textValueRequired(in: row) self.num = Self.num.realValue(in: row) self.int = try Int(Self.int.longValueRequired(in: row)) self.dbl = Self.dbl.realValue(in: row) self.blb = Self.blb.blobValue(in: row) } /// Encode the current instance into the given `SQLRow` dictionary. public func encode(row: inout SQLRow) throws { row[Self.id] = SQLValue(self.id) row[Self.txt] = SQLValue(self.txt) row[Self.num] = SQLValue(self.num) row[Self.int] = SQLValue(self.int) row[Self.dbl] = SQLValue(self.dbl) row[Self.blb] = SQLValue(self.blb) }} The `init` and `encode` implementations can be used to coerce the primitive SQLite value types into other Swift types. For example, to have a `UUID` property, you might implement it like: public struct UUIDHolder : SQLCodable, Identifiable { public var id: UUID static let id = SQLColumn(name: "ID", type: .text, primaryKey: true) public static let table = SQLTable(name: "UUID_HOLDER", columns: [id]) public init(id: UUID = UUID()) { self.id = id } public init(row: SQLRow, context: SQLContext) throws { self.id = try SQLBindingError.checkNonNull(UUID(uuidString: Self.id.textValueRequired(in: row)), in: Self.id) } public func encode(row: inout SQLRow) throws { row[Self.id] = SQLValue(self.id.uuidString) }} ### Querying with SQLPredicate [Section titled “Querying with SQLPredicate”](https://skip.dev/docs/modules/skip-sql/#querying-with-sqlpredicate) The `SQLPredicate` type enables querying the database for instances of a `SQLCodable` instance. For example: // issues: SELECT "ID", "TXT", "NUM", "INT", "DBL", "BLB" FROM "DEMO_TABLE" WHERE ("NUM" IS NULL OR "TXT" = 'ABC')let predicate = DemoTable.num.isNull().or(DemoTable.txt.equals(SQLValue("ABC"))) let resultSet = try sqlite.query(DemoTable.self).where(predicate).eval()defer { resultSet.close() } let cursor = resultSet.makeIterator()while let row = cursor.next() { let instance = try row.get() // instantiate the type from the row logger.log("got instance: \(instance)")} ### Primary keys and auto-increment columns [Section titled “Primary keys and auto-increment columns”](https://skip.dev/docs/modules/skip-sql/#primary-keys-and-auto-increment-columns) SkipSQL supports primary keys that work with SQLite’s ROWID mechanism, such that when an `INTEGER` column (i.e., `SQLValue.long`) is the primary key, the same storage is used as the underlying `ROWID` that all tables automatically have (see [ROWIDs and the INTEGER PRIMARY KEY ↗](https://sqlite.org/lang_createtable.html#rowids_and_the_integer_primary_key) ). The property should be either an optional `Int64?`, or if it marked with `autoincrement: true` and the type is a non-optional `Int64`, then inserting a value with the id property set to `0` will automatically assign the primary key when `.insert(ob)` is called. This enables a table to have a required primary key (which is useful when implementing `Identifiable`) with the special sentinal value of `0` that indicates that it is a new instance. public var id: Int64static let id = SQLColumn(name: "ID", type: .long, primaryKey: true, autoincrement: true) If the primary key is not marked with `autoincrement: true` and the property is not optional, then it is assumed that the primary key is manually assigned by the developer and care must be taken to ensure that duplicate values are not inserted. ### Date fields [Section titled “Date fields”](https://skip.dev/docs/modules/skip-sql/#date-fields) SQLite does not have any dedicated column type that handles date fields (see [Date And Time Functions ↗](https://sqlite.org/lang_datefunc.html#overview) ), but it can handle dates encoded either as a ISO-8601 string in a column of type TEXT, or in a numeric column containing the number of seconds since January 1, 1970. Examples of mapping to each type are as follows: #### Mapping Date to a REAL column [Section titled “Mapping Date to a REAL column”](https://skip.dev/docs/modules/skip-sql/#mapping-date-to-a-real-column) public struct SQLDateAsReal : SQLCodable { public var rowid: Int64 static let rowid = SQLColumn(name: "ROWID", type: .long, primaryKey: true, autoincrement: true) public var date: Date static let date = SQLColumn(name: "DATE", type: .real) public static let table = SQLTable(name: "SQL_DATE_AS_REAL", columns: [rowid, date]) public init(date: Date) { self.rowid = 0 self.date = date } public init(row: SQLRow, context: SQLContext) throws { self.rowid = try Self.rowid.longValueRequired(in: row) self.date = try Self.date.dateValueRequired(in: row) } public func encode(row: inout SQLRow) throws { row[Self.rowid] = SQLValue(self.rowid) row[Self.date] = SQLValue(self.date.timeIntervalSince1970) }} #### Mapping Date to a TEXT column [Section titled “Mapping Date to a TEXT column”](https://skip.dev/docs/modules/skip-sql/#mapping-date-to-a-text-column) public struct SQLDateAsText : SQLCodable { public var rowid: Int64 static let rowid = SQLColumn(name: "ROWID", type: .long, primaryKey: true, autoincrement: true) public var date: Date static let date = SQLColumn(name: "DATE", type: .text) public static let table = SQLTable(name: "SQL_DATE_AS_TEXT", columns: [rowid, date]) public init(date: Date) { self.rowid = 0 self.date = date } public init(row: SQLRow, context: SQLContext) throws { self.rowid = try Self.rowid.longValueRequired(in: row) self.date = try Self.date.dateValueRequired(in: row) } public func encode(row: inout SQLRow) throws { row[Self.rowid] = SQLValue(self.rowid) row[Self.date] = SQLValue(self.date.ISO8601Format()) }} ### Relations and joins [Section titled “Relations and joins”](https://skip.dev/docs/modules/skip-sql/#relations-and-joins) SkipSQL is not a complete object-relational mapping (ORM) package, but it does contain the ability to perform joins across multiple `SQLCodable` tables when one of the `SQLColumn` definitions includes a `SQLForeignKey`. /// A struct that can read and write its values to the `DEMO_TABLE` table.public struct DemoParent : SQLCodable { public var id: Int64 static let id = SQLColumn(name: "ID", type: .long, primaryKey: true, autoincrement: true) public var parentInfo: String? static let parentInfo = SQLColumn(name: "PARENT_INFO", type: .text) public static let table = SQLTable(name: "DEMO_TABLE", columns: [id, parentInfo])} public struct DemoChild : SQLCodable { public var id: Int64 static let id = SQLColumn(name: "ID", type: .long, primaryKey: true, autoincrement: true) public var parentID: Int64 static let parentID = SQLColumn(name: "PARENT_ID", type: .long, references: SQLForeignKey(table: DemoParent.table, column: DemoParent.id, onDelete: .cascade)) public var childInfo: String? static let childInfo = SQLColumn(name: "CHILD_INFO", type: .text) public static let table = SQLTable(name: "DEMO_CHILD", columns: [id, parentID, childInfo])} // perform a join between all the parents and childrenlet joined2: [(DemoParent?, DemoChild?)] = try sqlite.query(DemoParent.self, alias: "t0") .join(DemoChild.self, "t1", kind: .inner, on: DemoChild.parentID) .eval() .load() This operation will perform a one-to-many inner join from the `DemoParent` to the `DemoChild` tables, and return a list of tuples between the matching instances. Note that the tuple values are types as optionals, because for outer join types, it is possible to have empty rows, which would map to `nil` values. Implementation -------------- [Section titled “Implementation”](https://skip.dev/docs/modules/skip-sql/#implementation) SkipSQL speaks directly to the low-level SQLite3 C library that is pre-installed on all iOS and Android devices. On Darwin/iOS, and with SkipFuse on Android, it communicates directly through Swift’s C integration. With transpiled SkipLite on Android, it uses the [SkipFFI](https://skip.dev/docs/modules/skip-ffi) module to interact directly with the underlying sqlite installation on Android for SkipSQL, or with the locally-built SQLite that is packages and bundled with the application as a shared object file. Note that for performance and a consistent API, SkipSQL eschews Android’s `android.database.sqlite` Java wrapper, and instead uses the same SQLite C API on both Android and Darwin platforms. SQLite Versions --------------- [Section titled “SQLite Versions”](https://skip.dev/docs/modules/skip-sql/#sqlite-versions) ### Vendored SQLite Versions [Section titled “Vendored SQLite Versions”](https://skip.dev/docs/modules/skip-sql/#vendored-sqlite-versions) Because SkipSQL uses the version of SQLite that is shipped with the platform, care should be taken when using recent SQLite features, such as the [`json` ↗](https://sqlite.org/json1.html) function, which is new in SQLite 3.38. This would raise an error on Android versions below 14.0 (API 34) and iOS versions below 16.0. Be aware that some very useful SQL features may only have been added to more recent versions of SQLite, such as strict tables (added in 3.37). This may impact the Android API version you can deploy back to, so be sure to test your code on the oldest available Android emulator and iOS simulator for your project. Also be aware that the availability of some SQL features are contingent on the compile flags used to build the vendored sqlite implementation provided as part of the OS, such as `SQLITE_ENABLE_JSON1` enabling the various [`json_` ↗](https://www.sqlite.org/json1.html) operations. In the case of Android, be aware that local Robolectric testing will be insufficient to identify any limitations resulting from sqlite compile flags, since local testing will use the local (i.e., macOS-vendored) version of SQLite. Testing against an Android emulator (or device) should be performed when developing new SQL operations. | OS Version | SQLite Version | | --- | --- | | Android 9 (API 28) | 3.22 | | iOS 13 | 3.28 | | Android 10 (API 30) | 3.28 | | iOS 14 | 3.32 | | Android 11 (API 31) | 3.32 | | Android 12 (API 32) | 3.32 | | Android 13 (API 33) | 3.32 | | iOS 15 | 3.36 | | iOS 16 | 3.39 | | Android 14 (API 34) | 3.39 | | Android 15 (API 35) | 3.44 | | SQLPlus | 3.50 | | Android 16 (API 36) | 3.50 | Note that as cautioned in the [Android documentation ↗](https://developer.android.com/reference/android/database/sqlite/package-summary) , some Android device manufacturers include different versions of SQLite on their devices, so if your app depends on a SQLite version that may not be available on a device that your app supports, you may want to consider using [SQLPlus](https://skip.dev/docs/modules/skip-sql/#sqlplus) instead. * * * ### SQLPlus [Section titled “SQLPlus”](https://skip.dev/docs/modules/skip-sql/#sqlplus) The `skip-sql` framework includes an additional `SQLPlus` module, which creates a local build with the following extensions enabled: * Full Text Search (FTS) * Encryption (sqlcipher) The `SQLPlus` module uses sqlite version 3.50.4, which means that it will be safe to use newer sqlite features like the [`json` ↗](https://sqlite.org/json1.html) function, RIGHT and FULL outer joins, and full text search, regardless of the Android API and iOS versions of the deployment platform. This comes at the cost of additional build time for the native libraries, as well as a larger artifact size (around 1MB on iOS and 4MB on Android), but has the benefit that every device you deploy your app to — on iOS and Android — will be using the exact same build of SQLite. #### Using SQLPlus [Section titled “Using SQLPlus”](https://skip.dev/docs/modules/skip-sql/#using-sqlplus) The SQLPlus extensions can be used by importing the `SkipSQLPlus` module and passing `configuration: .plus` to the `SQLContext` constructor, like so: import SkipSQLCoreimport SkipSQLPlus let dbpath = URL.applicationSupportDirectory.appendingPathComponent("db.sqlite")let db = try SQLContext(path: dbpath.path, flags: [.create, .readWrite], configuration: .plus)// do something with the databasedb.close() #### JSON [Section titled “JSON”](https://skip.dev/docs/modules/skip-sql/#json) SQLPlus enables the [`json` ↗](https://sqlite.org/json1.html) extensions: let sqlplus = SQLContext(configuration: .plus)try sqlplus.exec(sql: #"CREATE TABLE users (id INTEGER PRIMARY KEY, profile JSON)"#) try sqlplus.exec(sql: #"INSERT INTO users (id, profile) VALUES (1, '{"name": "Alice", "age": 30}')"#)try sqlplus.exec(sql: #"INSERT INTO users (id, profile) VALUES (2, '{"name": "Bob", "age": 25}')"#) let j1 = try sqlplus.selectAll(sql: #"SELECT json_extract(profile, '$.name') as name FROM users WHERE id = ?"#, parameters: [.integer(1)]).firstassert j1 == [.text("Alice")] let j2 = try sqlplus.selectAll(sql: #"SELECT json_extract(profile, '$.name') as name, json_extract(profile, '$.age') as age FROM users WHERE id = ?"#, parameters: [.integer(2)]).firstassert j2 == [.text("Bob"), .integer(25)] #### Encryption [Section titled “Encryption”](https://skip.dev/docs/modules/skip-sql/#encryption) SQLPlus contains the SQLCipher extension, which adds 256 bit AES encryption of database files and other security features like: * On-the-fly encryption * Tamper detection * Memory sanitization * Strong key derivation SQLCipher is based on SQLite and stable upstream release features are periodically integrated. The extension is documented at the official [SQLCipher site ↗](https://www.zetetic.net/sqlcipher/) . It is used by many mobile applications like the [Signal ↗](https://github.com/signalapp/sqlcipher) iOS and Android app to secure local database files. Cryptographic algorithms are provided by the [LibTomCrypt ↗](https://github.com/libtom/libtomcrypt) C library, which is included alongside the sqlcipher sources. An example of creating an encryped database: import SkipSQLCoreimport SkipSQLPlus let dbpath = URL.applicationSupportDirectory.appendingPathComponent("encrypted.sqlite")let db = try SQLContext(path: dbpath.path, flags: [.create, .readWrite], configuration: .plus)try db.exec(sql: "PRAGMA key = 'password'")try db.exec(sql: "CREATE TABLE SOME_TABLE(col)")try db.exec(sql: "INSERT INTO SOME_TABLE(col) VALUES(?)", parameters: [.text("SOME SECRET STRING")])try db.close() ##### Encrypting an unencrypted database [Section titled “Encrypting an unencrypted database”](https://skip.dev/docs/modules/skip-sql/#encrypting-an-unencrypted-database) Note that setting the key on the database must be the first operation that is performed after the database is opened, before any other SQL is executed. To encrypt an unencryped database that has already been created, the database must be exported with the `export(path, key)` function and then re-opened with the key. An example utility extension to do this is: extension SQLContext { /// Takes an unencrypted database and encrypts it with the given key func encryptDatabase(key: String, at dbPath: URL) throws -> SQLContext { let v = self.userVersion let tmpDBURL = dbPath.appendingPathExtension("rekey") // create a new temporary location to encrypt the database try self.export(tmpDBURL.path, key: key) try self.close() // disconnect the current DB so we can safely delete and overwrite it // move the encrypted database to the new path try FileManager.default.removeItem(at: dbPath) try FileManager.default.moveItem(at: tmpDBURL, to: dbPath) // reconnect to the newly converted database let ctx = try SQLContext(path: dbPath.path, flags: .readWrite, configuration: .plus) try ctx.key(key) // set the key on the database // re-set the userVersion, which is not copied by pragma sqlcipher_export: // “sqlcipher_export does not alter the user_version of the target database. Applications are free to do this themselves.” – https://www.zetetic.net/sqlcipher/sqlcipher-api/#notes-export ctx.userVersion = v return ctx // return the newly-created context }} Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-sql/#building) This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-sql/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-sql/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes --- # Toolbar | Skip [Skip to content](https://skip.dev/docs/components/toolbar/#_top) Toolbar ======= Skip support for [SwiftUI.View.toolbar ↗](https://developer.apple.com/documentation/SwiftUI/Toolbars) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ToolbarPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ToolbarPlayground.swift) ![Android screenshot for Toolbar component (light mode)](http://assets.skip.dev/showcase/Toolbar-android-light_framed.png) ![iPhone screenshot for Toolbar component (light mode)](http://assets.skip.dev/showcase/Toolbar-iphone-light_framed.png) ![iPhone screenshot for Toolbar component (dark mode)](http://assets.skip.dev/showcase/Toolbar-iphone-dark_framed.png) ![Android screenshot for Toolbar component (dark mode)](http://assets.skip.dev/showcase/Toolbar-android-dark_framed.png) import SwiftUI enum ToolbarPlaygroundType: String, CaseIterable { case `default` case tint case custom case label case toolbarItem case toolbarItemGroup case topLeadingItem case topLeadingItemGroup case topLeadingBackButtonHidden case topLeadingTrailingItems case bottom case bottomGroup case bottomSpaced var title: String { switch self { case .default: return "Default" case .tint: return "Tint" case .custom: return "Custom" case .label: return "Label" case .toolbarItem: return "ToolbarItem" case .toolbarItemGroup: return "ToolbarItemGroup" case .topLeadingItem: return ".topLeading" case .topLeadingItemGroup: return ".topLeading Group" case .topLeadingBackButtonHidden: return ".topLeading Back Hidden" case .topLeadingTrailingItems: return "Both Top" case .bottom: return "Bottom" case .bottomGroup: return "Bottom 3 Group" case .bottomSpaced: return "Bottom 3 Spaced" } }} struct ToolbarPlayground: View { var body: some View { List(ToolbarPlaygroundType.allCases, id: \.self) { type in NavigationLink(type.title, value: type) } .toolbar { PlaygroundSourceLink(file: "ToolbarPlayground.swift") } .navigationDestination(for: ToolbarPlaygroundType.self) { switch $0 { case .default: DefaultToolbarItemPlayground() .navigationTitle($0.title) case .tint: TintToolbarItemGroupPlayground() .navigationTitle($0.title) case .custom: CustomToolbarItemPlayground() .navigationTitle($0.title) case .label: LabelToolbarItemPlayground() .navigationTitle($0.title) case .toolbarItem: ToolbarItemPlayground(placement: ToolbarItemPlacement.automatic, placement2: ToolbarItemPlacement.automatic) .navigationTitle($0.title) case .toolbarItemGroup: ToolbarItemGroupPlayground(placement: ToolbarItemPlacement.automatic) .navigationTitle($0.title) case .topLeadingItem: #if os(macOS) // ToolbarItemPlacement.topBarLeading unavailable on macOS #else ToolbarItemPlayground(placement: ToolbarItemPlacement.topBarLeading) .navigationTitle($0.title) #endif case .topLeadingItemGroup: #if os(macOS) // ToolbarItemPlacement.topBarLeading unavailable on macOS #else ToolbarItemGroupPlayground(placement: ToolbarItemPlacement.topBarLeading) .navigationTitle($0.title) #endif case .topLeadingBackButtonHidden: #if os(macOS) // ToolbarItemPlacement.topBarLeading unavailable on macOS #else ToolbarBackButtonHiddenPlayground() .navigationTitle($0.title) #endif case .topLeadingTrailingItems: #if os(macOS) // ToolbarItemPlacement.topBarLeading unavailable on macOS #else ToolbarItemPlayground(placement: ToolbarItemPlacement.topBarLeading, placement2: ToolbarItemPlacement.topBarTrailing) .navigationTitle($0.title) #endif case .bottom: #if os(macOS) // ToolbarItemPlacement.bottomBar unavailable on macOS #else ToolbarItemPlayground(placement: ToolbarItemPlacement.bottomBar, placement2: ToolbarItemPlacement.bottomBar) .navigationTitle($0.title) #endif case .bottomGroup: #if os(macOS) // ToolbarItemPlacement.bottomBar unavailable on macOS #else ToolbarBottomThreePlayground(spaced: false) .navigationTitle($0.title) #endif case .bottomSpaced: #if os(macOS) // ToolbarItemPlacement.bottomBar unavailable on macOS #else ToolbarBottomThreePlayground(spaced: true) .navigationTitle($0.title) #endif } } }} struct DefaultToolbarItemPlayground: View { @Environment(\.dismiss) var dismiss @State var firstTapCount = 0 @State var secondTapCount = 0 var body: some View { List { Button("Pop") { dismiss() } ForEach(0..<100) { i in Text("Content \(i)") } } .toolbar { Button("First: \(firstTapCount)") { firstTapCount += 1 } Button("Second: \(secondTapCount)") { secondTapCount += 1 } } }} struct CustomToolbarItemPlayground: View { @Environment(\.dismiss) var dismiss var body: some View { List { Button("Pop") { dismiss() } ForEach(0..<100) { i in Text("Content \(i)") } } .toolbar { Ellipse() .fill(.red.gradient) .frame(width: 100.0, height: 50.0) .onTapGesture { dismiss() } } }} struct LabelToolbarItemPlayground: View { @Environment(\.dismiss) var dismiss var body: some View { List { Button("Pop") { dismiss() } ForEach(0..<100) { i in Text("Content \(i)") } } .toolbar { Button(action: { dismiss() }) { Label("Dismiss", systemImage: "trash") } } }} struct TintToolbarItemGroupPlayground: View { @Environment(\.dismiss) var dismiss @State var firstTapCount = 0 @State var secondTapCount = 0 var body: some View { List { Button("Pop") { dismiss() } ForEach(0..<100) { i in Text("Content \(i)") } } .toolbar { ToolbarItemGroup { Button("First: \(firstTapCount)") { firstTapCount += 1 } .tint(.red) Button("Second: \(secondTapCount)") { secondTapCount += 1 } .tint(.green) } } }} struct ToolbarItemPlayground: View { @Environment(\.dismiss) var dismiss @State var firstTapCount = 0 @State var secondTapCount = 0 let placement: ToolbarItemPlacement var placement2: ToolbarItemPlacement? = nil var body: some View { List { Button("Pop") { dismiss() } ForEach(0..<100) { i in Text("Content \(i)") } } .toolbar { ToolbarItem(placement: placement) { Button("First: \(firstTapCount)") { firstTapCount += 1 } } if let placement2 { ToolbarItem(placement: placement2) { Button("Second: \(secondTapCount)") { secondTapCount += 1 } } } } }} struct ToolbarItemGroupPlayground: View { @Environment(\.dismiss) var dismiss @State var firstTapCount = 0 @State var secondTapCount = 0 let placement: ToolbarItemPlacement var body: some View { List { Button("Pop") { dismiss() } ForEach(0..<100) { i in Text("Content \(i)") } } .toolbar { ToolbarItemGroup(placement: placement) { Button("First: \(firstTapCount)") { firstTapCount += 1 } Button("Second: \(secondTapCount)") { secondTapCount += 1 } } } }} struct ToolbarBottomThreePlayground: View { @Environment(\.dismiss) var dismiss @State var firstTapCount = 0 @State var secondTapCount = 0 @State var thirdTapCount = 0 let spaced: Bool var body: some View { List { Button("Pop") { dismiss() } ForEach(0..<100) { i in Text("Content \(i)") } } .toolbar { #if os(macOS) // ToolbarItemPlacement.bottomBar unavailable on macOS #else ToolbarItemGroup(placement: .bottomBar) { Button("First: \(firstTapCount)") { firstTapCount += 1 } if spaced { Spacer() } Button("Second: \(secondTapCount)") { secondTapCount += 1 } if spaced { Spacer() } Button("Third: \(thirdTapCount)") { thirdTapCount += 1 } } #endif } }} struct ToolbarBackButtonHiddenPlayground: View { @Environment(\.dismiss) var dismiss var body: some View { List { ForEach(0..<100) { i in Text("Content \(i)") } } .navigationBarBackButtonHidden() .toolbar { #if os(macOS) // ToolbarItemPlacement.topBarLeading unavailable on macOS #else ToolbarItem(placement: .topBarLeading) { Button("Cancel") { dismiss() } } #endif } }} --- # Hiya Skip (Split Mixed) | Skip [Skip to content](https://skip.dev/docs/samples/skipapp-hiya/#_top) Hiya Skip (Split Mixed) ======================= This is a Skip dual-platform app project that demonstrates the integration between a native Swift model layer compiled for Android connected to a transpiled SwiftUI user interface. For more information on using natively-compiled Swift on Android, see the [native swift documentation](https://skip.dev/docs/native) . This is the exact project with will be output when running the command: skip init --native-model --appid=hiya.skip skipapp-hiya HiyaSkip HiyaSkipModel HiyaSkipLogic The package will contain three modules: 1. A top-level `HiyaSkip` target that uses SwiftUI that will be transpiled to Jetpack Compose on Android to make up the user interface portion of the app 2. A `HiyaSkipModel` model layer that will be compiled natively for both Android and iOS. It contains an `@Observable ViewModel` that will be used directly on iOS, and on Android will be bridged to the transpiled user-interface layer using [SkipFuse](https://skip.dev/docs/modules/skip-fuse) and [SkipBridge](https://skip.dev/docs/modules/skip-bridge) . 3. A pure swift cross-platform `HiyaSkipLogic` module that does not use any bridging, and is depended on by `HiyaSkipModel` The project structure looks like: skipapp-hiya/├── Android│   ├── app│   │   ├── build.gradle.kts│   │   ├── proguard-rules.pro│   │   └── src│   │   └── main│   │   ├── AndroidManifest.xml│   │   └── kotlin│   │   └── hiya│   │   └── skip│   │   └── Main.kt│   ├── gradle.properties│   └── settings.gradle.kts├── Darwin│   ├── Assets.xcassets│   │   ├── AccentColor.colorset│   │   │   └── Contents.json│   │   └── Contents.json│   ├── Entitlements.plist│   ├── HiyaSkip.xcconfig│   ├── HiyaSkip.xcodeproj│   │   └── project.pbxproj│   ├── Info.plist│   └── Sources│   └── HiyaSkipAppMain.swift├── Package.swift├── README.md├── Skip.env├── Sources│   ├── HiyaSkip│   │   ├── ContentView.swift│   │   ├── HiyaSkipApp.swift│   │   ├── Resources│   │   │   ├── Localizable.xcstrings│   │   │   └── Module.xcassets│   │   │   └── Contents.json│   │   └── Skip│   │   └── skip.yml│   ├── HiyaSkipLogic│   │   └── HiyaSkipLogic.swift│   └── HiyaSkipModel│   ├── Resources│   │   └── Localizable.xcstrings│   ├── Skip│   │   └── skip.yml│   └── ViewModel.swift└── Tests ├── HiyaSkipModelTests │   ├── HiyaSkipModelTests.swift │   ├── Resources │   │   └── TestData.json │   ├── Skip │   │   └── skip.yml │   └── XCSkipTests.swift └── HiyaSkipTests ├── HiyaSkipTests.swift ├── Resources │   └── TestData.json ├── Skip │   └── skip.yml └── XCSkipTests.swift Building -------- [Section titled “Building”](https://skip.dev/docs/samples/skipapp-hiya/#building) This project is both a stand-alone Swift Package Manager module, as well as an Xcode project that builds and transpiles the project into a Kotlin Gradle project for Android using the Skip plugin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary transpiler prerequisites: Kotlin, Gradle, and the Android build tools. Installation prerequisites can be confirmed by running `skip checkup`. Testing ------- [Section titled “Testing”](https://skip.dev/docs/samples/skipapp-hiya/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/samples/skipapp-hiya/#contributing) We welcome contributions to this package in the form of enhancements and bug fixes. The general flow for contributing to this and any other Skip package is: 1. Fork this repository and enable actions from the “Actions” tab 2. Check out your fork locally 3. When developing alongside a Skip app, add the package to a [shared workspace](https://skip.dev/docs/contributing) to see your changes incorporated in the app 4. Push your changes to your fork and ensure the CI checks all pass in the Actions tab 5. Add your name to the Skip [Contributor Agreement ↗](https://source.skip.dev/clabot-config) 6. Open a Pull Request from your fork with a description of your changes Running ------- [Section titled “Running”](https://skip.dev/docs/samples/skipapp-hiya/#running) Xcode and Android Studio must be downloaded and installed in order to run the app in the iOS simulator / Android emulator. An Android emulator must already be running, which can be launched from Android Studio’s Device Manager. To run both the Swift and Kotlin apps simultaneously, launch the HiyaSkipApp target from Xcode. A build phases runs the “Launch Android APK” script that will deploy the transpiled app a running Android emulator or connected device. Logging output for the iOS app can be viewed in the Xcode console, and in Android Studio’s logcat tab for the transpiled Kotlin app. --- # Shape | Skip [Skip to content](https://skip.dev/docs/components/shape/#_top) Shape ===== Skip support for [SwiftUI.Shape ↗](https://developer.apple.com/documentation/swiftui/shape) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ShapePlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ShapePlayground.swift) ![Android screenshot for Shape component (light mode)](http://assets.skip.dev/showcase/Shape-android-light_framed.png) ![iPhone screenshot for Shape component (light mode)](http://assets.skip.dev/showcase/Shape-iphone-light_framed.png) ![iPhone screenshot for Shape component (dark mode)](http://assets.skip.dev/showcase/Shape-iphone-dark_framed.png) ![Android screenshot for Shape component (dark mode)](http://assets.skip.dev/showcase/Shape-android-dark_framed.png) import SwiftUI struct ShapePlayground: View { var body: some View { ScrollView { VStack(spacing: 16.0) { HStack { Text("Capsule") Spacer() ZStack { Capsule() .fill() } .frame(width: 100.0, height: 50.0) .border(.blue) } HStack { Text("Capsule") Spacer() ZStack { Capsule() .fill() } .frame(width: 50.0, height: 100.0) .border(.blue) } HStack { Text("Circle") Spacer() ZStack { Circle() .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("Circle") Spacer() ZStack { Circle() .fill() } .frame(width: 100.0, height: 50.0) .border(.blue) } HStack { Text("Ellipse") Spacer() ZStack { Ellipse() .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("Ellipse") Spacer() ZStack { Ellipse() .fill() } .frame(width: 100.0, height: 50.0) .border(.blue) } HStack { Text("Rectangle") Spacer() ZStack { Rectangle() .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("Rectangle") Spacer() ZStack { Rectangle() .fill() } .frame(width: 100.0, height: 50.0) .border(.blue) } HStack { Text("RoundedRectangle") Spacer() ZStack { RoundedRectangle(cornerRadius: 40.0) .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("RoundedRectangle") Spacer() ZStack { RoundedRectangle(cornerSize: CGSize(width: 40.0, height: 20.0)) .fill() } .frame(width: 100.0, height: 50.0) .border(.blue) } HStack { Text("UnevenRoundedRectangle") Spacer() ZStack { UnevenRoundedRectangle(topLeadingRadius: 10.0, bottomLeadingRadius: 20.0, bottomTrailingRadius: 30.0, topTrailingRadius: 40.0) .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } Text("Custom").font(.title).bold() HStack { Text("Fill") Spacer() ZStack { CustomShape() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("Stroke") Spacer() ZStack { CustomShape() .stroke(lineWidth: 10.0) } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("Rotation") Spacer() ZStack { customPath(in: CGSize(width: 100.0, height: 100.0), transform: CGAffineTransform(rotationAngle: Angle(degrees: 30.0).radians)) } .frame(width: 100.0, height: 100.0) .border(.blue) } Text("Fill & Stroke").font(.title).bold() HStack { Text("fill(.red)") Spacer() ZStack { Circle() .fill(.red) } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("fill(.red.gradient)") Spacer() ZStack { Circle() .fill(.red.gradient) } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("stroke()") Spacer() ZStack { Circle() .stroke() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("stroke(.red, lineWidth: 10)") Spacer() ZStack { Circle() .stroke(.red, lineWidth: 10.0) } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("stroke(.red.gradient, lineWidth: 10)") Spacer() ZStack { Circle() .stroke(.red.gradient, style: StrokeStyle(lineWidth: 10.0)) } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("stroke(.red,\n style: StrokeStyle(\n lineWidth: 10.0,\n dash: [10]))") Spacer() ZStack { Circle() .stroke(.red, style: StrokeStyle(lineWidth: 10.0, dash: [10.0])) } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("strokeBorder(.red, lineWidth: 10)") Spacer() ZStack { Circle() .strokeBorder(.red, lineWidth: 10.0) } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("fill(.red)\n .stroke(.green, lineWidth: 10)") Spacer() ZStack { if #available(iOS 17, macOS 14, *) { Circle() .fill(.red) .stroke(.green, lineWidth: 10.0) } } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("stroke x2") Spacer() ZStack { if #available(iOS 17, macOS 14, *) { Circle() .stroke(.red, lineWidth: 10.0) .stroke(.green, lineWidth: 5.0) } } .frame(width: 100.0, height: 100.0) .border(.blue) } Text("Transforms").font(.title).bold() HStack { Text("inset(by: 10)") Spacer() ZStack { Rectangle() .inset(by: 10.0) .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("offset(x: 30, y: 10)") Spacer() ZStack { Rectangle() .offset(x: 30.0, y: 10.0) .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("rotation(Angle(degrees: 45))") Spacer() ZStack { Rectangle() .rotation(Angle(degrees: -30.0)) .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("scale(x: 0.5, y: 1.2)") Spacer() ZStack { Rectangle() .scale(x: 0.5, y: 1.2) .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("scale(x: 1.0, y: -1.0)") Spacer() ZStack { UnevenRoundedRectangle(topLeadingRadius: 10.0, bottomLeadingRadius: 20.0, bottomTrailingRadius: 30.0, topTrailingRadius: 40.0) .scale(x: 1.0, y: -1.0) .fill() } .frame(width: 100.0, height: 100.0) .border(.blue) } HStack { Text("scale, rotate, offset, stroke") Spacer() ZStack { Rectangle() .scale(x: 0.5, y: 1.2) .rotation(Angle(degrees: -30.0)) .offset(x: 30.0, y: 10.0) .stroke(.red, lineWidth: 10.0) } .frame(width: 100.0, height: 100.0) .border(.blue) } }.padding() } .toolbar { PlaygroundSourceLink(file: "ShapePlayground.swift") } }} struct CustomShape: Shape { let arcSize = 20.0 func path(in rect: CGRect) -> Path { var path = Path() path.move(to: CGPoint(x: rect.minX, y: rect.minY + arcSize)) path.addLine(to: CGPoint(x: rect.maxX - arcSize - arcSize * 2, y: rect.minY + arcSize)) path.addRelativeArc(center: CGPoint(x: rect.maxX - arcSize - arcSize, y: rect.minY + arcSize), radius: arcSize, startAngle: Angle(degrees: -180.0), delta: Angle(degrees: 180.0)) path.addArc(center: CGPoint(x: rect.maxX - arcSize, y: rect.minY + arcSize + arcSize), radius: arcSize, startAngle: Angle(degrees: -90.0), endAngle: Angle(degrees: 90.0), clockwise: false) path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) return path }} func customPath(in size: CGSize, transform: CGAffineTransform) -> Path { var path = Path() let rect = CGRect(origin: .zero, size: size).insetBy(dx: 20.0, dy: 0.0) path.addRect(rect, transform: transform) return path} --- # List | Skip [Skip to content](https://skip.dev/docs/components/list/#_top) List ==== Skip support for [SwiftUI.List ↗](https://developer.apple.com/documentation/swiftui/list) on Android. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . The following example screens and source code is from SkipUI’s [Showcase](https://skip.dev/docs/samples/skipapp-showcase/) sample app [`ListPlayground.swift` ↗](https://github.com/skiptools/skipapp-showcase/tree/main/Sources/Showcase/ListPlayground.swift) ![Android screenshot for List component (light mode)](http://assets.skip.dev/showcase/List-android-light_framed.png) ![iPhone screenshot for List component (light mode)](http://assets.skip.dev/showcase/List-iphone-light_framed.png) ![iPhone screenshot for List component (dark mode)](http://assets.skip.dev/showcase/List-iphone-dark_framed.png) ![Android screenshot for List component (dark mode)](http://assets.skip.dev/showcase/List-android-dark_framed.png) import SwiftUI enum ListPlaygroundType: String, CaseIterable { case fixedContent case indexedContent case collectionContent case forEachContent case sectioned case empty case emptyItems case plainStyle case plainStyleSectioned case plainStyleEmpty case hiddenBackground case hiddenBackgroundPlainStyle case editActions case observableEditActions case sectionedEditActions case onMoveDelete var title: String { switch self { case .fixedContent: return "Fixed Content" case .indexedContent: return "Indexed Content" case .collectionContent: return "Collection Content" case .forEachContent: return "ForEach Content" case .sectioned: return "Sectioned" case .empty: return "Empty" case .emptyItems: return "Empty Items" case .plainStyle: return "Plain Style" case .plainStyleSectioned: return "Plain Style Sectioned" case .plainStyleEmpty: return "Plain Style Empty" case .hiddenBackground: return "Hidden Background" case .hiddenBackgroundPlainStyle: return "Hidden Background Plain Style" case .editActions: return "EditActions" case .observableEditActions: return "Observable EditActions" case .sectionedEditActions: return "Sectioned EditActions" case .onMoveDelete: return ".onMove, .onDelete" } }} struct ListPlayground: View { @StateObject var editActionsModel = ObservableEditActionsListPlayground.Model() var body: some View { List(ListPlaygroundType.allCases, id: \.self) { type in NavigationLink(type.title, value: type) } .toolbar { PlaygroundSourceLink(file: "ListPlayground.swift") } .navigationDestination(for: ListPlaygroundType.self) { switch $0 { case .fixedContent: FixedContentListPlayground() .navigationTitle($0.title) case .indexedContent: IndexedContentListPlayground() .navigationTitle($0.title) case .collectionContent: CollectionContentListPlayground() .navigationTitle($0.title) case .forEachContent: ForEachContentListPlayground() .navigationTitle($0.title) case .sectioned: SectionedListPlayground() .navigationTitle($0.title) case .empty: EmptyListPlayground() .navigationTitle($0.title) case .emptyItems: EmptyItemsListPlayground() .navigationTitle($0.title) case .plainStyle: PlainStyleListPlayground() .navigationTitle($0.title) case .plainStyleSectioned: PlainStyleSectionedListPlayground() .navigationTitle($0.title) case .plainStyleEmpty: PlainStyleEmptyListPlayground() .navigationTitle($0.title) case .hiddenBackground: HiddenBackgroundListPlayground() .navigationTitle($0.title) case .hiddenBackgroundPlainStyle: HiddenBackgroundPlainStyleListPlayground() .navigationTitle($0.title) case .editActions: EditActionsListPlayground() .navigationTitle($0.title) case .observableEditActions: ObservableEditActionsListPlayground(model: editActionsModel) .navigationTitle($0.title) case .sectionedEditActions: SectionedEditActionsListPlayground() .navigationTitle($0.title) case .onMoveDelete: OnMoveDeleteListPlayground() .navigationTitle($0.title) } } }} struct FixedContentListPlayground: View { var body: some View { List { Group { Text("Row 1") Text("Row 2") } VStack { Text("Row 3a") Text("Row 3b") } } }} struct IndexedContentListPlayground: View { var body: some View { List(100..<200) { Text("Row \($0)") } }} struct CollectionContentListPlayground: View { struct ListItem { let i: Int let s: String } func items() -> [ListItem] { var items: [ListItem] = [] for i in 0..<100 { items.append(ListItem(i: i, s: "Item \(i)")) } return items } var body: some View { List(items(), id: \.i) { Text($0.s) } }} struct ForEachContentListPlayground: View { struct ListItem { let id: UUID // Test ID serialization let i: Int let s: String } func items() -> [ListItem] { var items: [ListItem] = [] for i in 0..<10 { items.append(ListItem(id: UUID(), i: i, s: "Foreach object row \(i)")) } return items } var body: some View { List { Text("Standalone row 1") ForEach(0..<10) { index in Text("ForEach index row: \(index)") } Text("Standalone row 2") ForEach(items(), id: \.id) { Text($0.s) } Text("Standalone row 3") ForEach(0..<10) { index1 in ForEach(1..<3) { index2 in Text("Nested ForEach row: \(index1).\(index2)") } } } }} struct SectionedListPlayground: View { var body: some View { List { Section("Section 1") { Text("Row 1.1") ForEach(0..<10) { index in Text("ForEach row: 1.\(index)") } } Section { Text("Row 2.1") ForEach(0..<10) { index in Text("ForEach row: 2.\(index)") } } header: { Text("Section 2") } footer: { Text("Footer 2") } ForEach(0..<2) { index1 in Section("ForEach section \(index1)") { ForEach(0..<5) { index2 in Text("ForEach row: \(index1).\(index2)") } } } } }} struct EmptyListPlayground: View { var body: some View { List { } }} struct EmptyItemsListPlayground: View { var body: some View { List { EmptyView() Text("After initial empty row") Section("Section 1") { EmptyView() Text("After initial section empty row") EmptyView() Text("After another section empty row") } Section("Section 2") { Text("Before final section empty row") EmptyView() } Text("Before final empty row") EmptyView() } }} struct PlainStyleListPlayground: View { var body: some View { List(0..<100) { Text("Row \($0)") } .listStyle(.plain) }} struct PlainStyleSectionedListPlayground: View { var body: some View { List { Section("Section 1") { Text("Row 1.1") ForEach(0..<30) { index in Text("ForEach row: 1.\(index)") } } Section { Text("Row 2.1") ForEach(0..<30) { index in Text("ForEach row: 2.\(index)") } } header: { Text("Section 2") } footer: { Text("Footer 2") } Section { ForEach(0..<30) { index in Text("ForEach row: 3.\(index)") } } footer: { Text("Footer 3") } } .listStyle(.plain) }} struct PlainStyleEmptyListPlayground: View { var body: some View { List { } .listStyle(.plain) }} struct HiddenBackgroundListPlayground: View { var body: some View { ZStack { Color.yellow.opacity(0.5) List { Section("Standard Row Background") { ForEach(0..<30) { index in Text("Row: 1.\(index)") } } Section { ForEach(0..<30) { index in Text("Row: 2.\(index)") .listRowBackground(Color.pink) .listRowSeparator(.hidden) } } header: { Text("Pink Row Background") } footer: { Text("... and hidden separators") } } .scrollContentBackground(.hidden) } }} struct HiddenBackgroundPlainStyleListPlayground: View { var body: some View { ZStack { Color.yellow.opacity(0.5) List { Section("Section 1") { ForEach(0..<30) { index in Text("Row: 1.\(index)") } } Section { ForEach(0..<30) { index in Text("Row: 2.\(index)") .listRowBackground(Color.pink) .listRowSeparator(.hidden) } } header: { Text("Section 2") } footer: { Text("Footer") } } .listStyle(.plain) .scrollContentBackground(.hidden) } }} struct EditActionsListPlayground: View { struct ListItem { let i: Int let s: String var toggled = false } @State var items = { var items: [ListItem] = [] for i in 0..<50 { items.append(ListItem(i: i, s: "Item \(i)")) } return items }() var body: some View { List($items, id: \.i, editActions: .all) { $item in if item.i % 5 == 0 { Text("\(item.s) .deleteDisabled") .deleteDisabled(true) } else if item.i % 4 == 0 { Text("\(item.s) .moveDisabled") .moveDisabled(true) } else { HStack { Text(item.s) Spacer() Toggle("isOn", isOn: $item.toggled) .labelsHidden() } } } }} struct ObservableEditActionsListPlayground: View { class Model: ObservableObject { @Published var items: [ListItem] = { var items: [ListItem] = [] for i in 0..<50 { items.append(ListItem(i: i, s: "Item \(i)")) } return items }() } struct ListItem { let i: Int let s: String var toggled = false } @ObservedObject var model: Model var body: some View { List($model.items, id: \.i, editActions: .all) { $item in HStack { Text(item.s) Spacer() Toggle("isOn", isOn: $item.toggled) .labelsHidden() } } }} struct SectionedEditActionsListPlayground: View { @State var items0 = { var items: [Int] = [] for i in 0..<10 { items.append(i) } return items }() @State var items1 = { var items: [Int] = [] for i in 11..<20 { items.append(i) } return items }() @State var items2 = { var items: [Int] = [] for i in 21..<30 { items.append(i) } return items }() var body: some View { List { Section("Section 0 (Fixed)") { ForEach(items0, id: \.self) { item in Text("Item \(item)") } } Section("Section 1") { ForEach($items1, id: \.self, editActions: .all) { $item in Text("Item \(item)") } } Section("Section 2") { ForEach($items2, id: \.self, editActions: .all) { $item in Text("Item \(item)") } } } }} struct OnMoveDeleteListPlayground: View { @State var items0 = { var items: [Int] = [] for i in 0..<10 { items.append(i) } return items }() @State var items1 = { var items: [Int] = [] for i in 11..<20 { items.append(i) } return items }() @State var items2 = { var items: [Int] = [] for i in 21..<30 { items.append(i) } return items }() @State var actionString = "" @State var action: () -> Void = {} @State var actionIsPresented = false var body: some View { List { Section(".onMove") { ForEach(items0, id: \.self) { item in Text("Item \(item)") } .onMove { fromOffsets, toOffset in actionString = "Move \(fromOffsets.count) item(s)" action = { withAnimation { items0.move(fromOffsets: fromOffsets, toOffset: toOffset) } action = {} } actionIsPresented = true } } Section(".onDelete") { ForEach(items1, id: \.self) { item in Text("Item \(item)") } .onDelete { offsets in actionString = "Delete \(offsets.count) item(s)" action = { withAnimation { items1.remove(atOffsets: offsets) } action = {} } actionIsPresented = true } } Section(".onMove, .onDelete") { ForEach(items2, id: \.self) { item in Text("Item \(item)") } .onMove { fromOffsets, toOffset in actionString = "Move \(fromOffsets.count) item(s)" action = { withAnimation { items2.move(fromOffsets: fromOffsets, toOffset: toOffset) } action = {} } actionIsPresented = true } .onDelete { offsets in actionString = "Delete \(offsets.count) item(s)" action = { withAnimation { items2.remove(atOffsets: offsets) } action = {} } actionIsPresented = true } } } .confirmationDialog(actionString, isPresented: $actionIsPresented) { Button(actionString, role: .destructive, action: action) } }} --- # FAQs | Skip [Skip to content](https://skip.dev/docs/faq/#_top) FAQs ==== General FAQs ------------ [Section titled “General FAQs”](https://skip.dev/docs/faq/#general-faqs) ### What is Skip? [Section titled “What is Skip?”](https://skip.dev/docs/faq/#what_is_skip) Skip is an Xcode plugin that builds your Swift and SwiftUI for Android. The Skip build process enables you to continuously iterate on both your iOS and Android app with a single unified codebase, while mixing in as much or as little platform-specific code as you like. To learn more, start with the [documentation](https://skip.dev/docs/) . ### How does Skip compare to other frameworks like Flutter, React Native, Xamarin, and Cordova? [Section titled “How does Skip compare to other frameworks like Flutter, React Native, Xamarin, and Cordova?”](https://skip.dev/docs/faq/#flutter) Skip is focused on genuinely native app development exclusively for the two dominant mobile platforms – iOS and Android. A comparison table between the various frameworks can be found on the [Skip Comparison Matrix](https://skip.dev/compare) . ### What is Swift? [Section titled “What is Swift?”](https://skip.dev/docs/faq/#swift) Swift is the officially-recommended language for app development for iOS devices, including the iPhone and iPad. Swift integrates seamlessly with Objective-C, the legacy language for iOS development. For information on Swift, see [https://developer.apple.com/swift/ ↗](https://developer.apple.com/swift/) . ### What is SwiftUI? [Section titled “What is SwiftUI?”](https://skip.dev/docs/faq/#swiftui) According to [developer.apple.com ↗](https://developer.apple.com/ios/planning/#build-the-data-structures-youll-use-in-your-app) , SwiftUI “offers a modern, platform-agnostic approach to building your UI and app infrastructure. With SwiftUI, you specify your interface programmatically and let the system display and update that interface dynamically”. Since debuting in 2019, SwiftUI can already be used to build apps for the iPhone, iPad, tvOS, watchOS, visionOS, and other operating systems, and has become the recommended way to create new apps for the iPhone and iPad. Skip brings SwiftUI to the Android platform through the SkipStone plugin and the [SkipUI compatibility framework](https://skip.dev/docs/faq/#skipstack) . ### What is Kotlin? [Section titled “What is Kotlin?”](https://skip.dev/docs/faq/#kotlin) Kotlin is the officially-recommended language for app development for Android devices. Kotlin integrates seamlessly with Java, the legacy language for Android development. For information on Kotlin and Android, see [https://developer.android.com/kotlin/ ↗](https://developer.android.com/kotlin/) . ### What is Jetpack Compose? [Section titled “What is Jetpack Compose?”](https://skip.dev/docs/faq/#compose) According to [developer.android.com ↗](https://developer.android.com/jetpack/compose) : “Jetpack Compose is Android’s recommended modern toolkit for building native UI.” Jetpack Compose, which debuted in 2019, presents a declarative Kotlin API for platform-agnostic user interfaces. ### What are “Native” and “Transpiled” modes? [Section titled “What are “Native” and “Transpiled” modes?”](https://skip.dev/docs/faq/#modes) Skip supports both _compiling_ Swift natively for Android, and _transpiling_ Swift into Kotlin. Read about Skip’s modes in the [documentation](https://skip.dev/docs/modes/) . ### What is a “Transpiler”? [Section titled “What is a “Transpiler”?”](https://skip.dev/docs/faq/#transpiler) Transpilation is defined as the conversion between programming languages that operate at approximately the same level of abstraction. The SkipStone plugin has both native and transpiled modes, but even native-mode apps require some Kotlin code generation to interface with the Android system. In additional to generating Kotlin and Compose, the SkipStone plugin transpiles your app’s Swift Package Manager project into a buildable Gradle project and transpiles your iOS app’s metadata into Android metadata. This is all done automatically by Xcode, the result being that you can build, test, and run your Swift app, and the transpiled equivalent app will also be built, tested, and run side-by-side. ### Do I need to know Kotlin to develop with Skip? [Section titled “Do I need to know Kotlin to develop with Skip?”](https://skip.dev/docs/faq/#kotlin_experience) Skip provides [several mechanisms](https://skip.dev/docs/platformcustomization/) to integrate with Kotlin, but you do not need to know any Kotlin in order to build an app with Skip. You will likely learn some Kotlin as you develop with Skip, however - particularly when integrating Android-specific features. ### What are the system requirements for using Skip? [Section titled “What are the system requirements for using Skip?”](https://skip.dev/docs/faq/#sysreq) macOS 15 (ARM or Intel) with Xcode 16.4 or higher. Additional software is required to build and test projects for Android. See Skip’s [installation instructions](https://skip.dev/docs/gettingstarted) . ### Can Skip run on Linux or Windows? [Section titled “Can Skip run on Linux or Windows?”](https://skip.dev/docs/faq/#linux) Skip does have limited support for Linux (and Windows via WSL). The `skip` tool itself can be installed with Homebrew (see [Getting Started](https://skip.dev/docs/gettingstarted/) ), and `skip` can be used to create projects, build frameworks, run in CI, and act as a frontend to `skip android [build|test|sdk|emulator]`. However, it cannot yet be used to build complete apps due to the reliance on the SwiftUI and Xcode toolchain distributed as part of macOS. We are considering ways of expanding Skip’s utility on Linux and Windows for the future. ### Which parts of Skip are free and which parts are commercial? [Section titled “Which parts of Skip are free and which parts are commercial?”](https://skip.dev/docs/faq/#free_commercial) Earlier, Skip would require a license key for non-commercial app development. No longer: Skip is now completely free and open-source, but please do consider [supporting](https://skip.dev/sponsor/) the project. ### Which FOSS license is recommended for framework development? [Section titled “Which FOSS license is recommended for framework development?”](https://skip.dev/docs/faq/#foss_license_recommendations) We generally recommend the LGPL, which provides a good balance of flexibility and freedom. LGPL libraries can be embedded in closed-source commercial applications. Many popular software components like WebKit, JavaScriptCore, and Qt are distributed under the LGPL. ### Can Skip Apps be distributed on the Apple App Store and the Google Play Store? [Section titled “Can Skip Apps be distributed on the Apple App Store and the Google Play Store?”](https://skip.dev/docs/faq/#appstore) Yes. Applications built with Skip utilize the user interface toolkits recommended by the platform vendors themselves, and thereby follow the guidelines and principles that are unique to each of these two individual platforms. On the iOS side, the [Skip Core Framrwork](https://skip.dev/docs/faq/#skipstack) modules can be eliminated from the build (with [SkipZero](https://skip.dev/docs/faq/#skipzero) ), resulting in an app submission that contains no trace of any Skip libraries whatsoever. One example of an app that is distributed on the Apple App Store and the Google Play Store is the [Skip Showcase](https://skip.dev/docs/samples/skipapp-showcase/) app. ### What does it mean it be Genuinely Native? [Section titled “What does it mean it be Genuinely Native?”](https://skip.dev/docs/faq/#genuinely_native) Skip apps are genuinely native, in that they use the vendor-recommended UI toolkits directly, thereby guaranteeing maximum compatibility, accessibility, and performance. They use SwiftUI directly on the iPhone (“_SwiftUI is the preferred app-builder technology, because it offers a modern, platform-agnostic approach to building your UI and app infrastructure._” – [developer.apple.com ↗](https://developer.apple.com/ios/planning/#build-the-data-structures-youll-use-in-your-app) ) and Compose on Android (“_We recommend using Jetpack Compose if you’re looking to build a new app_” – [android-developers.googleblog.com ↗](https://android-developers.googleblog.com/2023/02/hundreds-of-thousands-of-developers-are-learning-jetpack-compose.html) ). ### What is Skip Zero? [Section titled “What is Skip Zero?”](https://skip.dev/docs/faq/#skipzero) When a Skip app relies only on the [Skip Core Frameworks](https://skip.dev/docs/faq/#skipstack) modules (SkipUnit, SkipLib, SkipFoundation, SkipModel, and SkipUI), these libraries can be completely excluded from the app build. This means that you can ship a Skip app for Android and iOS, and there will be no vestige of Skip on the iOS side at all. One benefit, among many others, is that your app distribution can be minuscule (e.g., the [HelloSkip.ipa](https://github.com/skiptools/skipapp-hello/releases) sample is under 25KB). This also makes Skip easily “ejectable” from your iOS project, in that you can drop the Skip tools at any time and continue to have a fully-functional iOS project. ### Is Skip a cloud-based service? Do you upload my code to your servers? [Section titled “Is Skip a cloud-based service? Do you upload my code to your servers?”](https://skip.dev/docs/faq/#cloud) No, and no. The Skip build plugin runs locally on your macOS machine as part of the Xcode build process. The plugin is secured using the standard Xcode plugin sandboxing mechanisms that block network and file system access. Neither your Swift nor any generated code ever leave your machine. The only network requirements are for resolving any external dependencies your project has, either on the SwiftPM side for Swift dependencies, or on the Gradle side for Kotlin dependencies. ### How is Skip distributed? [Section titled “How is Skip distributed?”](https://skip.dev/docs/faq/#distribution) The SkipStone build plugin is distributed as a binary executable artifact and driven by the Skip plugin hosted at [https://source.skip.tools/skip.git ↗](https://source.skip.tools/skip.git) . In addition, the `skip` command-line tool must be installed using the [Homebrew ↗](https://brew.sh/) command: `brew install skiptools/skip/skip`. This will install the `skip` utility that can be used to create projects and run tests, as well as the prerequisites for being able to perform local Android testing: `gradle`, `openjdk`, `android-platform-tools`, and `android-commandlinetools`. ### Do Skip or any of the Skip frameworks collect data for the purposes of user tracking? [Section titled “Do Skip or any of the Skip frameworks collect data for the purposes of user tracking?”](https://skip.dev/docs/faq/#tracking) The Skip plugin does not collect any data, perform any telemetry, or otherwise “phone home” unless explicitly requested (e.g., running the `skip upgrade` command to check for the latest version of Skip). The Skip frameworks, which are all free and open-source (and which can thus be audited externally), also do not collect any telemetry or track users in any way. We provide this notice to help you fill out [Apple’s App Privacy Details ↗](https://developer.apple.com/app-store/app-privacy-details/) and comply with [Google’s User Data ↗](https://support.google.com/googleplay/android-developer/answer/10144311) policies. Note that when installing `skip` using Homebrew, they may use telemetry to report the installation event. See the [Homebrew Analytics ↗](https://docs.brew.sh/Analytics#what) documentation for their explanation and details on how it can be disabled. ### How does Skip compare to low-code or no-code app-building technologies? [Section titled “How does Skip compare to low-code or no-code app-building technologies?”](https://skip.dev/docs/faq/#lowcode) Skip is the opposite of no-code/low-code offerings. Skip embraces the dominant emerging UI paradigm of declarative user interfaces written in the same language as the platform-native components. This means Swift for SwiftUI, and Kotlin for Compose. ### Does Skip use Artificial Intelligence? [Section titled “Does Skip use Artificial Intelligence?”](https://skip.dev/docs/faq/#ai) No, Skip is a purely offline tool with predictable behavior. However, AI tools can be very useful for generating Swift and SwiftUI code, especially for getting started with the outline of an iOS app. This code will generally be compatible with SkipUI and the other Skip libraries, which can be a great aid in getting a dual-platform app started quickly. Development FAQs ---------------- [Section titled “Development FAQs”](https://skip.dev/docs/faq/#development-faqs) ### What is the minimum iOS version for Skip apps? [Section titled “What is the minimum iOS version for Skip apps?”](https://skip.dev/docs/faq/#ios_version) iOS 16+. It is estimated at [developer.apple.com ↗](https://developer.apple.com/support/app-store/) that 96% of all devices introduced in the last four years use iOS 16 or higher, as measured by devices that transacted on the App Store as of February 4, 2024. ### What is the minimum Android version for Skip apps? [Section titled “What is the minimum Android version for Skip apps?”](https://skip.dev/docs/faq/#android_version) Skip targets Android API level 34 (which is the minimum allowed level for submitting new apps to the Play Store, according to [https://developer.android.com/google/play/requirements/target-sdk ↗](https://developer.android.com/google/play/requirements/target-sdk) ) with a minimum supported version of API level 28. Android API 28 (“P”; Android 9) will run on over 95% of active Android devices as of January, 2026, according to the device reach estimate by Android Studio. ### How large are apps that are built using Skip? [Section titled “How large are apps that are built using Skip?”](https://skip.dev/docs/faq/#app_size) For iOS apps utilizing [SkipZero](https://skip.dev/docs/faq/#skipzero) , the smallest `app.ipa` size is around 50KB. For Android apps, it is around 5MB. This difference is largely due to the fact that Android apps need to bundle the Jetpack Compose libraries with the app itself, whereas SwiftUI is included with iOS. ### Why is the first build of my app so slow? [Section titled “Why is the first build of my app so slow?”](https://skip.dev/docs/faq/#slow_build) Skip libraries are distributed through SwiftPM as source code, which means that your first build includes building portions of Skip itself as well. Additionally, the first time you build the generated Gradle project in order to test and run on Android, the Gradle build system will need to download all of its dependency jars and build tools. This can lead to the initial build of an app downloading many dependencies. The Compose dependencies alone will add around 1GB to the size of the Gradle cache folder, as seen with `du -skh ~/.gradle`. But once you have downloaded and cached your project’s dependencies, future builds shouldn’t need to fetch them again. ### Does Skip need network access? [Section titled “Does Skip need network access?”](https://skip.dev/docs/faq/#network_access) Skip itself is a locally-installed Xcode plugin and does not talk to the network, making it suitable for offline use. However, your app’s dependencies will need to be resolved and cached for your app to be built, tested, or run. This means that if you add any new dependencies, either on the Swift side in the `Package.swift`, or on the Kotlin side in the `build.gradle.kts` (as configured through the module’s `Skip/skip.yml` file), you will need to perform a build at least once for the dependencies to be resolved and downloaded. ### Does Skip support large codebases? [Section titled “Does Skip support large codebases?”](https://skip.dev/docs/faq/#large_codebase) Skip leverages Swift Package Manager’s support for dependencies between modules, in both local and external git repositories. Skip’s plugin is run individually on each transitive dependency of your project, and the resulting Gradle projects will have these dependencies mirrored. This results in a highly modularizable system, where large codebases can be distributed across multiple modules, each with their individual documentation, test cases, and [continuous integration workflows](https://skip.dev/docs/faq/#ci) . ### What is parity testing? [Section titled “What is parity testing?”](https://skip.dev/docs/faq/#parity_tests) Skip encourages modularization of large codebases, each with their individual test targets. The command `skip test` can be run in a project that contains Skip tests, and both the Swift `XCTest` cases and the generated Kotlin `JUnit` cases will be run, after which the tool will output a markdown table summarizing each of the test results for each of the platforms. ### What are the Skip Core Frameworks? [Section titled “What are the Skip Core Frameworks?”](https://skip.dev/docs/faq/#skipstack) The Skip Core Framework modules (also knows as the “Skip Stack”) are the core compatibility modules that transparently bridge from standard iOS frameworks into their equivalents for Kotlin, Java, and Android. They are implicitly imported in transpiled Skip Lite modules whenever their equivalent frameworks are imported. * [SkipUnit](https://skip.dev/docs/modules/skip-unit) bridges the [XCTest ↗](https://developer.apple.com/documentation/xctest) framework to provide unit testing support through [JUnit 4 ↗](https://junit.org/junit4/) . * [SkipLib](https://skip.dev/docs/modules/skip-lib) bridges the [Swift Standard Library ↗](https://developer.apple.com/documentation/swift/swift-standard-library) for conversion between low-level types and their [Kotlin equivalents ↗](https://kotlinlang.org/api/latest/jvm/stdlib/) . * [SkipFoundation](https://skip.dev/docs/modules/skip-foundation) provides Java and Android equivalents for the [Foundation ↗](https://developer.apple.com/documentation/foundation) framework * [SkipModel](https://skip.dev/docs/modules/skip-model) provides support for [Observation ↗](https://developer.apple.com/documentation/observation/) through the [Compose Runtime ↗](https://developer.android.com/jetpack/androidx/releases/compose-runtime) framework. * [SkipUI](https://skip.dev/docs/modules/skip-ui) provides an implementation of [SwiftUI ↗](https://developer.apple.com/documentation/swiftui) that bridges to Android’s [Jetpack Compose UI ↗](https://developer.android.com/jetpack/androidx/releases/compose-ui) components. The Skip Core Frameworks are the fundamental building blocks on which the rest of the [Skip ecosystem of libraries](https://skip.dev/docs/modules/) is based. ### Can I preview the Kotlin that Skip’s transpiled mode generates? [Section titled “Can I preview the Kotlin that Skip’s transpiled mode generates?”](https://skip.dev/docs/faq/#playground) For examples of the Swift to Kotlin transpilation, see the examples in the [Transpilation reference](https://skip.dev/docs/swiftsupport/#examples) . When using Skip for development, you can [access the transpiled Kotlin](https://skip.dev/docs/debugging/#accessing-transpiled-output) it generates at any time. ### How much runtime overhead does SkipUI add to the Compose side of the app? [Section titled “How much runtime overhead does SkipUI add to the Compose side of the app?”](https://skip.dev/docs/faq/#overhead) Very little. Many of SkipUI’s views are very simple shims that bridge from their SwiftUI equivalents into Compose, such as [Divider.swift ↗](https://source.skip.dev/skip-ui/blob/main/Sources/SkipUI/SkipUI/Components/Divider.swift) simply invoking `androidx.compose.material3.Divider`. To support `@Environment` and `@State`, Skip needs to perform some additional book-keeping that may have some runtime cost. ### Why does my Android app feel slow? [Section titled “Why does my Android app feel slow?”](https://skip.dev/docs/faq/#slow_android_app) There is often a significant difference between Debug and Release build performance on Android devices. Always use a Release build when evaluating real-world performance. ### What pure Swift libraries can be used by a Skip App? [Section titled “What pure Swift libraries can be used by a Skip App?”](https://skip.dev/docs/faq/#compat) Skip uses Swift Package Manager to handle source code dependency resolution. The [Swift Package Index ↗](https://swiftpackageindex.com/search?query=platform%3Aios%2Candroid) site tracks the thousands of Swift packages that compile for Android and are available to apps using Skip’s native mode. If a dependent module has a `Skip/skip.yml` file and is **not** in native mode, it will be transpiled into Kotlin. You can also use native Swift dependencies from the Swift side of your app only (and seek alternative implementations through the use of `#if os(Android)` blocks or custom Kotlin files; for an example, see the [LottieMotionView](https://source.skip.dev/skip-motion/blob/main/Sources/SkipMotion/SkipMotion.swift) implementation). Skip’s constellation of [frameworks](https://skip.dev/docs/modules/) are expected to provide most functionality that is commonly needed by modern apps, but you are free to develop your own libraries and frameworks, either from scratch, or by forking an existing repository to add Skip support for the library. ### How do I add dependencies to my Skip app? [Section titled “How do I add dependencies to my Skip app?”](https://skip.dev/docs/faq/#adddeps) Swift dependencies are added by editing the `Package.swift` to add dependent packages to the `dependencies` array. Kotlin and Java dependencies are added by editing the `Skip/skip.yml` file for the module. See the [Dependencies documentation](https://skip.dev/docs/dependencies) for details. ### How does logging work in Skip? [Section titled “How does logging work in Skip?”](https://skip.dev/docs/faq/#logging) Skip supports the `OSLog` module, so an app can log using a `Logger` instance, which will output log messages to the Xcode console for the iOS side of the app. For the Android side, log messages are passed to `android.util.Log` and can be viewed from the Logcat tab in Android Studio, or by running `adb logcat` from the terminal. Note that the `print()` function does not send any output to Logcat, so `OSLog.Logger` should be the preferred method of logging. ### How do resources work in Skip? [Section titled “How do resources work in Skip?”](https://skip.dev/docs/faq/#resources) The `SkipFoundation` framework implements the `Bundle.module` function for accessing resources stored in the standard SwiftPM location of `Sources/ModuleName/Resources/`. These resources are automatically bundled in the Gradle project, and will be embedded in the resulting `.apk` artifact. Note that unlike Foundation on Darwin platforms, the app archive is not expanded on disk for Android apps, but rather can only be accessed using a streaming interface. SkipFoundation handles this transparently for you, so you can load a resource with `try Data(contentsOfURL Bundle.module.url(forResource: "SomeResource", withExtension: "ext"))`. However, you cannot perform random access on a resources URL like you can on iOS. This means that embedded resources, such as a `.sqlite` database, would need to first be manually extracted to the app’s storage folder before they can be accessed. ### How does localization work in Skip? [Section titled “How does localization work in Skip?”](https://skip.dev/docs/faq/#localization) Skip supports the standard [`Localizable.xcstrings` ↗](https://developer.apple.com/documentation/Xcode/localizing-and-varying-text-with-a-string-catalog) translation source file. These files are automatically updated by Xcode 15 to contain any localization-aware string in your app. This allows you to have a single translation file that is used by both your iOS and Android app, so that your SwiftUI `Text("Hello!")` can be rendered as “Bonjour!” in French. ### How can I use a SQLite database in my Skip App? [Section titled “How can I use a SQLite database in my Skip App?”](https://skip.dev/docs/faq/#sqlite) We have a [SkipSQL framework](https://skip.dev/docs/modules/skip-sql/) which provides low-level access to the system-installed SQLite3 library on both iOS and Android. ### How can I use a Firebase database in my Skip App? [Section titled “How can I use a Firebase database in my Skip App?”](https://skip.dev/docs/faq/#firebase) Skip has a [SkipFirebase framework](https://skip.dev/docs/modules/skip-firebase/) module that provides integration with the official Firebase iOS and Android SDKs. Currently the Firestore integration is the most complete. ### Can I display Lottie animations in my app? [Section titled “Can I display Lottie animations in my app?”](https://skip.dev/docs/faq/#lottie) We have a [SkipMotion framework](https://skip.dev/docs/modules/skip-motion/) framework and a [Lottie Demo app](https://skip.dev/docs/samples/skipapp-lottiedemo/) available. ### How can I change the app name as displayed on the home screen? [Section titled “How can I change the app name as displayed on the home screen?”](https://skip.dev/docs/faq/#appname) For the iOS side, you can edit the `Darwin/AppName.xcconfig` and set: INFOPLIST_KEY_CFBundleDisplayName = App Name And for the Android side, you can edit `Android/app/src/main/AndroidManifest.xml` and set the `android:label` attribute: any View { view.composeModifier { $0.imePadding() } }}#endif The `ContentModfiier` protocol consists of a single function: public protocol ContentModifier { func modify(view: any View) -> any View} ### Skip Lite [Section titled “Skip Lite”](https://skip.dev/docs/modules/skip-ui/#skip-lite-1) If you are writing your SwiftUI using Skip Lite, you don’t need to define a `ContentModfifier`. You can apply [Material](https://skip.dev/docs/modules/skip-ui/#material) modifiers or `.composeModifier` directly: #if SKIPimport androidx.compose.foundation.layout.imePadding#endif ... TextField("Enter username:", text: $username) #if SKIP .composeModifier { $0.imePadding() } #endif You can also apply [scoped modifiers ↗](https://developer.android.com/develop/ui/compose/modifiers#scope-safety) . e.g. in a `LazyHStack` you can use modifiers scoped to [`LazyItemScope` ↗](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyItemScope) , like `animateItem()`. #if SKIPimport androidx.compose.foundation.lazy.LazyItemScope#endif ... LazyHStack { ForEach(0.. ColorScheme)?, content: @Composable () -> Unit) The `scheme` argument takes a closure with two arguments: Skip’s default `androidx.compose.material3.ColorScheme`, and whether dark mode is being requested. Your closure returns the `androidx.compose.material3.ColorScheme` to use for the supplied content. For example, to customize the surface colors for your entire app, you could edit `Main.kt` as follows: @Composableinternal fun PresentationRootView(context: ComposeContext) { Material3ColorScheme({ colors, isDark -> colors.copy(surface = if (isDark) Color.purple.asComposeColor() else Color.yellow.asComposeColor()) }, content = { // ... Original content of this function ... })} Skip also provides the SwiftUI `.material3ColorScheme(_:)` modifier to customize a SwiftUI view hierarchy. The modifier takes the same closure as the `Material3ColorScheme` Kotlin function. Apply this modifier using the `.composeModifier` techniques discussed in the previous section. For example: Skip Fuse: MyView() #if os(Android) .composeModifier { ColorSchemeModifier() } #endif ... #if SKIPstruct ColorSchemeModifier : ContentModifier { func modify(view: any View) -> any View { view.material3ColorScheme { colors, isDark in colors.copy(surface: isDark ? Color.purple.asComposeColor() : Color.yellow.asComposeColor()) } }}#endif Skip Lite: MyView() #if SKIP .material3ColorScheme { colors, isDark in colors.copy(surface: isDark ? Color.purple.asComposeColor() : Color.yellow.asComposeColor()) } #endif Skip’s built-in components use the following Material 3 colors, if you’d like to customize them: * `surface` * `primary` * `onBackground` * `outline` * `outlineVariant` ### Material Components [Section titled “Material Components”](https://skip.dev/docs/modules/skip-ui/#material-components) In addition to the `.material3ColorScheme` modifier detailed above, Skip includes many other `.material3` modifiers for its underlying Material 3 components. This family of modifiers share a common API pattern: * The modifiers take a closure argument. This closure receives a `Material3Options` struct configured with Skip’s defaults, and it returns a struct with any desired modifications. * Every `Material3Options` struct implements a conventional Kotlin `copy` method. This allows you to copy and modify the struct in a single call. * The modifiers place your closure into the SwiftUI `Environment`. This means that you can apply the modifier on a root view, and it will affect all subviews. While you may be used to placing navigation and tab bar modifiers on the views _within_ the `NavigationStack` or `TabView`, the `.material3` family of modifiers always go _on or outside_ the views you want to affect. * Because they are designed to reach beneath Skip’s SwiftUI covers, the modifiers use Compose terminology and types. In fact the properties of the supplied `Material3Options` structs typically exactly match the corresponding `androidx.compose.material3` component function parameters. Here is an example of changing the selected indicator color on your Android tab bar, which is implemented by the Material 3 `NavigationBar` component: Skip Fuse: TabView { ...}#if os(Android).composeModifier { NavigationBarModifier() }#endif ... #if SKIPstruct NavigationBarModifier : ContentModifier { func modify(view: any View) -> any View { view.material3NavigationBar { options in let updatedColors = options.itemColors.copy(selectedIndicatorColor: Color.green.asComposeColor()) return options.copy(itemColors: updatedColors) } }}#endif Skip Lite: TabView { ...}#if SKIP.material3NavigationBar { options in let updatedColors = options.itemColors.copy(selectedIndicatorColor: Color.green.asComposeColor()) return options.copy(itemColors: updatedColors)}#endif SkipUI currently includes the following Material modifiers: extension View { public func material3BottomAppBar(_ options: @Composable (Material3BottomAppBarOptions) -> Material3BottomAppBarOptions) -> some View} public struct Material3BottomAppBarOptions { public var modifier: androidx.compose.ui.Modifier public var containerColor: androidx.compose.ui.graphics.Color public var contentColor: androidx.compose.ui.graphics.Color public var tonalElevation: androidx.compose.ui.unit.Dp public var contentPadding: androidx.compose.foundation.layout.PaddingValues} extension View { public func material3Button(_ options: @Composable (Material3ButtonOptions) -> Material3ButtonOptions) -> some View} public struct Material3ButtonOptions { public var onClick: () -> Void public var modifier: androidx.compose.ui.Modifier public var enabled: Bool public var shape: androidx.compose.ui.graphics.Shape public var colors: androidx.compose.material3.ButtonColors public var elevation: androidx.compose.material3.ButtonElevation? public var border: androidx.compose.foundation.BorderStroke? public var contentPadding: androidx.compose.foundation.layout.PaddingValues public var interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource?} extension View { public func material3NavigationBar(_ options: @Composable (Material3NavigationBarOptions) -> Material3NavigationBarOptions) -> some View} public struct Material3NavigationBarOptions { public var modifier: androidx.compose.ui.Modifier public var containerColor: androidx.compose.ui.graphics.Color public var contentColor: androidx.compose.ui.graphics.Color public var tonalElevation: androidx.compose.ui.unit.Dp public var onItemClick: (Int) -> Void public var itemIcon: @Composable (Int) -> Void public var itemModifier: @Composable (Int) -> androidx.compose.ui.Modifier public var itemEnabled: (Int) -> Boolean public var itemLabel: (@Composable (Int) -> Void)? public var alwaysShowItemLabels: Bool public var itemColors: androidx.compose.material3.NavigationBarItemColors public var itemInteractionSource: androidx.compose.foundation.interaction.MutableInteractionSource?} extension View { public func material3SegmentedButton(_ options: @Composable (Material3SegmentedButtonOptions) -> Material3SegmentedButtonOptions) -> some View} public struct Material3SegmentedButtonOptions { public let index: Int public let count: Int public var selected: Boolean public var onClick: () -> Void public var modifier: androidx.compose.ui.Modifier public var enabled: Bool public var shape: androidx.compose.ui.graphics.Shape public var colors: androidx.compose.material3.SegmentedButtonColors public var border: androidx.compose.foundation.BorderStroke? public var contentPadding: androidx.compose.foundation.layout.PaddingValues public var interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource? public var icon: @Composable () -> Void} extension View { public func material3Text(_ options: @Composable (Material3TextOptions) -> Material3TextOptions) -> some View} public struct Material3TextOptions { public var text: String? public var annotatedText: AnnotatedString? public var modifier: androidx.compose.ui.Modifier public var color: androidx.compose.ui.graphics.Color public var fontSize: androidx.compose.ui.unit.TextUnit public var fontStyle: androidx.compose.ui.text.font.FontStyle? public var fontWeight: androidx.compose.ui.text.font.FontWeight? public var fontFamily: androidx.compose.ui.text.font.FontFamily? public var letterSpacing: androidx.compose.ui.unit.TextUnit public var textDecoration: androidx.compose.ui.text.style.TextDecoration? public var textAlign: androidx.compose.ui.text.style.TextAlign? public var lineHeight: androidx.compose.ui.unit.TextUnit public var overflow: androidx.compose.ui.text.style.TextOverflow public var softWrap: Bool public var maxLines: Int public var minLines: Int public var onTextLayout: ((androidx.compose.ui.text.TextLayoutResult) -> Void)? public var style: androidx.compose.ui.text.style.TextStyle} extension View { public func material3TextField(_ options: @Composable (Material3TextFieldOptions) -> Material3TextFieldOptions) -> some View} public struct Material3TextFieldOptions { public var value: androidx.compose.ui.text.input.TextFieldValue public var onValueChange: (androidx.compose.ui.text.input.TextFieldValue) -> Void public var modifier: androidx.compose.ui.Modifier public var enabled: Bool public var readOnly: Bool public var textStyle: androidx.compose.ui.text.TextStyle public var label: (@Composable () -> Void)? public var placeholder: (@Composable () -> Void)? public var leadingIcon: (@Composable () -> Void)? public var trailingIcon: (@Composable () -> Void)? public var prefix: (@Composable () -> Void)? public var suffix: (@Composable () -> Void)? public var supportingText: (@Composable () -> Void)? public var isError: Bool public var visualTransformation: androidx.compose.ui.text.input.VisualTransformation public var keyboardOptions: androidx.compose.foundation.text.KeyboardOptions public var keyboardActions: androidx.compose.foundation.text.KeyboardActions public var singleLine: Bool public var maxLines: Int public var minLines: Int public var interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource? public var shape: androidx.compose.ui.graphics.Shape public var colors: androidx.compose.material3.TextFieldColors} extension View { public func material3TopAppBar(_ options: @Composable (Material3TopAppBarOptions) -> Material3TopAppBarOptions) -> some View} public struct Material3TopAppBarOptions { public var title: @Composable () -> Void public var modifier: androidx.compose.ui.Modifier public var navigationIcon: @Composable () -> Void public var colors: androidx.compose.material3.TopAppBarColors public var scrollBehavior: androidx.compose.material3.TopAppBarScrollBehavior? public var preferCenterAlignedStyle: Bool public var preferLargeStyle: Bool} Note that `.material3TopAppBar` involves API that Compose deems experimental, so you must add the following to any Skip Fuse `ContentModfifier` or Skip Lite `View` where you use it: // SKIP INSERT: @OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)struct MyContentModifier : ContentModifier { ...} ### Material Effects [Section titled “Material Effects”](https://skip.dev/docs/modules/skip-ui/#material-effects) Compose applies an automatic “ripple” effect to components on tap. You can customize the color and alpha of this effect with the `material3Ripple` modifier. To disable the effect altogether, return `nil` from your modifier closure. extension View { public func material3Ripple(_ options: @Composable (Material3RippleOptions?) -> Material3RippleOptions?) -> some View} public struct Material3RippleOptions { public var color: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.Unspecified public var rippleAlpha: androidx.compose.material.ripple.RippleAlpha? = nil} Supported SwiftUI ----------------- [Section titled “Supported SwiftUI”](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) The following table summarizes SkipUI’s SwiftUI support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within `#if !os(Android)` blocks - you can use any SwiftUI you want. Support levels: * ✅ – Full * 🟢 – High * 🟡 – Medium * 🟠 – Low | Support | API | | --- | --- | | 🟢 | `@AppStorage` ([example](https://skip.dev/docs/components/storage/)
)

* Optional values are not supported | | ✅ | `@Bindable` | | ✅ | `@Binding` ([example](https://skip.dev/docs/components/state/)
) | | ✅ | `@Environment` ([example](https://skip.dev/docs/components/modifier/)
)

* See [Environment Keys](https://skip.dev/docs/modules/skip-ui/#environment-keys) | | ✅ | `@EnvironmentObject` ([example](https://skip.dev/docs/components/observable/)
) | | 🟢 | `@FocusState`

* `FocusState.Binding` is not supported, though you can manually create a `Binding(get: { myFocusState.wrappedValue }, set: { myFocusState.wrappedValue = $0 })` | | 🟡 | `@GestureState`

* Only supported in SkipFuseUI compiled Swift | | ✅ | `@ObservedObject` ([example](https://skip.dev/docs/components/observable/)
) | | ✅ | `@State` ([example](https://skip.dev/docs/components/state/)
) | | ✅ | `@StateObject` ([example](https://skip.dev/docs/components/state/)
) | | ✅ | `AsyncImage` ([example](https://skip.dev/docs/components/image/)
) | | ✅ | `Button` ([example](https://skip.dev/docs/components/button/)
) | | ✅ | `Capsule` ([example](https://skip.dev/docs/components/shape/)
) | | ✅ | `Circle` ([example](https://skip.dev/docs/components/shape/)
) | | 🟢 | `Color` ([example](https://skip.dev/docs/components/color/)
)

* `init(red: Double, green: Double, blue: Double, opacity: Double = 1.0)`
* `init(white: Double, opacity: Double = 1.0)`
* `init(hue: Double, saturation: Double, brightness: Double, opacity: Double = 1.0)`
* `init(_ color: UIColor)`
* `init(uiColor: UIColor)`
* `init(_ name: String, bundle: Bundle? = nil)`
* `static let accentColor: Color`
* `static let primary: Color`
* `static let secondary: Color`
* `static let clear: Color`
* `static let white: Color`
* `static let black: Color`
* `static let gray: Color`
* `static let red: Color`
* `static let orange: Color`
* `static let yellow: Color`
* `static let green: Color`
* `static let mint: Color`
* `static let teal: Color`
* `static let cyan: Color`
* `static let blue: Color`
* `static let indigo: Color`
* `static let purple: Color`
* `static let pink: Color`
* `static let brown: Color`
* `func opacity(_ opacity: Double) -> Color`
* `var gradient: AnyGradient`
* See [Colors](https://skip.dev/docs/modules/skip-ui/#colors) | | 🟡 | `DatePicker` ([example](https://skip.dev/docs/components/datepicker/)
)

* `init(selection: Binding, displayedComponents: DatePickerComponents = [.hourAndMinute, .date], @ViewBuilder label: () -> any View)`
* `init(_ title: String, selection: Binding, displayedComponents: DatePickerComponents = [.hourAndMinute, .date])`
* Date range constraints (`in: ClosedRange`) are supported via Skip Fuse bridging | | 🟡 | `DisclosureGroup` ([example](https://skip.dev/docs/components/disclosuregroup/)
)

* `init(isExpanded: Binding, @ViewBuilder content: @escaping () -> any View, @ViewBuilder label: () -> any View)`
* `init(_ titleKey: LocalizedStringKey, isExpanded: Binding, @ViewBuilder content: @escaping () -> any View)`
* `init(_ titleResource: LocalizedStringResource, isExpanded: Binding, @ViewBuilder content: @escaping () -> any View)`
* `init(_ label: String, isExpanded: Binding, @ViewBuilder content: @escaping () -> any View)`
* Does not animate when used as a `List` or `Form` item
* Always animates when \*\*not\*\* used as a `List` or `Form` item | | ✅ | `Divider` ([example](https://skip.dev/docs/components/divider/)
) | | 🟢 | `DragGesture` ([example](https://skip.dev/docs/components/gesture/)
)

* See [Gestures](https://skip.dev/docs/modules/skip-ui/#gestures) | | 🟡 | `EllipticalGradient` ([example](https://skip.dev/docs/components/gradient/)
)

* Fills as a circular gradient instead of elliptical unless the gradient is used as its own `View` | | ✅ | `EmptyModifier` | | ✅ | `EmptyView` | | 🟡 | `Font`

* `static let largeTitle: Font`
* `static let title: Font`
* `static let title2: Font`
* `static let title3: Font`
* `static let headline: Font`
* `static let subheadline: Font`
* `static let body: Font`
* `static let callout: Font`
* `static let footnote: Font`
* `static let caption: Font`
* `static let caption2: Font`
* `static func system(_ style: Font.TextStyle, design: Font.Design? = nil, weight: Font.Weight? = nil) -> Font`
* `static func system(size: CGFloat, weight: Font.Weight? = nil, design: Font.Design? = nil) -> Font`
* `static func custom(_ name: String, size: CGFloat) -> Font`
* `static func custom(_ name: String, size: CGFloat, relativeTo textStyle: Font.TextStyle) -> Font`
* `static func custom(_ name: String, fixedSize: CGFloat) -> Font`
* `func italic(_ isActive: Bool = true) -> Font`
* `func weight(_ weight: Font.Weight) -> Font`
* `func bold(_ isActive: Bool = true) -> Font`
* `func monospaced(_ isActive: Bool = true) -> Font`
* `func pointSize(_ size: CGFloat) -> Font`
* `func scaledBy(_ factor: CGFloat) -> Font` | | 🟢 | `ForEach`

* See [ForEach](https://skip.dev/docs/modules/skip-ui/#foreach) | | ✅ | `Form` ([example](https://skip.dev/docs/components/form/)
) | | 🟡 | `GeometryProxy`

* `var size: CGSize`
* `func frame(in coordinateSpace: some CoordinateSpaceProtocol) -> CGRect`
* `var safeAreaInsets: EdgeInsets`
* Only `.local` and `.global` coordinate spaces are supported | | 🟡 | `GeometryReader`

* See `GeometryProxy` | | ✅ | `Group` | | ✅ | `HStack` ([example](https://skip.dev/docs/components/stack/)
) | | 🟢 | `Image` ([example](https://skip.dev/docs/components/image/)
)

* `init(_ name: String, bundle: Bundle? = Bundle.main)`
* `init(_ name: String, bundle: Bundle? = Bundle.main, label: Text)`
* `init(systemName: String)`
* `init(uiImage: UIImage)`
* See [Images](https://skip.dev/docs/modules/skip-ui/#images) | | 🟢 | `Label` ([example](https://skip.dev/docs/components/label/)
)

* `init(@ViewBuilder title: () -> any View, @ViewBuilder icon: () -> any View)`
* `init(_ title: String, systemImage: String)`
* `init(_ title: String, image: String)`
* See [Images](https://skip.dev/docs/modules/skip-ui/#images) | | 🟡 | `LazyHGrid`

* See [Grids](https://skip.dev/docs/modules/skip-ui/#grids) | | 🟡 | `LazyHStack`

* Does not support pinned headers and footers
* When placed in a `ScrollView`, it must be the only child of that view | | 🟡 | `LazyVGrid`

* See [Grids](https://skip.dev/docs/modules/skip-ui/#grids) | | 🟡 | `LazyVStack`

* Does not support pinned headers and footers
* When placed in a `ScrollView`, it must be the only child of that view | | ✅ | `LinearGradient` ([example](https://skip.dev/docs/components/gradient/)
) | | ✅ | `Link` ([example](https://skip.dev/docs/components/link/)
) | | 🟢 | `List` ([example](https://skip.dev/docs/components/list/)
)

* See [Lists](https://skip.dev/docs/modules/skip-ui/#lists) | | 🟢 | `LongPressGesture` ([example](https://skip.dev/docs/components/gesture/)
)

* See [Gestures](https://skip.dev/docs/modules/skip-ui/#gestures) | | 🟢 | `MagnifyGesture` ([example](https://skip.dev/docs/components/gesture/)
)

* See [Gestures](https://skip.dev/docs/modules/skip-ui/#gestures) | | ✅ | `Menu` ([example](https://skip.dev/docs/components/menu/)
) | | 🟢 | `NavigationLink` ([example](https://skip.dev/docs/components/list/)
)

* See [Navigation](https://skip.dev/docs/modules/skip-ui/#navigation) | | 🟢 | `NavigationPath`

* `init()`
* `init(_ elements: any Sequence)`
* `var count: Int`
* `var isEmpty: Bool`
* `mutating func append(_ value: Any)`
* `mutating func removeLast(_ k: Int = 1)`
* Does not support \`codable\` property | | 🟢 | `NavigationStack` ([example](https://skip.dev/docs/components/navigationstack/)
)

* See [Navigation](https://skip.dev/docs/modules/skip-ui/#navigation) | | ✅ | `Oval` ([example](https://skip.dev/docs/components/shape/)
) | | ✅ | `Picker` ([example](https://skip.dev/docs/components/picker/)
) | | 🟢 | `ProgressView` ([example](https://skip.dev/docs/components/progressview/)
)

* `init()`
* `init(value: Double?, total: Double = 1.0)`
* `init(@ViewBuilder label: () -> any View)`
* `init(_ titleKey: LocalizedStringKey)`
* `init(_ titleResource: LocalizedStringResource)`
* `init(_ title: String)`
* `init(value: Double?, total: Double = 1.0, @ViewBuilder label: () -> any View)`
* `init(_ titleKey: LocalizedStringKey, value: Double?, total: Double = 1.0)`
* `init(_ titleResource: LocalizedStringResource, value: Double?, total: Double = 1.0)`
* `init(_ title: String, value: Double?, total: Double = 1.0)` | | ✅ | `RadialGradient` ([example](https://skip.dev/docs/components/gradient/)
) | | ✅ | `Rectangle` ([example](https://skip.dev/docs/components/shape/)
) | | 🟢 | `RotateGesture` ([example](https://skip.dev/docs/components/gesture/)
)

* See [Gestures](https://skip.dev/docs/modules/skip-ui/#gestures) | | ✅ | `RoundedRectangle` ([example](https://skip.dev/docs/components/shape/)
) | | ✅ | `ScrollView` ([example](https://skip.dev/docs/components/frame/)
) | | ✅ | `ScrollView`

* See [Scrolling](https://skip.dev/docs/modules/skip-ui/#scrolling) | | 🟡 | `ScrollViewProxy`

* See [Scrolling](https://skip.dev/docs/modules/skip-ui/#scrolling) | | 🟡 | `ScrollViewReader`

* See [Scrolling](https://skip.dev/docs/modules/skip-ui/#scrolling) | | 🟢 | `Section` ([example](https://skip.dev/docs/components/list/)
)

* See [Lists](https://skip.dev/docs/modules/skip-ui/#lists) | | ✅ | `SecureField` ([example](https://skip.dev/docs/components/securefield/)
) | | 🟠 | `ShareLink` ([example](https://skip.dev/docs/components/sharelink/)
)

* Supports sharing `String` or `URL` data only | | 🟡 | `Slider` ([example](https://skip.dev/docs/components/slider/)
)

* `init(value: Binding, in bounds: ClosedRange = 0.0...1.0, step: Double? = nil)`
* `init(value: Binding, in bounds: ClosedRange = 0.0...1.0, step: Double? = nil, @ViewBuilder label: () -> any View)`
* `init(value: Binding, in bounds: ClosedRange = 0.0...1.0, step: Double? = nil, onEditingChanged: (Bool) -> Void)`
* `init(value: Binding, in bounds: ClosedRange = 0.0...1.0, step: Double? = nil, @ViewBuilder label: () -> any View, onEditingChanged: (Bool) -> Void)` | | 🟢 | `Spacer` ([example](https://skip.dev/docs/components/border/)
)

* In Compose, when multiple elements want to expand they will share the available space equally | | 🟡 | `Stepper`

* `init(value: Binding, step: Int = 1, @ViewBuilder label: () -> any View)`
* `init(value: Binding, in bounds: ClosedRange, step: Int = 1, @ViewBuilder label: () -> any View)` (Fuse bridging)
* `init(value: Binding, step: Double = 1.0, @ViewBuilder label: () -> any View)`
* `init(value: Binding, in bounds: ClosedRange, step: Double = 1.0, @ViewBuilder label: () -> any View)` (Fuse bridging)
* `init(_ title: String, value: Binding, step: Int = 1)`
* `init(_ title: String, value: Binding, in bounds: ClosedRange, step: Int = 1)` (Fuse bridging)
* `init(_ title: String, value: Binding, step: Double = 1.0)`
* `init(_ title: String, value: Binding, in bounds: ClosedRange, step: Double = 1.0)` (Fuse bridging)
* `init(_ title: String, onIncrement: (() -> Void)?, onDecrement: (() -> Void)?)`
* `init(@ViewBuilder label: () -> any View, onIncrement: (() -> Void)?, onDecrement: (() -> Void)?)` | | 🟢 | `Tab` ([example](https://skip.dev/docs/components/tabview/)
)

* See [Navigation](https://skip.dev/docs/modules/skip-ui/#navigation) | | 🟠 | `Table`

* `init(_ data: any RandomAccessCollection, @ViewBuilder content: () -> some View)`
* `init(_ data: any RandomAccessCollection, selection: Binding, @ViewBuilder columns: () -> some View)`
* `init(_ data: any RandomAccessCollection, selection: Binding>, @ViewBuilder columns: () -> some View)`
* All `TableColumns` must be directly nested in the parent `Table` content block
* Multiple selection is not supported | | 🟠 | `TableColumn`

* `init(_ title: String, value: (ObjectType) -> String, comparator: Comparator? = nil)`
* `init(_ title: String, value: Value? = nil, comparator: Comparator? = nil, @ViewBuilder content: @escaping (ObjectType) -> some View)`
* `init(_ title: Text, value: (ObjectType) -> String, comparator: Comparator? = nil)`
* `init(_ title: Text, value: Value? = nil, comparator: Comparator? = nil, @ViewBuilder content: @escaping (ObjectType) -> some View)`
* `func width(_ value: CGFloat? = nil)`
* `func width(min: CGFloat? = nil, ideal: CGFloat? = nil, max: CGFloat? = nil)` | | 🟠 | `TabSection`

* See [Navigation](https://skip.dev/docs/modules/skip-ui/#navigation) | | 🟢 | `TabView` ([example](https://skip.dev/docs/components/tabview/)
)

* See [Navigation](https://skip.dev/docs/modules/skip-ui/#navigation) | | 🟢 | `TapGesture` ([example](https://skip.dev/docs/components/gesture/)
)

* See [Gestures](https://skip.dev/docs/modules/skip-ui/#gestures) | | 🟢 | `Text` ([example](https://skip.dev/docs/components/text/)
)

* `Text(...) + Text(...)` is not supported
* For formatters, only `Text.DateStyle.date` and `Text.DateStyle.time` are supported | | 🟢 | `TextEditor`

* `.font`, `.lineSpacing`, etc modifiers have no effect | | 🟢 | `TextField` ([example](https://skip.dev/docs/components/textfield/)
)

* `init(_ title: String, text: Binding, selection: Binding? = nil, prompt: Text? = nil)` | | 🟡 | `Toggle` ([example](https://skip.dev/docs/components/toggle/)
)

* `init(isOn: Binding, @ViewBuilder label: () -> any View)` | | 🟢 | `ToolbarContent`

* All of the items in a given custom `ToolbarContent` view must have the same `ToolbarItemPlacement` | | ✅ | `ToolbarItem` ([example](https://skip.dev/docs/components/toolbar/)
) | | ✅ | `ToolbarItemGroup` ([example](https://skip.dev/docs/components/toolbar/)
) | | ✅ | `ToolbarSpacer` | | ✅ | `ToolbarTitleMenu` | | 🟠 | `UIKit`

* See [Supported UIKit](https://skip.dev/docs/modules/skip-ui/#supported-uikit) | | ✅ | `UnevenRoundedRectangle` ([example](https://skip.dev/docs/components/shape/)
) | | ✅ | Custom `Views` | | ✅ | Custom `ViewModifiers` | | ✅ | `ViewThatFits` | | ✅ | `VStack` ([example](https://skip.dev/docs/components/stack/)
)

* See [Layout](https://skip.dev/docs/modules/skip-ui/#layout) | | 🟡 | `withAnimation`

* See [Animation](https://skip.dev/docs/modules/skip-ui/#animation) | | ✅ | `ZStack` ([example](https://skip.dev/docs/components/stack/)
) | | 🟢 | `.accessibilityAddTraits`

* Only traits that map to Compose accessibility roles are used | | ✅ | `.accessibilityHeading` | | ✅ | `.accessibilityHidden` | | ✅ | `.accessibilityIdentifier` | | ✅ | `.accessibilityLabel` | | ✅ | `.accessibilityValue` | | ✅ | `.alert` | | ✅ | `.allowsHitTesting` | | 🟡 | `.animation`

* See [Animation](https://skip.dev/docs/modules/skip-ui/#animation) | | 🟡 | `.aspectRatio`

* `contentMode` is only supported for images | | ✅ | `.autocorrectionDisabled` | | 🟢 | `.background` ([example](https://skip.dev/docs/components/background/)
)

* See [Safe Area](https://skip.dev/docs/modules/skip-ui/#safe-area) | | ✅ | `.backgroundStyle` | | 🟡 | `.badge`

* Supported on `List` items
* Not yet supported on `TabView` | | 🟢 | `.blendMode`

* `.plusDarker` and `.plusLighter` both map to Compose `Plus` | | 🟢 | `.blur`

* `opaque` parameter is ignored | | ✅ | `.bold` | | ✅ | `.border` ([example](https://skip.dev/docs/components/border/)
) | | ✅ | `.brightness` | | 🟢 | `.buttonStyle`

* `.automatic`
* `.plain`
* `.borderless`
* `.bordered`
* `.borderedProminent`
* Custom styles are not supported | | 🟡 | `.clipped`

* Most content in Compose clips automatically | | ✅ | `.clipShape` | | ✅ | `.colorScheme`

* See also [ColorScheme](https://skip.dev/docs/modules/skip-ui/#colorscheme) | | ✅ | `.colorMultiply` | | ✅ | `.confirmationDialog` ([example](https://skip.dev/docs/components/confirmationdialog/)
) | | ✅ | `.contrast` | | ✅ | `.cornerRadius` | | 🟠 | `.datePickerStyle`

* `.automatic`
* `.compact`
* Custom styles are not supported | | ✅ | `.deleteDisabled` | | ✅ | `.disabled` | | 🟢 | `.drawingGroup`

* `opaque` and `colorMode` parameters are ignored | | ✅ | `.environment` | | ✅ | `.environmentObject` | | ✅ | `.fill` | | ✅ | `.focused` | | ✅ | `.flipsForRightToLeftLayoutDirection` | | ✅ | `.font` ([example](https://skip.dev/docs/components/text/)
) | | ✅ | `.foregroundColor` | | ✅ | `.foregroundStyle` | | 🟢 | `.frame` ([example](https://skip.dev/docs/components/frame/)
)

* See [Layout](https://skip.dev/docs/modules/skip-ui/#layout) | | 🟢 | `.fullScreenCover`

* `func fullScreenCover(isPresented: Binding, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> any View) -> some View`
* See [Modals](https://skip.dev/docs/modules/skip-ui/#modals) | | 🟢 | `.gesture` ([example](https://skip.dev/docs/components/gesture/)
)

* See [Gestures](https://skip.dev/docs/modules/skip-ui/#gestures) | | ✅ | `.gradient` ([example](https://skip.dev/docs/components/gradient/)
) | | ✅ | `.grayscale` | | ✅ | `.hidden` | | ✅ | `.hueRotation` | | 🟢 | `.ignoresSafeArea`

* See [Safe Area](https://skip.dev/docs/modules/skip-ui/#safe-area) | | ✅ | `.inset` | | ✅ | `.interactiveDismissDisabled`

* See [Modals](https://skip.dev/docs/modules/skip-ui/#modals) | | ✅ | `.italic` | | ✅ | `.keyboardType` ([example](https://skip.dev/docs/components/keyboard/)
) | | ✅ | `.labelsHidden` | | ✅ | `.labelStyle`

* `.automatic`
* `.titleOnly`
* `.iconOnly`
* `.titleAndIcon`
* Custom styles are not supported | | 🟢 | `.lineLimit`

* func lineLimit(\_ number: Int?) -> some View
* func lineLimit(\_ number: Int, reservesSpace: Bool) -> some View | | ✅ | `.linespacing` | | ✅ | `.listItemTint` | | ✅ | `.listRowBackground` | | ✅ | `.listRowSeparator` | | ✅ | `.listStyle` | | ✅ | `.luminanceToAlpha` | | ✅ | `.mask` | | ✅ | `.minimumScaleFactor` | | ✅ | `.modifier` ([example](https://skip.dev/docs/components/modifier/)
) | | ✅ | `.monospaced` | | ✅ | `.moveDisabled` | | ✅ | `.multilineTextAlignment` | | ✅ | `.navigationBarBackButtonHidden` | | ✅ | `.navigationBarTitleDisplayMode` | | ✅ | `.navigationDestination` | | 🟢 | `.navigationTitle`

* `func navigationTitle(_ title: String) -> some View`
* `func navigationTitle(_ title: Text) -> some View` | | ✅ | `.offset` ([example](https://skip.dev/docs/components/offset/)
) | | ✅ | `.onAppear` | | ✅ | `.onChange` | | ✅ | `.onDelete` | | ✅ | `.onDisappear` | | 🟢 | `.onLongPressGesture` ([example](https://skip.dev/docs/components/gesture/)
)

* See [Gestures](https://skip.dev/docs/modules/skip-ui/#gestures) | | ✅ | `.onMove` | | ✅ | `.onOpenURL` | | ✅ | `.onPreferenceChange` | | ✅ | `.onReceive` | | ✅ | `.onSubmit` | | 🟢 | `.onTapGesture`

* See [Gestures](https://skip.dev/docs/modules/skip-ui/#gestures) | | ✅ | `.opacity` | | ✅ | `.overlay` ([example](https://skip.dev/docs/components/overlay/)
) | | ✅ | `.padding` | | 🟡 | `.pickerStyle`

* `.automatic`
* `.navigationLink`
* `.menu`
* `.segmented`
* Custom styles are not supported | | ✅ | `.position` | | ✅ | `.preference` | | ✅ | `.preferredColorScheme`

* See also [ColorScheme](https://skip.dev/docs/modules/skip-ui/#colorscheme) | | 🟢 | `.progressViewStyle`

* `.automatic`
* `.linear`
* `.circular`
* Custom styles are not supported | | 🟡 | `.redacted`

* Only `RedactionReasons.placeholder` is supported | | ✅ | `.refreshable` | | 🟠 | `.resizable`

* `func resizable() -> Image` | | 🟢 | `.rotation`

* `func rotation(_ angle: Angle) -> any Shape` | | ✅ | `.rotationEffect`

* `func rotationEffect(_ angle: Angle) -> some View`
* `func rotationEffect(_ angle: Angle, anchor: UnitPoint) -> some View` | | 🟢 | `.rotation3DEffect`

* `func rotation3DEffect(_ angle: Angle, axis: (x: CGFloat, y: CGFloat, z: CGFloat), perspective: CGFloat = 1.0) -> some View` | | ✅ | `.saturation` | | 🟢 | `.scale`

* `func scale(_ scale: CGFloat) -> any Shape`
* `func scale(x: CGFloat = 1.0, y: CGFloat = 1.0) -> any Shape` | | 🟡 | `.scaledToFill`

* Only supported for images | | 🟡 | `.scaledToFit`

* Only supported for images | | ✅ | `.scaleEffect`

* `func scaleEffect(_ scale: CGSize, anchor: UnitPoint = .center) -> some View`
* `func scaleEffect(_ s: CGFloat, anchor: UnitPoint = .center) -> some View`
* `func scaleEffect(x: CGFloat = 1.0, y: CGFloat = 1.0, anchor: UnitPoint = .center) -> some View` | | ✅ | `.scrollContentBackground` | | 🟢 | `.scrollDismissesKeyboard`

* In Compose, the default behavior (`.automatic`) is to never dismiss on scroll
* `.interactively` behaves like `.immediately` | | 🟠 | `.scrollTargetBehavior`

* Only `.viewAligned` is supported
* See [Scrolling](https://skip.dev/docs/modules/skip-ui/#scrolling) | | 🟠 | `.scrollTargetLayout`

* See [Scrolling](https://skip.dev/docs/modules/skip-ui/#scrolling) | | 🟡 | `.searchable` ([example](https://skip.dev/docs/components/searchable/)
)

* `func searchable(text: Binding, prompt: Text? = nil) -> some View`
* `func searchable(text: Binding, prompt: String) -> some View` | | 🟢 | `.shadow` ([example](https://skip.dev/docs/components/shadow/)
)

* Place this modifier before `.background`, `.overlay` modifiers | | 🟢 | `.sheet` ([example](https://skip.dev/docs/components/sheet/)
)

* `func sheet(isPresented: Binding, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> any View) -> some View`
* See [Modals](https://skip.dev/docs/modules/skip-ui/#modals) | | 🟢 | `.strikethrough`

* `pattern` and `color` are ignored | | ✅ | `.stroke` | | ✅ | `.strokeBorder` | | ✅ | `.submitLabel` | | ✅ | `.symbolVariant` | | ✅ | `.tabItem`

* See [Navigation](https://skip.dev/docs/modules/skip-ui/#navigation) | | ✅ | `.tabViewStyle`

* `.automatic`
* `.page`
* `.tabBarOnly`
* Custom styles are not supported | | ✅ | `.tag` | | ✅ | `.task` | | ✅ | `.textCase` | | 🟢 | `.textContentType` | | ✅ | `.textEditorStyle` | | 🟡 | `.textFieldStyle`

* `.automatic`
* `.roundedBorder` | | ✅ | `.textInputAutocapitalization` | | ✅ | `.tint` | | 🟢 | `.toolbar`

* `func toolbar(@ViewBuilder content: () -> any View) -> some View`
* `func toolbar(_ visibility: Visibility, for bars: ToolbarPlacement...) -> some View` | | ✅ | `.toolbarBackground` | | ✅ | `.toolbarColorScheme` | | ✅ | `.toolbarTitleDisplayMode` | | ✅ | `.toolbarTitleMenu` | | ✅ | `.tracking` | | 🟢 | `.transition`

* See [Animation](https://skip.dev/docs/modules/skip-ui/#animation) | | 🟢 | `.underline`

* `pattern` and `color` are ignored | | ✅ | `.truncationMode` | | ✅ | `.zIndex` ([example](https://skip.dev/docs/components/zindex/)
) | Supported UIKit --------------- [Section titled “Supported UIKit”](https://skip.dev/docs/modules/skip-ui/#supported-uikit) SkipUI does not support UIKit views themselves, but it does support a subset of the UIKit framework, such as the pasteboard and haptic feedback classes, that act as interfaces to the underlying services on Android. The following table summarizes SkipUI’s UIKit support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within `#if !SKIP` blocks - you can use any UIKit you want. Support levels: * ✅ – Full * 🟢 – High * 🟡 – Medium * 🟠 – Low | Support | API | | --- | --- | | 🟠 | `UIApplication`

* `static var shared: UIApplication`
* `var applicationState: UIApplication.State`
* `var isIdleTimerDisabled: Bool`
* `func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]) async -> Bool` | | ✅ | `#colorLiteral()` | | 🟠 | `UIColor`

* `init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)` | | 🟠 | `UIImage`

* `init?(contentsOfFile: String)`
* `init?(data: Data)`
* `init?(data: Data, scale: CGFloat)` | | ✅ | `UIImpactFeedbackGenerator` | | ✅ | `UINotificationFeedbackGenerator` | | 🟠 | `UIPasteboard`

* `static var general: UIPasteboard`
* `static var changedNotification: Notification.Name`
* `var numberOfItems: Int`
* `var hasStrings: Bool`
* `var string: String?`
* `var strings: [String]?`
* `var hasURLs: Bool`
* `var url: URL?`
* `var urls: [URL]?` | | ✅ | `UISelectionFeedbackGenerator` | Supported UserNotifications --------------------------- [Section titled “Supported UserNotifications”](https://skip.dev/docs/modules/skip-ui/#supported-usernotifications) Skip integrates its support for the UserNotifications framework into SkipUI. **Important:** on Android devices to properly display notification icons in the status bar they must have specific properites. The icon must have a transparent background and a solid shape (usually white). It’s recommended to use a specific icon for that and it must be specified in the AndroidManifest.xml with the following code inside the application tag: SKIP assume that the icon is called `ic_notification` and it’s available inside the drawable resources. Otherwise it will fall back to use the app icon (eg. @mipmap/ic\_launcher). Note that in this case it will most likely be displayed as solid white dot instead of the usual app icon. The following table summarizes SkipUI’s UserNotifications support on Android. Anything not listed here is likely not supported. Note that in your iOS-only code - i.e. code within `#if !SKIP` blocks - you can use any UserNotifications API you want. Support levels: * ✅ – Full * 🟢 – High * 🟡 – Medium * 🟠 – Low | Support | API | | --- | --- | | 🟡 | `UNAuthorizationOptions`

* Ignored on Android | | 🟡 | `UNMutableNotificationContent`

* See \`UNNotificationContent\` | | ✅ | `UNNotification` | | 🟡 | `UNNotificationContent`

* Only \`title\`, \`body\`, \`userInfo\`, and \`public.image\`-type attachments are used | | 🟡 | `UNNotificationPresentationOptions`

* Only \`.banner\` and \`.alert\` are used | | ✅ | `UNNotificationRequest` | | ✅ | `UNNotificationResponse` | | 🟡 | `UNNotificationSound`

* Ignored on Android | | 🟡 | `UNNotificationTrigger`

* Ignored on Android | | 🟠 | `UNUserNotificationCenter`

* `static func current() -> UNUserNotificationCenter`
* `func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool`
* `var delegate: (any UNUserNotificationCenterDelegate)?`
* `func add(_ request: UNNotificationRequest) async throws`
* The \`add\` function ignores all scheduling and repeat options and simply delivers the notification immediately. | | ✅ | `UNUserNotificationCenterDelegate` | Topics ------ [Section titled “Topics”](https://skip.dev/docs/modules/skip-ui/#topics) ### Animation [Section titled “Animation”](https://skip.dev/docs/modules/skip-ui/#animation) Skip supports SwiftUI’s `.animation` and `.transition` modifiers as well as its `withAnimation` function on Android. The following properties are currently animatable: * `.background` color * `.border` color * `.fill` color * `.font` size * `.foregroundColor` * `.foregroundStyle` color * `.frame` width and height * `.offset` * `.opacity` * `.rotationEffect` * `.scaleEffect` * `.stroke` color All of SwiftUI’s built-in transitions are supported on Android. To use transitions or to animate views being added or removed in general, however, you **must** assign a unique `.id` value to every view in the parent `HStack`, `VStack`, or `ZStack`: VStack { FirstView() .id(100) if condition { SecondView() .transition(.scale) .id(200) }}.animation(.default) Skip converts the various SwiftUI animation types to their Compose equivalents. For many SwiftUI spring animations, though, Skip uses Compose’s simple `EaseInOutBack` easing function rather than a true spring. Only constructing a spring with `SwiftUI.Spring(mass:stiffness:damping:)` creates a true Compose spring animation. Using an easing function rather than a true spring allows us to overcome Compose’s limitations on springs: * True spring animations cannot set a duration * True spring animations cannot have a delay * True spring animations cannot repeat Custom `Animatables` and `Transitions` are not supported. Finally, if you nest `withAnimation` blocks, Android will apply the innermost animation to all block actions. ### Colors [Section titled “Colors”](https://skip.dev/docs/modules/skip-ui/#colors) #### Accent Color [Section titled “Accent Color”](https://skip.dev/docs/modules/skip-ui/#accent-color) In addition to programmatically using SwiftUI’s `.tint` modifier, iOS allows you to set your application’s accent color via the `AccentColor` resource in your app’s `Assets` asset catalog. In a Skip app, you can find `Assets` in the `Darwin` folder. Skip also supports these mechanisms, but your generated Android app can’t access the `Darwin` folder contents. To define an accent color resource for your Android app, create a color set called `AccentColor` in the `Sources//Resources/Module` asset catalog. See the section on Named Colors below for additional details. #### Named Colors [Section titled “Named Colors”](https://skip.dev/docs/modules/skip-ui/#named-colors) Named colors can be bundled in asset catalogs provided in the `Resources` folder of your SwiftPM modules. Your `Package.swift` project should have the module’s `.target` include the `Resources` folder for resource processing (which is the default for projects created with `skip init`): .target(name: "MyModule", dependencies: ..., resources: [.process("Resources")], plugins: skipstone) Once an asset catalog is added to your `Resources` folder, any named colors can be loaded and displayed using the `Color(_:bundle:)` constructor. For example: Color("WarningYellow", bundle: .module) See the [Skip Showcase app](https://skip.dev/docs/samples/skipapp-showcase) `ColorPlayground` for a concrete example of using a named color in an asset catalog, and see that project’s Xcode project file ([screenshot ↗](https://assets.skip.dev/screens/SkipUI_Asset_Image.png) ) to see the configuration of the `.xcassets` file for the app module. When an app project is first created with `skip init`, it will contain two separate asset catalogs: a project-level `Assets.xcassets` catalog that contains the app’s icons, and an empty module-level `Module.xcassets` catalog. **Add your assets to `Module.xcassets`.** Only the module-level catalog will be transpiled, since the project-level catalog is not processed by the skip transpiler. For Android, Skip only uses named colors that you’ve set for “Universal” devices. You can define the color using RGB values or use any of the “Universal System Color” constants. ### ColorScheme [Section titled “ColorScheme”](https://skip.dev/docs/modules/skip-ui/#colorscheme) SkipUI fully supports the `.preferredColorScheme` modifier. If you created your app with the `skip` tool prior to v0.8.26, however, you will have to update the included `Android/app/src/main/kotlin/.../Main.kt` file in order for the modifier to work correctly. Using the latest [`Main.kt` ↗](https://source.skip.dev/skipapp-hello/blob/main/Android/app/src/main/kotlin/hello/skip/Main.kt) as your template, please do the following: 1. Replace the all of the import statements with ones from latest `Main.kt` 2. Replace the contents of the `setContent { ... }` block with the content from the latest `Main.kt` 3. Replace the `MaterialThemeRootView()` function with the `PresentationRootView(context:)` function from the latest `Main.kt` With these updates in place, you should be able to use `.preferredColorScheme` successfully. ### Custom Fonts [Section titled “Custom Fonts”](https://skip.dev/docs/modules/skip-ui/#custom-fonts) Custom fonts can be embedded and referenced using `Font.custom`. Fonts are loaded differently depending on the platform. On iOS the custom font name is the full Postscript name of the font, and on Android the name is the font’s file name without the extension. Android requires that font file names contain only alphanumeric characters and underscores, so you should manually name your embedded font to the lowercased and underscore-separated form of the Postscript name of the font. SkipUI’s `Font.custom` call will accommodate this by translating your custom font name like “Protest Guerrilla” into an Android-compatible name like “protest\_guerrilla.ttf”. Text("Custom Font") .font(Font.custom("Protest Guerrilla", size: 30.0)) // protest_guerrilla.ttf Custom fonts are embedded differently for each platform. On Android you should create a folder `Android/app/src/main/res/font/` and add the font file, which will cause Android to automatically embed any fonts in that folder as resources. For iOS, you must add the font by adding to the Xcode project’s app target and ensure the font file is included in the file list in the app target’s “Build Phases” tab’s “Copy Bundle Resources” phase. In addition, iOS needs to have the font explicitly listed in the Xcode project target’s “Info” tab under “Custom Application Target Properties” by adding a new key for the “Fonts provided by application” (whose raw name is “UIAppFonts”) and adding each font’s file name to the string array. See the [Skip Showcase app](https://skip.dev/docs/samples/skipapp-showcase) `TextPlayground` for a concrete example of using a custom font, and see that project’s Xcode project file ([screenshot ↗](https://assets.skip.dev/screens/SkipUI_Custom_Font.png) ) to see how the font is included on both the iOS and Android sides of the app. ### Environment Keys [Section titled “Environment Keys”](https://skip.dev/docs/modules/skip-ui/#environment-keys) SwiftUI has many built-in environment keys. These keys are defined in `EnvironmentValues` and typically accessed with the `@Environment` property wrapper. In additional to supporting your custom environment keys, SkipUI exposes the following built-in environment keys: * `autocorrectionDisabled` (read-only) * `backgroundStyle` * `dismiss` * `font` * `horizontalSizeClass` * `isEnabled` * `isSearching` (read-only) * `layoutDirection` * `lineLimit` * `locale` * `openURL` * `refresh` * `scenePhase` * `scrollDismissesKeyboardMode` * `timeZone` * `truncationMode` * `verticalSizeClass` ### ForEach [Section titled “ForEach”](https://skip.dev/docs/modules/skip-ui/#foreach) The SwiftUI `ForEach` view allows you to generate views for a range or collection of content. SkipUI support any `Int` range or any `RandomAccessCollection`. If the collection elements do not implement the `Identifiable` protocol, specify the key path to a property that can be used to uniquely identify each element. These `id` values must follow our [Restrictions on Identifiers](https://skip.dev/docs/modules/skip-ui/#restrictions-on-identifiers) . ForEach([person1, person2, person3], id: \.fullName) { person in HStack { Text(person.fullName) Spacer() Text(person.age) }} **Important**: When the body of your `ForEach` contains multiple top-level views (e.g. a full row of a `VGrid`), or any single view that expands to additional views (like a `Section` or a nested `ForEach`), SkipUI must “unroll” the loop in order to supply all its views individually to Compose. This means that the `ForEach` will be entirely iterated up front, though the views it produces won’t yet be rendered. ### Gestures [Section titled “Gestures”](https://skip.dev/docs/modules/skip-ui/#gestures) SkipUI currently supports tap, long press, drag, magnify, and rotate gestures. You can use either the general `.gesture` modifier or the specialized modifiers like `.onTapGesture` to add gesture support to your views. The following limitations apply: * `@GestureState` is only supported in Skip Fuse. Use the `Gesture.onEnded` modifier to reset your state. * Tap counts > 2 are not supported. * Gesture velocity and predicted end location are always reported as zero and the current location, respectively. * Only the `onChanged` and `onEnded` gesture modifiers are supported. * Customization of minimum touch duration, distance, etc. is generally not supported. * When applying gestures to an offset view, place any gesture modifiers **before** the `.offset` modifier. There is one exception to the last limitation: you **can** create a `DragGesture(minimumDistance: 0)` in order to detect touch down events immediately. #### Shapes and Paths [Section titled “Shapes and Paths”](https://skip.dev/docs/modules/skip-ui/#shapes-and-paths) SwiftUI automatically applies a mask to shapes and paths so that touches outside the shape do not trigger its gestures. SkipUI emulates this feature, but it is **not** supported on custom shapes and paths that have a `.stroke` applied. These shapes will register touches anywhere in their bounds. Consider using `.strokeBorder` instead of `.stroke` when a gesture mask is needed on a custom shape. ### Grids [Section titled “Grids”](https://skip.dev/docs/modules/skip-ui/#grids) SkipUI renders SwiftUI grid views using native Compose grids. This provides maximum performance and a native feel on Android. The different capabilities of SwiftUI and Compose grids, however, imposes restrictions on SwiftUI grid support in Android: * Pinned headers and footers are not supported. * When you place a `LazyHGrid` or `LazyVGrid` in a `ScrollView`, it must be the only child of that view. * When you define your grid with an array of `GridItem` specs, your Android grid is **based on the first `GridItem`**. Compose does not support different specs for different rows or columns, so SkipUI applies the first spec to all of them. * Maximum `GridItem` sizes are ignored. * Also see the `ForEach` [topic](https://skip.dev/docs/modules/skip-ui/#foreach) . ### Haptics [Section titled “Haptics”](https://skip.dev/docs/modules/skip-ui/#haptics) SkipUI supports UIKit’s `UIFeedbackGenerator` API for generating haptic feedback on the device, typically as a result of user interaction. Some examples are as follows: // impact haptic feedbackUIImpactFeedbackGenerator(style: .light).impactOccurred()UIImpactFeedbackGenerator(style: .medium).impactOccurred()UIImpactFeedbackGenerator(style: .heavy).impactOccurred() UIImpactFeedbackGenerator().impactOccurred(intensity: 0.5) // notification haptic feedbackUINotificationFeedbackGenerator().notificationOccurred(.success)UINotificationFeedbackGenerator().notificationOccurred(.warning)UINotificationFeedbackGenerator().notificationOccurred(.error) // selection haptic feedbackUISelectionFeedbackGenerator().selectionChanged() ### Images [Section titled “Images”](https://skip.dev/docs/modules/skip-ui/#images) #### Network Images [Section titled “Network Images”](https://skip.dev/docs/modules/skip-ui/#network-images) SkipUI supports loading images from network URLs using SwiftUI’s `AsyncImage`. Our implementation uses the [Coil ↗](https://coil-kt.github.io/coil/) library to download images on Android. This includes support for a loading indicator, such as: AsyncImage(url: URL(string: "https://picsum.photos/id/237/200/300")) { image in image.resizable()} placeholder: { ProgressView()} #### Image Assets [Section titled “Image Assets”](https://skip.dev/docs/modules/skip-ui/#image-assets) Images can be bundled in asset catalogs provided in the `Resources` folder of your SwiftPM modules. Your `Package.swift` project should have the module’s `.target` include the `Resources` folder for resource processing (which is the default for projects created with `skip init`): .target(name: "MyModule", dependencies: ..., resources: [.process("Resources")], plugins: skipstone) Once an asset catalog is added to your `Resources` folder, any bundled images can be loaded and displayed using the `Image(name:bundle:)` constructor. For example: Image("Cat", bundle: .module, label: Text("Cat JPEG image")) When an app project is first created with `skip init`, it will contain two separate asset catalogs: a project-level `Assets.xcassets` catalog that contains the app’s icons, and an empty module-level `Module.xcassets` catalog. **Add your assets to `Module.xcassets`.** Only the module-level catalog will be transpiled, since the project-level catalog is not processed by the skip transpiler. See the [Skip Showcase app](https://skip.dev/docs/samples/skipapp-showcase) `ImagePlayground` for a concrete example of using a bundled image in an asset catalog, and see that project’s Xcode project file ([screenshot ↗](https://assets.skip.dev/screens/SkipUI_Asset_Image.png) ) to see the configuration of the `.xcassets` file for the app module. In addition to raster image formats like .png and .jpg, vector images in the .svg and .pdf formats are also supported in asset catalogs. This can be useful for providing images that can scale up or down with losing quality, and are commonly used for icons. Supported .svg sources are discussed in the [System Symbols](https://skip.dev/docs/modules/skip-ui/#system-symbols) documentation below. PDF images must have the “Preserve Vector Data” flag set in the asset in Xcode ([screenshot ↗](https://assets.skip.dev/screens/SkipUI_PDF_Image.png) ) in order to support tinting with the `.foregroundStyle(color)` modifier. Otherwise, the colors set in the PDF itself will always be used when displaying the image. Image("baseball-icon", bundle: .module, label: Text("Baseball Icon")) .resizable() .aspectRatio(contentMode: .fit) .foregroundStyle(Color.cyan) .frame(width: 30, height: 30) #### Bundled Images [Section titled “Bundled Images”](https://skip.dev/docs/modules/skip-ui/#bundled-images) In addition to using asset catalogs, images may be included in the `Resources` folder and referenced directly using `AsyncImage` to display local image resources. This works on both iOS and through Skip on Android. So if you have an image `Sources/MyModule/Resources/sample.jpg` then the following SwiftUI will display the image on both platforms: AsyncImage(url: Bundle.module.url(forResource: "sample", withExtension: "jpg")) #### System Symbols [Section titled “System Symbols”](https://skip.dev/docs/modules/skip-ui/#system-symbols) The `Image(systemName:)` constructor is used to display a standard system symbol name that is provided on Darwin platforms. There is no built-in equivalent to these symbols on Android, but you can add same-named vector symbols manually, so that code like `Image(systemName: "folder.fill")` will use the built-in “folder.fill” symbol on iOS, but will use your included `folder.fill.svg` vector asset on Android. 1. If it doesn’t already exist, create a `Module.xcassets` asset catalog in your top-level app module’s `Resources` folder. 2. Download the desired symbol from the [Google Material Icons ↗](https://fonts.google.com/icons) catalog. Make sure to download in the iOS SVG format (see the [documentation ↗](https://developers.google.com/fonts/docs/material_icons#icons_for_ios) ). You can also export symbols from the [SF Symbols app ↗](https://developer.apple.com/sf-symbols/) . 3. Give the downloaded symbol file the same name as the iOS symbol you want it to represent on Android. Keep the `.svg` file extension. 4. Drag the file to your `Module.xcassets` asset catalog. See the [Skip Showcase app](https://skip.dev/docs/samples/skipapp-showcase) `ImagePlayground` for a concrete example of using a system symbol with an exported symbol image, and see that project’s Xcode project file ([screenshot ↗](https://assets.skip.dev/screens/SkipUI_Custom_Symbol.png) ) to see how the symbol is included in the `.xcassets` file for the app module. SkipUI currently supports using the view’s `foregroundStyle` and `fontWeight` to customize the color and weight of the symbol, but other symbol modifiers such as `symbolVariant` and `symbolRenderingMode` are currently unsupported. Downloaded symbols can be used directly, or they can be edited using an SVG editor to provide custom vector symbols for you app, as described at [Creating custom symbol images for your app ↗](https://developer.apple.com/documentation/uikit/uiimage/creating_custom_symbol_images_for_your_app) . You use `Image(systemName:)` to load a system symbol image and `Image(_:bundle)` to load your custom symbol, as the following code shows: // Display a system symbol imageImage(systemName: "multiply.circle.fill") // Display a custom symbol image that is included in the module's asset catalogImage("custom.multiply.circle", bundle: .module) This is discussed further in the documentation for [Loading a symbol image ↗](https://developer.apple.com/documentation/uikit/uiimage/configuring_and_displaying_symbol_images_in_your_ui#3234560) . #### Fallback Symbols [Section titled “Fallback Symbols”](https://skip.dev/docs/modules/skip-ui/#fallback-symbols) If a matching system symbol with the same name is not found in any of the asset catalog files for the top-level app module, SkipUI will fallback to a small subset of pre-defined symbol names that map to the equivalent Compose material symbols (as seen at [https://developer.android.com/reference/kotlin/androidx/compose/material/icons/Icons ↗](https://developer.android.com/reference/kotlin/androidx/compose/material/icons/Icons) ). The fallback symbols will not match the iOS equivalents exactly, but will provide a rough approximation of the symbol’s shape and meaning. | iOS | Android | | --- | --- | | arrow.clockwise.circle | Icons.Outlined.Refresh | | arrow.forward | Icons.Outlined.ArrowForward | | arrow.forward.square | Icons.Outlined.ExitToApp | | arrow.left | Icons.Outlined.ArrowBack | | arrowtriangle.down.fill | Icons.Outlined.ArrowDropDown | | bell | Icons.Outlined.Notifications | | bell.fill | Icons.Filled.Notifications | | calendar | Icons.Outlined.DateRange | | cart | Icons.Outlined.ShoppingCart | | cart.fill | Icons.Filled.ShoppingCart | | checkmark | Icons.Outlined.Check | | checkmark.circle | Icons.Outlined.CheckCircle | | checkmark.circle.fill | Icons.Filled.CheckCircle | | chevron.down | Icons.Outlined.KeyboardArrowDown | | chevron.left | Icons.Outlined.KeyboardArrowLeft | | chevron.right | Icons.Outlined.KeyboardArrowRight | | chevron.up | Icons.Outlined.KeyboardArrowUp | | ellipsis | Icons.Outlined.MoreVert | | envelope | Icons.Outlined.Email | | envelope.fill | Icons.Filled.Email | | exclamationmark.triangle | Icons.Outlined.Warning | | exclamationmark.triangle.fill | Icons.Filled.Warning | | face.smiling | Icons.Outlined.Face | | gearshape | Icons.Outlined.Settings | | gearshape.fill | Icons.Filled.Settings | | hand.thumbsup | Icons.Outlined.ThumbUp | | hand.thumbsup.fill | Icons.Filled.ThumbUp | | heart | Icons.Outlined.FavoriteBorder | | heart.fill | Icons.Outlined.Favorite | | house | Icons.Outlined.Home | | house.fill | Icons.Filled.Home | | info.circle | Icons.Outlined.Info | | info.circle.fill | Icons.Filled.Info | | line.3.horizontal | Icons.Outlined.Menu | | list.bullet | Icons.Outlined.List | | location | Icons.Outlined.LocationOn | | location.fill | Icons.Filled.LocationOn | | lock | Icons.Outlined.Lock | | lock.fill | Icons.Filled.Lock | | magnifyingglass | Icons.Outlined.Search | | mappin.circle | Icons.Outlined.Place | | mappin.circle.fill | Icons.Filled.Place | | paperplane | Icons.Outlined.Send | | paperplane.fill | Icons.Filled.Send | | pencil | Icons.Outlined.Create | | person | Icons.Outlined.Person | | person.crop.circle | Icons.Outlined.AccountCircle | | person.crop.circle.fill | Icons.Filled.AccountCircle | | person.crop.square | Icons.Outlined.AccountBox | | person.crop.square.fill | Icons.Filled.AccountBox | | person.fill | Icons.Filled.Person | | phone | Icons.Outlined.Call | | phone.fill | Icons.Filled.Call | | play | Icons.Outlined.PlayArrow | | play.fill | Icons.Filled.PlayArrow | | plus | Icons.Outlined.Add | | plus.circle.fill | Icons.Outlined.AddCircle | | square.and.arrow.up | Icons.Outlined.Share | | square.and.arrow.up.fill | Icons.Filled.Share | | star.fill | Icons.Filled.Star | | trash | Icons.Outlined.Delete | | trash.fill | Icons.Filled.Delete | | wrench | Icons.Outlined.Build | | wrench.fill | Icons.Filled.Build | | xmark | Icons.Outlined.Clear | In Android-only code, you can also supply any `androidx.compose.material.icons.Icons` image name as the `systemName`. For example: #if SKIPImage(systemName: "Icons.Filled.Settings")#endif ### Layout [Section titled “Layout”](https://skip.dev/docs/modules/skip-ui/#layout) SkipUI fully supports SwiftUI’s various layout mechanisms, including `HStack`, `VStack`, `ZStack`, and the `.frame` modifier. If you discover layout edge cases where the result on Android does not match the result on iOS, please file an Issue. The following is a list of known cases where results may not match: * Skip never places content in an implicit `VStack`, like SwiftUI sometimes does. Always place multiple views in an explicit stack of the desired type. * Expanding elements such as `Spacer` or `.frame(maxWidth: .infinity)` within nested `HStacks` or `VStacks` may measure differently. Try un-nesting stacks to get more SwiftUI-like layout. Note: if your app was developed under an earlier version of Skip and it relies on nuances of older layout behavior, you can apply the Android-only `.layoutImplementationVersion()` modifier. Set this modifier on a `View` hierarchy to simulate the previous behavior: SomeRootView() #if os(Android) .layoutImplementationVersion(0) #endif ### Lists [Section titled “Lists”](https://skip.dev/docs/modules/skip-ui/#lists) SwiftUI `Lists` are powerful and flexible components. SkipUI currently supports the following patterns for specifying `List` content. Static content. Embed a child view for each row directly within the `List`: List { Text("Row 1") Text("Row 2") Text("Row 3")} Indexed content. Specify an `Int` range and a closure to create a row for each index: List(1...100) { index in Text("Row \(index)")} Collection content. Supply any `RandomAccessCollection` - typically an `Array` - and a closure to create a row for each element. If the elements do not implement the `Identifiable` protocol, specify the key path to a property that can be used to uniquely identify each element: List([person1, person2, person3], id: \.fullName) { person in HStack { Text(person.fullName) Spacer() Text(person.age) }} `ForEach` content. Use `ForEach` to specify indexed or collection content. This allows you to mix content types. List { Text("People").bold() ForEach([person1, person2, person3], id: \.fullName) { person in HStack { Text(person.fullName) Spacer() Text(person.age) } }} When using collection content or a `ForEach` with collection content, you can enable swipe-to-delete and drag-to-reorder by supplying a binding to the collection and the appropriate set of edit actions. List($people, id: \.fullName, editActions: .all) { $person in Text(person.fullName) .deleteDisabled(!person.isDeletable) }} You can also enable editing by using a `ForEach` with the `.onDelete` and `.onMove` modifiers. Make sure your `ForEach` also supplies an `id` for each item. #### List Limitations [Section titled “List Limitations”](https://skip.dev/docs/modules/skip-ui/#list-limitations) * Compose requires that every `id` value in a `List` is unique. This applies even if your list consists of multiple `Sections` or uses multiple `ForEach` components to define its content. * Additionally, `id` values must follow our [Restrictions on Identifiers](https://skip.dev/docs/modules/skip-ui/#restrictions-on-identifiers) . * Nesting of `ForEach` and `Section` views is limited. * See also the `ForEach` view [topic](https://skip.dev/docs/modules/skip-ui/#foreach) . ### Navigation [Section titled “Navigation”](https://skip.dev/docs/modules/skip-ui/#navigation) SwiftUI has three primary forms of navigation: `TabView`, `NavigationStack`, and modal presentations. SkipUI has implemented all three, albeit with the restrictions explained below. SkipUI’s `TabView` does yet not support SwiftUI’s overflow tab behavior. Adding too many tabs will just result in too many tabs rather than SwiftUI’s automatic “More” tab. Similarly, `TabSections` and other features meant for top or sidebar tab placements are ignored. Otherwise, `TabView` acts as you would expect. In SwiftUI, you push views onto a `NavigationStack` with `NavigationLink`. `NavigationLink` has two ways to specify its destination view: embedding the view directly, or specifying a value that is mapped to a view through the `.navigationDestination` modifier, as in the following code sample: NavigationStack { ListView() .navigationTitle(Self.title)} struct ListView : View { var body: some View { List(City.allCases) { city in NavigationLink(value: city) { rowView(city: city) } } .navigationDestination(for: City.self) { city in CityView(city: city) } }} There is another form of `.navigationDestination` that takes a binding and a destination: func navigationDestination(isPresented: Binding, @ViewBuilder destination: () -> any View) -> some View SkipUI supports all of these models. When using `.navigationDestination(isPresented:destination:)`, note that manually setting `isPresented` to `false` will **not** dismiss your view. Prefer standard dismissing mechanisms. Using `.navigationDestination(for:destination:)` to bind data types to destinations also requires some care. It is currently the case that if a pushed view defines a new `.navigationDestination` for key type `T`, it will overwrite any previous stack view’s `T` destination mapping. **Take care not to unintentionally re-map the same key type in the same navigation stack.** Compose imposes an additional restriction as well: we must be able to stringify `.navigationDestination` data key types. See [Restrictions on Identifiers](https://skip.dev/docs/modules/skip-ui/#restrictions-on-identifiers) below. #### Modals [Section titled “Modals”](https://skip.dev/docs/modules/skip-ui/#modals) Skip supports standard modal presentations. Android apps typically allow users to dismiss modals with the Android back button. Skip allows you to selectively disable this behavior with the Android-only `backDismissDisabled(_ isDisabled: Bool = true)` SwiftUI modifier. If you use this modifier, you **must** put it on the top-level view embedded in your `.sheet` or `.fullScreenCover`, as in the following example: SomeContentView() .sheet(isPresented: $isSheetPresented) { SheetContentView() #if os(Android) .backDismissDisabled() #endif } Due to Compose limitations, changing the value passed to `.backDismissDisabled(_: Bool = true)` while the modal is presented has no effect. Only the value at the time of presentation is considered. Note that you might want to pair `backDismissDisabled` with SwiftUI’s `.interactiveDismissDisabled` modifier to also disable dismissing via dragging the sheet down. ### Restrictions on Identifiers [Section titled “Restrictions on Identifiers”](https://skip.dev/docs/modules/skip-ui/#restrictions-on-identifiers) Compose requires all state values to be serializable. This restriction is typically transparent to your code, because when you use property wrappers like `@State`, SkipUI automatically tracks your state objects and gives Compose serializable identifiers in their place. Some SwiftUI values, however, must be stored directly in Compose, including `navigationDestination` values and `List` item identifiers. When this is the case, SkipUI creates a `String` from the value you supply using the following algorithm: * If the value is `Identifiable`, use `String(describing: value.id)` * If the value is `RawRepresentable`, use `String(describing: value.rawValue)` * Else use `String(describing: value)` Please ensure that when using these API, the above algorithm will create unique, stable strings for unique values. ### Safe Area [Section titled “Safe Area”](https://skip.dev/docs/modules/skip-ui/#safe-area) Like the iPhone, Android devices can render content behind system bars like the top status bar and bottom gesture area. SwiftUI code using the `.ignoresSafeArea` modifier to extend content behind system bars will work the same across SwiftUI and SkipUI, with two exceptions: * SkipUI ignores the `SafeAreaRegions.keyboard` region. SkipUI does not represent the onscreen keyboard as a safe area. Rather, it follows the typical Android practice of shrinking the content area to fit above the keyboard. * The `.background(_ style: any ShapeStyle, ignoresSafeAreaEdges edges: Edge.Set = .all)` modifier currently defaults the second argument to `[]` rather than `.all`. Specify the desired edges explicitly if you want to ignore the safe area, as in: MyView() .background(.yellow, ignoresSafeAreaEdges: .all) Remember that you can use `#if SKIP` blocks to confine your `.ignoresSafeArea` calls for iOS or Android only. #### Enabling or Disabling Edge-to-Edge [Section titled “Enabling or Disabling Edge-to-Edge”](https://skip.dev/docs/modules/skip-ui/#enabling-or-disabling-edge-to-edge) Modern SkipUI versions enable Jetpack Compose’s “edgeToEdge” mode by default. If you created your app with the `skip` tool prior to v0.8.32, however, you will have to update the included `Android/app/src/main/kotlin/.../Main.kt` file to render content behind system bars. Using the latest [`Main.kt` ↗](https://source.skip.dev/skipapp-hello/blob/main/Android/app/src/main/kotlin/hello/skip/Main.kt) as your template, please do the following: 1. Add the following import: `import androidx.activity.enableEdgeToEdge` 2. Add the following line to the `MainActivity.onCreate(savedInstanceState:)` function: override fun onCreate(savedInstanceState: android.os.Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() // <--- Add this line ... With these updates in place, your app should extend below the system bars. If you’re running a modern SkipUI version and want to _disable_ edge-to-edge mode, simply remove the `enableEdgeToEdge()` call. ### Scrolling [Section titled “Scrolling”](https://skip.dev/docs/modules/skip-ui/#scrolling) `ScrollView` generally works just as on iOS, but is subject to several limitations on its content. While you must be aware of these limitations, they should not prove too difficult to work with in practice: * The `UnitRect` parameter to `ScrollView` and `ScrollViewProxy` is ignored. * `ScrollViewProxy` works only for `List` and lazy containers: `LazyHStack`, `LazyVStack`, `LazyHGrid`, and `LazyVGrid`. * If you place a lazy container in a `ScrollView`, it must be the **only** content of that `ScrollView`. * The content of any `ScrollView` with the `.scrollTargetBehavior` modifier applied must be a single lazy container with the `.scrollTargetLayout` modifier applied, as in the following example: ScrollView(.horizontal) { LazyHStack { ... } .scrollTargetLayout()}.scrollTargetBehavior(.viewAligned) ### Custom Intents [Section titled “Custom Intents”](https://skip.dev/docs/modules/skip-ui/#custom-intents) The implementation for `UIApplication.open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:])` uses the [Android Intents ↗](https://developer.android.com/guide/components/intents-filters#Types) system to launch the service associated with the given URL. As well as handling the standard link types like “https://”, “tel:”, and “sms:”, Skip also enables specifying a custom intent name as the special `OpenExternalURLOptionsKey` value “intent”, so that you can easily launch a particular intent on Android. For example: #if os(Android)Button("Send Email") { let mailto = URL(string: "mailto:hello@example.com")! var options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:] // specify the exact intent to use options[UIApplication.OpenExternalURLOptionsKey(rawValue: "intent")] = "android.intent.action.SENDTO" // add values for the keys supported by the intent // https://developer.android.com/reference/android/content/Intent#EXTRA_SUBJECT options[UIApplication.OpenExternalURLOptionsKey(rawValue: "android.intent.extra.SUBJECT")] = "Email Subject Line" options[UIApplication.OpenExternalURLOptionsKey(rawValue: "android.intent.extra.TEXT")] = "Email body" Task { await UIApplication.shared.open(mailto, options: options) }}#endif Contributing ------------ [Section titled “Contributing”](https://skip.dev/docs/modules/skip-ui/#contributing) We welcome contributions to SkipUI. The Skip product [documentation](https://skip.dev/docs/contributing/) includes helpful instructions and tips on local Skip library development. The most pressing need is to implement more core components and view modifiers. View modifiers in particular are a ripe source of low-hanging fruit. The Compose `Modifier` type often has built-in functions that replicate SwiftUI modifiers, making these SwiftUI modifiers easy to implement. To help fill in unimplemented API in SkipUI: 1. Find unimplemented API. Unimplemented API will either be within `#if !SKIP` blocks, or will be marked with `@available(*, unavailable)`. Note that most unimplemented `View` modifiers are in the `View.swift` source file. 2. Write an appropriate Compose implementation. See [Implementation Strategy](https://skip.dev/docs/modules/skip-ui/#implementation-strategy) below. 3. Add a compiled Swift wrapper to SkipFuseUI. 4. Write Showcase code to exercise your component. See [Tests](https://skip.dev/docs/modules/skip-ui/#tests) . 5. [Submit a PR ↗](https://source.skip.dev/skip-ui/pulls) . Other forms of contributions such as test cases, comments, and documentation are also welcome! Tests ----- [Section titled “Tests”](https://skip.dev/docs/modules/skip-ui/#tests) The most common way to test SkipUI’s support for a SwiftUI component is through the [Skip Showcase](https://skip.dev/docs/samples/skipapp-showcase) and [Skip Showcase Fuse](https://skip.dev/docs/samples/skipapp-showcase-fuse) apps. Whenever you add or update support for a visible element of SwiftUI, make sure there is a showcase view that exercises the element. This not only gives us a mechanism to test appearance and behavior, but the showcase app becomes a demonstration of supported SwiftUI components on Android over time. Implementation Strategy ----------------------- [Section titled “Implementation Strategy”](https://skip.dev/docs/modules/skip-ui/#implementation-strategy) ### SkipLite Code Transformations [Section titled “SkipLite Code Transformations”](https://skip.dev/docs/modules/skip-ui/#skiplite-code-transformations) SkipUI does not work in isolation. When used from Skip Lite transpiled Swift, it depends on transformations the [skip ↗](https://source.skip.dev/skip) plugin makes to SwiftUI code. And while Skip generally strives to write Kotlin that is similar to hand-crafted code, these SwiftUI transformations are not something you’d want to write yourself. Before discussing SkipUI’s implementation, let’s explore them. Both SwiftUI and Compose are declarative UI frameworks. Both have mechanisms to track state and automatically re-render when state changes. SwiftUI models user interface elements with `View` objects, however, while Compose models them with `@Composable` functions. The Skip transpiler must therefore translate your code defining a `View` graph into `@Composable` function calls. This involves two primary transformations: 1. The transpiler inserts code to sync `View` members that have special meanings in SwiftUI - `@State`, `@EnvironmentObject`, etc - with the corresponding Compose state mechanisms, which are not member-based. The syncing goes two ways, so that your `View` members are populated from Compose’s state values, and changing your `View` members updates Compose’s state values. 2. The transpiler turns `@ViewBuilders` - including `View.body` - into `@Composable` function calls. The second transformation in particular deserves some explanation, because it may help you to understand SkipUI’s internal API. Consider the following simple example: struct V: View { let isHello: Bool var body: some View { if isHello { Text("Hello!") } else { Text("Goodbye!") } }} The transpilation would look something like the following: class V: View { val isHello: Bool constructor(isHello: Bool) { this.isHello = isHello } override fun body(): View { return ComposeBuilder { composectx -> if (isHello) { Text("Hello!").Compose(context = composectx) } else { Text("Goodbye!").Compose(context = composectx) } ComposeResult.ok } } ...} Notice the changes to the `body` content. Rather than returning an arbitrary view tree, the transpiled `body` always returns a single `ComposeBuilder`, a special SkipUI view type that invokes a `@Composable` block. The logic of the original `body` is now within that block, and any `View` that `body` would have returned instead invokes its own `Compose(context:)` function to render the corresponding Compose component. The `Compose(context:)` function is part of SkipUI’s `View` API. Thus the transpiler is able to turn any `View.body` - actually any `@ViewBuilder` - into a `ComposeBuilder`: a block of Compose code that it can invoke to render the desired content. A [later section](https://skip.dev/docs/modules/skip-ui/#composeview) details how you can use SkipUI’s `ComposeView` yourself to move fluidly between SwiftUI and Compose when writing your Android UI. ### Implementation Phases [Section titled “Implementation Phases”](https://skip.dev/docs/modules/skip-ui/#implementation-phases) SkipUI contains stubs for the entire SwiftUI framework. API generally goes through three phases: 1. Code that no one has begun to port to Skip starts in `#if !SKIP` blocks. This hides it from the Skip transpiler. 2. The first implementation step is to move code out of `#if !SKIP` blocks so that it will be transpiled. This is helpful on its own, even if you just mark the API `@available(*, unavailable)` because you are not ready to implement it for Compose. An `unavailable` attribute will provide Skip users with a clear error message, rather than relying on the Kotlin compiler to complain about unfound API. * When moving code out of a `#if !SKIP` block, please strip Apple’s extensive API comments. There is no reason for Skip to duplicate the official SwiftUI documentation, and it obscures any Skip-specific implementation comments we may add. * SwiftUI uses complex generics extensively, and the generics systems of Swift and Kotlin have significant differences. You may have to replace some generics or generic constraints with looser typing in order to transpile successfully. Typing will still be enforced in user code by the Swift compiler. * Reducing the number of Swift extensions and instead folding API into the primary declaration of a type can make Skip’s internal symbol storage more efficient. You should, however, leave `View` modifiers that are specific to a given component - e.g. `.navigationTitle` is specific to `NavigationStack` - within the component’s source file. 3. Finally, we add a Compose implementation and remove any `unavailable` attribute. Note that SkipUI should remain buildable throughout this process. Being able to successfully compile SkipUI in Xcode helps us validate that our ported components still mesh with the rest of the framework. ### Components [Section titled “Components”](https://skip.dev/docs/modules/skip-ui/#components) Before implementing a component, familiarize yourself with SkipUI’s `View` protocol in `Sources/View/View.swift` as well as the files in the `Sources/Compose` directory. It is also helpful to browse the source code for components and modifiers that have already been ported. See the table of [Supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . This simplified `Text` view exemplifies a typical SwiftUI component implementation: public struct Text: View, Renderable Equatable, Sendable { let text: String public init(_ text: String) { self.text = text } ... #if SKIP @Composable override func Render(context: ComposeContext) { let modifier = context.modifier let font = EnvironmentValues.shared.font ?? Font(fontImpl: { LocalTextStyle.current }) ... androidx.compose.material3.Text(text: text, modifier: modifier, style: font.fontImpl(), ...) } #else public var body: some View { stubView() } #endif} As you can see, the `Text` type is defined just as it is in SwiftUI. We then use an `#if SKIP` block to implement the composable `Renderable.Render` function for Android, while we stub the `body` var to satisfy the Swift compiler. `Render` makes the necessary Compose calls to render the component, applying the modifier from the given `context` as well as any applicable environment values. If `Text` had any child views, `Render` would call `child.Compose(context: context.content())` to compose its child content. ### Modifiers [Section titled “Modifiers”](https://skip.dev/docs/modules/skip-ui/#modifiers) Modifiers, on the other hand, use the `ModifiedContent` to perform actions, including changing the `androidx.compose.ui.Modifier` passed to the modified view. Here is the `.opacity` modifier: extension View { public func opacity(_ opacity: Double) -> some View { #if SKIP return ModifiedContent(content: self, modifier: RenderModifier { context in return context.modifier.alpha(Float(opacity)) }) #else return self #endif }} Some modifiers have their own rendering logic. These modifiers use a different `RenderModifier` constructor that defines the composition. Here, for example, `.frame` composes the view within a Compose `Box` with the proper dimensions and alignment: extension View { public func frame(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center) -> some View { #if SKIP return ModifiedContent(content: self, modifier: RenderModifier { renderable, context in var modifier = context.modifier if let width { modifier = modifier.width(width.dp) } if let height { modifier = modifier.height(height.dp) } let contentContext = context.content() ComposeContainer(modifier: modifier, fixedWidth: width != nil, fixedHeight: height != nil) { modifier in Box(modifier: modifier, contentAlignment: alignment.asComposeAlignment()) { renderable.Render(context: contentContext) } } }) #else return self #endif }} Still other modifiers don’t affect rendering at all, but perform side effects or the environment. Pass a `SideEffectModifier` or `EnvironmentModifier` to the `ModifiedContent` in these cases. Like other SwiftUI components, modifiers use `#if SKIP ... #else ...` to stub the Swift implementation and keep SkipUI buildable in Xcode. --- # SkipUI Component Samples | Skip [Skip to content](https://skip.dev/docs/components/#_top) SkipUI Component Samples ======================== View samples of Skip’s SwiftUI support on Android below. Consult the SkipUI module for a [complete list of supported SwiftUI](https://skip.dev/docs/modules/skip-ui/#supported-swiftui) . * [Accessibility](https://skip.dev/docs/components/accessibility) * [Alert](https://skip.dev/docs/components/alert) * [Animation](https://skip.dev/docs/components/animation) * [Audio](https://skip.dev/docs/components/audio) * [Background](https://skip.dev/docs/components/background) * [Border](https://skip.dev/docs/components/border) * [Button](https://skip.dev/docs/components/button) * [Color](https://skip.dev/docs/components/color) * [ColorScheme](https://skip.dev/docs/components/colorscheme) * [ConfirmationDialog](https://skip.dev/docs/components/confirmationdialog) * [DatePicker](https://skip.dev/docs/components/datepicker) * [Divider](https://skip.dev/docs/components/divider) * [Form](https://skip.dev/docs/components/form) * [Frame](https://skip.dev/docs/components/frame) * [GeometryReader](https://skip.dev/docs/components/geometryreader) * [Gesture](https://skip.dev/docs/components/gesture) * [Gradient](https://skip.dev/docs/components/gradient) * [Grid](https://skip.dev/docs/components/grid) * [HapticFeedback](https://skip.dev/docs/components/hapticfeedback) * [Icon](https://skip.dev/docs/components/icon) * [Image](https://skip.dev/docs/components/image) * [Keyboard](https://skip.dev/docs/components/keyboard) * [Label](https://skip.dev/docs/components/label) * [Link](https://skip.dev/docs/components/link) * [List](https://skip.dev/docs/components/list) * [Localization](https://skip.dev/docs/components/localization) * [Menu](https://skip.dev/docs/components/menu) * [Modifier](https://skip.dev/docs/components/modifier) * [NavigationStack](https://skip.dev/docs/components/navigationstack) * [Observable](https://skip.dev/docs/components/observable) * [Offset](https://skip.dev/docs/components/offset) * [OffsetPosition](https://skip.dev/docs/components/offsetposition) * [OnSubmit](https://skip.dev/docs/components/onsubmit) * [Overlay](https://skip.dev/docs/components/overlay) * [Pasteboard](https://skip.dev/docs/components/pasteboard) * [Picker](https://skip.dev/docs/components/picker) * [ProgressView](https://skip.dev/docs/components/progressview) * [SafeArea](https://skip.dev/docs/components/safearea) * [ScenePhase](https://skip.dev/docs/components/scenephase) * [ScrollView](https://skip.dev/docs/components/scrollview) * [Searchable](https://skip.dev/docs/components/searchable) * [SecureField](https://skip.dev/docs/components/securefield) * [Shadow](https://skip.dev/docs/components/shadow) * [Shape](https://skip.dev/docs/components/shape) * [ShareLink](https://skip.dev/docs/components/sharelink) * [Sheet](https://skip.dev/docs/components/sheet) * [Slider](https://skip.dev/docs/components/slider) * [Spacer](https://skip.dev/docs/components/spacer) * [Stack](https://skip.dev/docs/components/stack) * [State](https://skip.dev/docs/components/state) * [Storage](https://skip.dev/docs/components/storage) * [Symbol](https://skip.dev/docs/components/symbol) * [Table](https://skip.dev/docs/components/table) * [TabView](https://skip.dev/docs/components/tabview) * [Text](https://skip.dev/docs/components/text) * [TextField](https://skip.dev/docs/components/textfield) * [Timer](https://skip.dev/docs/components/timer) * [Toggle](https://skip.dev/docs/components/toggle) * [Toolbar](https://skip.dev/docs/components/toolbar) * [Transition](https://skip.dev/docs/components/transition) * [VideoPlayer](https://skip.dev/docs/components/videoplayer) * [ZIndex](https://skip.dev/docs/components/zindex) --- # Skip Sample Apps | Skip [Skip to content](https://skip.dev/docs/samples/#_top) Skip Sample Apps ================ Skip Sample Apps ================ [Section titled “Skip Sample Apps”](https://skip.dev/docs/samples/#skip-sample-apps) | App | Repository | Version | | --- | --- | --- | | [SwiftUI Showcase Fuse](https://skip.dev/docs/samples/skipapp-showcase-fuse) | [skipapp-showcase-fuse ↗](https://source.skip.dev/skipapp-showcase-fuse.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-showcase-fuse.svg?style=flat)](https://github.com/skiptools/skipapp-showcase-fuse/releases) | | [Hello Skipper Fuse](https://skip.dev/docs/samples/skipapp-hello-fuse) | [skipapp-hello-fuse ↗](https://source.skip.dev/skipapp-hello-fuse.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-hello-fuse.svg?style=flat)](https://github.com/skiptools/skipapp-hello-fuse/releases) | | [Travel App Fuse](https://skip.dev/docs/samples/skipapp-bookings-fuse) | [skipapp-bookings-fuse ↗](https://source.skip.dev/skipapp-bookings-fuse.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-bookings-fuse.svg?style=flat)](https://github.com/skiptools/skipapp-bookings-fuse/releases) | | [Travel Posters Fuse](https://skip.dev/docs/samples/skipapp-travelposters-native) | [skipapp-travelposters-native ↗](https://source.skip.dev/skipapp-travelposters-native.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-travelposters-native.svg?style=flat)](https://github.com/skiptools/skipapp-travelposters-native/releases) | | [Notes (SQL)](https://skip.dev/docs/samples/skipapp-notes) | [skipapp-notes ↗](https://source.skip.dev/skipapp-notes.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-notes.svg?style=flat)](https://github.com/skiptools/skipapp-notes/releases) | | [Fireside Fuse (Firebase)](https://skip.dev/docs/samples/skipapp-fireside-fuse) | [skipapp-fireside-fuse ↗](https://source.skip.dev/skipapp-fireside-fuse.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-fireside-fuse.svg?style=flat)](https://github.com/skiptools/skipapp-fireside-fuse/releases) | | [SwiftUI Showcase Lite](https://skip.dev/docs/samples/skipapp-showcase) | [skipapp-showcase ↗](https://source.skip.dev/skipapp-showcase.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-showcase.svg?style=flat)](https://github.com/skiptools/skipapp-showcase/releases) | | [Hello Skipper Lite](https://skip.dev/docs/samples/skipapp-hello) | [skipapp-hello ↗](https://source.skip.dev/skipapp-hello.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-hello.svg?style=flat)](https://github.com/skiptools/skipapp-hello/releases) | | [Travel App Lite](https://skip.dev/docs/samples/skipapp-bookings) | [skipapp-bookings ↗](https://source.skip.dev/skipapp-bookings.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-bookings.svg?style=flat)](https://github.com/skiptools/skipapp-bookings/releases) | | [Data Bake (SQL)](https://skip.dev/docs/samples/skipapp-databake) | [skipapp-databake ↗](https://source.skip.dev/skipapp-databake.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-databake.svg?style=flat)](https://github.com/skiptools/skipapp-databake/releases) | | [Weather App](https://skip.dev/docs/samples/skipapp-weather) | [skipapp-weather ↗](https://source.skip.dev/skipapp-weather.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-weather.svg?style=flat)](https://github.com/skiptools/skipapp-weather/releases) | | [Fireside (Firebase)](https://skip.dev/docs/samples/skipapp-fireside) | [skipapp-fireside ↗](https://source.skip.dev/skipapp-fireside.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-fireside.svg?style=flat)](https://github.com/skiptools/skipapp-fireside/releases) | | [Lottie Demo](https://skip.dev/docs/samples/skipapp-lottiedemo) | [skipapp-lottiedemo ↗](https://source.skip.dev/skipapp-lottiedemo.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-lottiedemo.svg?style=flat)](https://github.com/skiptools/skipapp-lottiedemo/releases) | | [Scrumskipper](https://skip.dev/docs/samples/skipapp-scrumskipper) | [skipapp-scrumskipper ↗](https://source.skip.dev/skipapp-scrumskipper.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skipapp-scrumskipper.svg?style=flat)](https://github.com/skiptools/skipapp-scrumskipper/releases) | --- # Skip Module Index | Skip [Skip to content](https://skip.dev/docs/modules/#_top) Skip Module Index ================= | Module | Repository | Version | Status | | --- | --- | --- | --- | | [SkipAuth0](https://skip.dev/docs/modules/skip-auth0) | [skip-auth0.git ↗](https://source.skip.dev/skip-auth0.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-auth0.svg?style=flat)](https://github.com/skiptools/skip-auth0/releases) | [![Release](https://github.com/skiptools/skip-auth0/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-auth0/actions) | | [SkipAuthenticationServices](https://skip.dev/docs/modules/skip-authentication-services) | [skip-authentication-services.git ↗](https://source.skip.dev/skip-authentication-services.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-authentication-services.svg?style=flat)](https://github.com/skiptools/skip-authentication-services/releases) | [![Release](https://github.com/skiptools/skip-authentication-services/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-authentication-services/actions) | | [SkipAV](https://skip.dev/docs/modules/skip-av) | [skip-av.git ↗](https://source.skip.dev/skip-av.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-av.svg?style=flat)](https://github.com/skiptools/skip-av/releases) | [![Release](https://github.com/skiptools/skip-av/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-av/actions) | | [SkipBluetooth](https://skip.dev/docs/modules/skip-bluetooth) | [skip-bluetooth.git ↗](https://source.skip.dev/skip-bluetooth.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-bluetooth.svg?style=flat)](https://github.com/skiptools/skip-bluetooth/releases) | [![Release](https://github.com/skiptools/skip-bluetooth/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-bluetooth/actions) | | [SkipBridge](https://skip.dev/docs/modules/skip-bridge) | [skip-bridge.git ↗](https://source.skip.dev/skip-bridge.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-bridge.svg?style=flat)](https://github.com/skiptools/skip-bridge/releases) | [![Release](https://github.com/skiptools/skip-bridge/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-bridge/actions) | | [SkipDevice](https://skip.dev/docs/modules/skip-device) | [skip-device.git ↗](https://source.skip.dev/skip-device.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-device.svg?style=flat)](https://github.com/skiptools/skip-device/releases) | [![Release](https://github.com/skiptools/skip-device/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-device/actions) | | [SkipFFI](https://skip.dev/docs/modules/skip-ffi) | [skip-ffi.git ↗](https://source.skip.dev/skip-ffi.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-ffi.svg?style=flat)](https://github.com/skiptools/skip-ffi/releases) | [![Release](https://github.com/skiptools/skip-ffi/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-ffi/actions) | | [SkipFirebase](https://skip.dev/docs/modules/skip-firebase) | [skip-firebase.git ↗](https://source.skip.dev/skip-firebase.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-firebase.svg?style=flat)](https://github.com/skiptools/skip-firebase/releases) | [![Release](https://github.com/skiptools/skip-firebase/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-firebase/actions) | | [SkipFoundation](https://skip.dev/docs/modules/skip-foundation) | [skip-foundation.git ↗](https://source.skip.dev/skip-foundation.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-foundation.svg?style=flat)](https://github.com/skiptools/skip-foundation/releases) | [![Release](https://github.com/skiptools/skip-foundation/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-foundation/actions) | | [SkipFuse](https://skip.dev/docs/modules/skip-fuse) | [skip-fuse.git ↗](https://source.skip.dev/skip-fuse.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-fuse.svg?style=flat)](https://github.com/skiptools/skip-fuse/releases) | [![Release](https://github.com/skiptools/skip-fuse/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-fuse/actions) | | [SkipFuseUI](https://skip.dev/docs/modules/skip-fuse-ui) | [skip-fuse-ui.git ↗](https://source.skip.dev/skip-fuse-ui.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-fuse-ui.svg?style=flat)](https://github.com/skiptools/skip-fuse-ui/releases) | [![Release](https://github.com/skiptools/skip-fuse-ui/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-fuse-ui/actions) | | [SkipKeychain](https://skip.dev/docs/modules/skip-keychain) | [skip-keychain.git ↗](https://source.skip.dev/skip-keychain.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-keychain.svg?style=flat)](https://github.com/skiptools/skip-keychain/releases) | [![Release](https://github.com/skiptools/skip-keychain/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-keychain/actions) | | [SkipKit](https://skip.dev/docs/modules/skip-kit) | [skip-kit.git ↗](https://source.skip.dev/skip-kit.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-kit.svg?style=flat)](https://github.com/skiptools/skip-kit/releases) | [![Release](https://github.com/skiptools/skip-kit/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-kit/actions) | | [SkipLib](https://skip.dev/docs/modules/skip-lib) | [skip-lib.git ↗](https://source.skip.dev/skip-lib.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-lib.svg?style=flat)](https://github.com/skiptools/skip-lib/releases) | [![Release](https://github.com/skiptools/skip-lib/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-lib/actions) | | [SkipMarketplace](https://skip.dev/docs/modules/skip-marketplace) | [skip-marketplace.git ↗](https://source.skip.dev/skip-marketplace.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-marketplace.svg?style=flat)](https://github.com/skiptools/skip-marketplace/releases) | [![Release](https://github.com/skiptools/skip-marketplace/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-marketplace/actions) | | [SkipModel](https://skip.dev/docs/modules/skip-model) | [skip-model.git ↗](https://source.skip.dev/skip-model.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-model.svg?style=flat)](https://github.com/skiptools/skip-model/releases) | [![Release](https://github.com/skiptools/skip-model/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-model/actions) | | [SkipMotion](https://skip.dev/docs/modules/skip-motion) | [skip-motion.git ↗](https://source.skip.dev/skip-motion.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-motion.svg?style=flat)](https://github.com/skiptools/skip-motion/releases) | [![Release](https://github.com/skiptools/skip-motion/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-motion/actions) | | [SkipNFC](https://skip.dev/docs/modules/skip-nfc) | [skip-nfc.git ↗](https://source.skip.dev/skip-nfc.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-nfc.svg?style=flat)](https://github.com/skiptools/skip-nfc/releases) | [![Release](https://github.com/skiptools/skip-nfc/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-nfc/actions) | | [SkipPostHog](https://skip.dev/docs/modules/skip-posthog) | [skip-posthog.git ↗](https://source.skip.dev/skip-posthog.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-posthog.svg?style=flat)](https://github.com/skiptools/skip-posthog/releases) | [![Release](https://github.com/skiptools/skip-posthog/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-posthog/actions) | | [SkipQRCode](https://skip.dev/docs/modules/skip-qrcode) | [skip-qrcode.git ↗](https://source.skip.dev/skip-qrcode.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-qrcode.svg?style=flat)](https://github.com/skiptools/skip-qrcode/releases) | [![Release](https://github.com/skiptools/skip-qrcode/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-qrcode/actions) | | [SkipRevenue](https://skip.dev/docs/modules/skip-revenue) | [skip-revenue.git ↗](https://source.skip.dev/skip-revenue.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-revenue.svg?style=flat)](https://github.com/skiptools/skip-revenue/releases) | [![Release](https://github.com/skiptools/skip-revenue/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-revenue/actions) | | [SkipScript](https://skip.dev/docs/modules/skip-script) | [skip-script.git ↗](https://source.skip.dev/skip-script.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-script.svg?style=flat)](https://github.com/skiptools/skip-script/releases) | [![Release](https://github.com/skiptools/skip-script/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-script/actions) | | [SkipSentry](https://skip.dev/docs/modules/skip-sentry) | [skip-sentry.git ↗](https://source.skip.dev/skip-sentry.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-sentry.svg?style=flat)](https://github.com/skiptools/skip-sentry/releases) | [![Release](https://github.com/skiptools/skip-sentry/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-sentry/actions) | | [SkipSocketIO](https://skip.dev/docs/modules/skip-socketio) | [skip-socketio.git ↗](https://source.skip.dev/skip-socketio.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-socketio.svg?style=flat)](https://github.com/skiptools/skip-socketio/releases) | [![Release](https://github.com/skiptools/skip-socketio/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-socketio/actions) | | [SkipSQL](https://skip.dev/docs/modules/skip-sql) | [skip-sql.git ↗](https://source.skip.dev/skip-sql.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-sql.svg?style=flat)](https://github.com/skiptools/skip-sql/releases) | [![Release](https://github.com/skiptools/skip-sql/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-sql/actions) | | [SkipStripe](https://skip.dev/docs/modules/skip-stripe) | [skip-stripe.git ↗](https://source.skip.dev/skip-stripe.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-stripe.svg?style=flat)](https://github.com/skiptools/skip-stripe/releases) | [![Release](https://github.com/skiptools/skip-stripe/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-stripe/actions) | | [SkipSupabase](https://skip.dev/docs/modules/skip-supabase) | [skip-supabase.git ↗](https://source.skip.dev/skip-supabase.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-supabase.svg?style=flat)](https://github.com/skiptools/skip-supabase/releases) | [![Release](https://github.com/skiptools/skip-supabase/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-supabase/actions) | | [SkipUI](https://skip.dev/docs/modules/skip-ui) | [skip-ui.git ↗](https://source.skip.dev/skip-ui.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-ui.svg?style=flat)](https://github.com/skiptools/skip-ui/releases) | [![Release](https://github.com/skiptools/skip-ui/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-ui/actions) | | [SkipUnit](https://skip.dev/docs/modules/skip-unit) | [skip-unit.git ↗](https://source.skip.dev/skip-unit.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-unit.svg?style=flat)](https://github.com/skiptools/skip-unit/releases) | [![Release](https://github.com/skiptools/skip-unit/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-unit/actions) | | [SkipWeb](https://skip.dev/docs/modules/skip-web) | [skip-web.git ↗](https://source.skip.dev/skip-web.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-web.svg?style=flat)](https://github.com/skiptools/skip-web/releases) | [![Release](https://github.com/skiptools/skip-web/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-web/actions) | | [SkipXML](https://skip.dev/docs/modules/skip-xml) | [skip-xml.git ↗](https://source.skip.dev/skip-xml.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-xml.svg?style=flat)](https://github.com/skiptools/skip-xml/releases) | [![Release](https://github.com/skiptools/skip-xml/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-xml/actions) | | [SkipZip](https://skip.dev/docs/modules/skip-zip) | [skip-zip.git ↗](https://source.skip.dev/skip-zip.git) | [![Release](https://img.shields.io/github/v/release/skiptools/skip-zip.svg?style=flat)](https://github.com/skiptools/skip-zip/releases) | [![Release](https://github.com/skiptools/skip-zip/actions/workflows/ci.yml/badge.svg?style=flat)](https://github.com/skiptools/skip-zip/actions) | ![Skip Modules Diagram](https://assets.skip.dev/diagrams/skip-modules.svg) --- # Skip Fuse vs Lite | Skip [Skip to content](https://skip.dev/docs/status/#_top) Skip Fuse vs Lite ================= ### Skip Fuse [Section titled “Skip Fuse”](https://skip.dev/docs/status/#skip_fuse) **Skip Fuse** uses the native Swift-on-Android toolchain to compile your Swift for Android, auto-generating [JNI ↗](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/) bridging code to communicate with [Kotlin ↗](https://kotlinlang.org/) and Java. ### Skip Lite [Section titled “Skip Lite”](https://skip.dev/docs/status/#skip_lite) **Skip Lite** transpiles[1](https://skip.dev/docs/status/#user-content-fn-1) your Swift source to Kotlin source, which is then compiled using the standard Kotlin compiler. Except where otherwise noted, this documentation focuses on Skip Fuse. To learn more about Skip Lite and the tradeoffs between native and transpiled modes, read [Native and Transpiled](https://skip.dev/docs/modes/) . Status ------ [Section titled “Status”](https://skip.dev/docs/status/#status) Skip is stable and powers many production apps on the App Store and Play Store. Skip Fuse is a recent advance in Skip’s capabilities. We appreciate your [feedback](https://skip.dev/docs/help/) to continue to improve its functionality and tooling. Continue reading the Skip documentation to learn more. The best way to get help and connect with fellow Skip users is to join the community [Slack](https://skip.dev/slack/) . We also have forums for both general [discussions ↗](http://forums.skip.dev/) and specific [issues and bug reports ↗](https://source.skip.dev/skip/issues) . Footnotes --------- [Section titled “Footnotes”](https://skip.dev/docs/status/#footnote-label) 1. [_Transpilation_ ↗](https://en.wiktionary.org/wiki/transpile) is the process of converting one programming language into another. [↩](https://skip.dev/docs/status/#user-content-fnref-1) --- # Native Swift Tech Preview | Skip [Skip to content](https://skip.dev/docs/native/#_top) Native Swift Tech Preview ========================= Native Swift ============ [Section titled “Native Swift”](https://skip.dev/docs/native/#native-swift) Skip has long allowed developers to create cross-platform iOS and Android apps in Swift and SwiftUI by _transpiling_ your Swift to Android’s native Kotlin language. Now, Skip gives you the ability to use native, **compiled** Swift for cross-platform development as well. Skip’s native Swift support is a combination of: * A native Swift toolchain for Android. * Integration of Swift functionality like logging and networking with the Android operating system. * Swift API for accessing various Android services. * Bridging technology for using Kotlin/Java API from Swift, and for using Swift API from Kotlin/Java. * The ability to power Jetpack Compose and shared SwiftUI user interfaces with native Swift `@Observables`. * Xcode integration and tooling to build and deploy across both iOS and Android. Instructions for using native Swift have been integrated into the [Skip documentation](https://skip.dev/docs/) , starting with the chapter on [Native vs. Transpiled](https://skip.dev/docs/modes/) modes. Enjoy! --- # Stripe | Skip [Skip to content](https://skip.dev/docs/modules/skip-stripe/#_top) Stripe ====== This is a free Skip Swift/Kotlin framework that contains integration with the Stripe SDK’s ‘Mobile Payment Element for [iOS ↗](https://docs.stripe.com/sdks/ios) and [Android ↗](https://docs.stripe.com/sdks/android) . Setup ----- [Section titled “Setup”](https://skip.dev/docs/modules/skip-stripe/#setup) To include this framework in your project, add the following dependency to your `Package.swift` file: let package = Package( name: "my-package", products: [ .library(name: "MyProduct", targets: ["MyTarget"]), ], dependencies: [ .package(url: "https://source.skip.tools/skip-stripe.git", "0.0.0"..<"2.0.0"), ], targets: [ .target(name: "MyTarget", dependencies: [ .product(name: "SkipStripe", package: "skip-stripe") ]) ]) ### Usage [Section titled “Usage”](https://skip.dev/docs/modules/skip-stripe/#usage) The API includes a `StripePaymentButton` which is created using a `StripePaymentConfiguration`. For information on how to initialize the configuration object, see the Stripe documentation for [iOS ↗](https://docs.stripe.com/payments/accept-a-payment?platform=ios&ui=payment-sheet) and [Android ↗](https://docs.stripe.com/payments/accept-a-payment?platform=android&ui=payment-sheet) . The `SkiperStrip` sample app in this repository shows a complete Skip-to-native integration using: * `skip-stripe/examples/PaymentSheetService.swift` – wrapper around Stripe’s iOS payment sheet presenter. * `skip-stripe/examples/StripePaymentService.swift` – shared service that creates payment intents, ephemeral keys, and SkipStripe configurations for Android. * `skip-stripe/examples/ContentView.swift` – SwiftUI screen demonstrating both iOS and Android flows with `SimpleStripePaymentButton`. import SkipStripe struct PaymentButton: View { let paymentConfig = StripePaymentConfiguration( publishableKey: "PUBLISHABLE_KEY", merchantName: "Merchant, Inc.", customerID: "CUSTOMER_ID", ephemeralKeySecret: "EPHEMERAL_KEY_SECRET", clientSecret: "CLIENT_SECRET" ) var body: some View { StripePaymentButton(configuration: paymentConfig, completion: paymentCompletion) { Text("Pay Now!") } .buttonStyle(.borderedProminent) } func paymentCompletion(result: StripePaymentResult) { switch result { case .completed: logger.log("Payment completed") case .canceled: logger.log("Payment canceled") case .failed(error: let error): logger.log("Payment error: \(error)") } }} Usage Notes ----------- [Section titled “Usage Notes”](https://skip.dev/docs/modules/skip-stripe/#usage-notes) Using Stripe for payments is subject to the policies of the app marketplace that distributes the application. Generally speaking, they can be used for purchases and subscriptions for _non-digital_ goods, whereas digital purchses usually use the native billing system of the hosting service. For more details, see: * [Make in-app purchases in Android apps ↗](https://support.google.com/googleplay/answer/1061913) * [Understanding Google Play’s Payments policy ↗](https://support.google.com/googleplay/android-developer/answer/10281818) * [Apple App Review Guidelines: In-App Purchase ↗](https://developer.apple.com/app-store/review/guidelines/#in-app-purchase) * [Apple Human Interface Guidelines: In-app purchase ↗](https://developer.apple.com/design/human-interface-guidelines/in-app-purchase) For interfacing with the native in-app purchasing system on the host device, consider using the [SkipMarketplace](https://skip.dev/docs/modules/skip-marketplace) framework instead. Building -------- [Section titled “Building”](https://skip.dev/docs/modules/skip-stripe/#building) This project is a free Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin. Building the module requires that Skip be installed using [Homebrew ↗](https://brew.sh/) with `brew install skiptools/skip/skip`. This will also install the necessary build prerequisites: Kotlin, Gradle, and the Android build tools. Testing ------- [Section titled “Testing”](https://skip.dev/docs/modules/skip-stripe/#testing) The module can be tested using the standard `swift test` command or by running the test target for the macOS destination in Xcode, which will run the Swift tests as well as the transpiled Kotlin JUnit tests in the Robolectric Android simulation environment. Parity testing can be performed with `skip test`, which will output a table of the test results for both platforms. ---