iPad attempting to import Journaling Suggestions

I am using #canImport(JournalingSuggestions), but something is going wrong and my app is attempting to import the framework on iPad, and crashing on launch. How can I ensure that it's properly filtered out from everything except iPhone?

import SwiftUI
#if canImport(JournalingSuggestions)
import JournalingSuggestions
#endif

struct JournalingSuggestionsView: View {
    var body: some View {
        #if canImport(JournalingSuggestions)
            JournalingSuggestionsPicker {
                Text("Open Journaling Suggestions")
            } onCompletion: { suggestion in
                print(suggestion)
            }
        #else
            Text("Journaling suggestions not available on this platform.")
        #endif
    }
}

Error:

dyld[8689]: Library not loaded: /System/Library/Frameworks/JournalingSuggestions.framework/JournalingSuggestions
  Referenced from: <A656E6BC-4883-3245-BE71-3F84C2F41119> /private/var/containers/Bundle/Application/C6C11F57-AFAA-442A-B726-7AADDDB50D79/Catalog.app/Catalog
  Reason: tried: '/System/Library/Frameworks/JournalingSuggestions.framework/JournalingSuggestions' (no such file), '/private/preboot/Cryptexes/OS/System/Library/Frameworks/JournalingSuggestions.framework/JournalingSuggestions' (no such file), '/System/Library/Frameworks/JournalingSuggestions.framework/JournalingSuggestions' (no such file, not in dyld cache)

System info: Xcode 15.2 iPadOS 17.3.1

Answered by Vision Pro Engineer in 784514022

As an update to @eskimo 's response,

There's a few cases here:

  • Case #1: Building for the simulator.
  • Case #2: Building for a real device, but running on a device where the framework isn't present (eg: iPad, Mac)
  • Case #3: Building for an iOS version before iOS 17.2 where the framework isn't available

For case #1: use #if canImport(JournalingSuggestions) like this:

#if canImport(JournalingSuggestions)
    import JournalingSuggestions
#endif

Then, in your code where you use JournalingSuggestionsPicker, check if you can import it first. This way, if you're not building for a real device, the framework will not be imported.

For case #2: Since you're building for a real device, the framework can be imported. Since it's not an iPhone, your app will crash when run. Protect against this by first checking if you can import UIKit, then setting isJournalingSuggestionsAvailable to be true if you're on an iPhone. If you're on Mac, where UIKit can't be imported, isJournalingSuggestionsAvailable will be false anyways. For example:

#if canImport(UIKit)
import UIKit
let isJournalingSuggestionsAvailable = UIDevice.current.userInterfaceIdiom == .phone
#else
let isJournalingSuggestionsAvailable = false
#endif

In your code, where you use JournalingSuggestionsPicker, first check if you can import it with the info in case #1 above, then check if isJournalingSuggestionsAvailable is true.

For case #3: To handle the older iOS case, you need if #available(iOS 17.2, *)

Altogether, it looks like this in code:

#if canImport(JournalingSuggestions)
    if isJournalingSuggestionsAvailable {
        if #available(iOS 17.2, *) {
            JournalingSuggestionsPicker {
                Text("Choose a suggestion")
            } onCompletion: { suggestion in
                print("suggestion:", suggestion)
            }
        } else {
            Text("Requires iOS 17.2.")
        }
    } else {
        Text("Dynamically not available on this platform.")
    }
#else
    Text("Statically not available on this platform.")
#endif
}

Important - iPad weak linking

To ensure your app can run on iPad without crashing, you must weak link to the framework. The normal way to do this is via the Status column in the Link Binary With Libraries build phase. That doesn’t work here because the framework isn’t available on the iOS Simulator platform, so the simulator build fails, as @bryan1anderson pointed out. The workaround is to pass in -Xlinker -weak_framework -Xlinker JournalingSuggestions via the Other Linker Flags build setting.

Accepted Answer

You need to weak link to the framework. To do this:

  1. In your app’s target editor, switch to the General tab and add the Journaling Suggestions framework to the Frameworks, Libraries, and Embedded Content list.

  2. Switch to the Build Phases tab and, in the Link Binary with Libraries phase, switch the Status popup to Optional.

This shouldn’t be this painful, and we have a bug on file about that (r. 121326851).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

(I had some weird UI issue in the forums when adding this before, so I might be duplicating this reply).

Thank you, Quinn! That fixed my crash. Lastly, I will still need to somehow differentiate between iPhone and iPad to hide UI. Is the UIDevice idiom still the recommended approach for that? (I'm shaking off the rust after several years of not developing for Apple platforms).

Also, would it be helpful to add any of these details to new radar or would that be unnecessarily duplicating the one you posted?

Appreciate your help.

This did not fix it for me. I did all of this. Now I can't even build on simulator.

As an update to @eskimo 's response,

There's a few cases here:

  • Case #1: Building for the simulator.
  • Case #2: Building for a real device, but running on a device where the framework isn't present (eg: iPad, Mac)
  • Case #3: Building for an iOS version before iOS 17.2 where the framework isn't available

For case #1: use #if canImport(JournalingSuggestions) like this:

#if canImport(JournalingSuggestions)
    import JournalingSuggestions
#endif

Then, in your code where you use JournalingSuggestionsPicker, check if you can import it first. This way, if you're not building for a real device, the framework will not be imported.

For case #2: Since you're building for a real device, the framework can be imported. Since it's not an iPhone, your app will crash when run. Protect against this by first checking if you can import UIKit, then setting isJournalingSuggestionsAvailable to be true if you're on an iPhone. If you're on Mac, where UIKit can't be imported, isJournalingSuggestionsAvailable will be false anyways. For example:

#if canImport(UIKit)
import UIKit
let isJournalingSuggestionsAvailable = UIDevice.current.userInterfaceIdiom == .phone
#else
let isJournalingSuggestionsAvailable = false
#endif

In your code, where you use JournalingSuggestionsPicker, first check if you can import it with the info in case #1 above, then check if isJournalingSuggestionsAvailable is true.

For case #3: To handle the older iOS case, you need if #available(iOS 17.2, *)

Altogether, it looks like this in code:

#if canImport(JournalingSuggestions)
    if isJournalingSuggestionsAvailable {
        if #available(iOS 17.2, *) {
            JournalingSuggestionsPicker {
                Text("Choose a suggestion")
            } onCompletion: { suggestion in
                print("suggestion:", suggestion)
            }
        } else {
            Text("Requires iOS 17.2.")
        }
    } else {
        Text("Dynamically not available on this platform.")
    }
#else
    Text("Statically not available on this platform.")
#endif
}

Important - iPad weak linking

To ensure your app can run on iPad without crashing, you must weak link to the framework. The normal way to do this is via the Status column in the Link Binary With Libraries build phase. That doesn’t work here because the framework isn’t available on the iOS Simulator platform, so the simulator build fails, as @bryan1anderson pointed out. The workaround is to pass in -Xlinker -weak_framework -Xlinker JournalingSuggestions via the Other Linker Flags build setting.

This did not fix it @sha921 @eskimo

#if canImport(JournalingSuggestions) just does not work. The code inside of it always runs. It doesn't matter if I mark the framework as optional in build phases. I doesn't matter if I provide the manual linker flag.

Anything inside #if canImport(JournalingSuggestions) still executes on iPads.

This is BEYOND problematic because it doesn't MATTER if I use the device idiom to check if it's on an iPad or a phone. Because SwiftUI runs that code no matter what. The binary will always crash because #if canImport(JournalingSuggestions) does not block execution.

And besides, the linker flags solution DID NOT fix the simulator issue. Same exact issue. This is a massive problem.

#if canImport(JournalingSuggestions)

                                if true == false {
                                    //OBVIOUSLY this case will never "run" but SwiftUI still checks it, and therefore crashes the app
                                    //THIS STILL CAUSES A CRASH ON IPAD, if I want it to run i need to comment out.
                                    JournalingSuggestionsPicker {
                                                   Text("Choose a suggestion")
                                               } onCompletion: { suggestion in
                                                   print("suggestion:", suggestion)
                                               }
                                } else {
                                    //Only getting here if I comment out JournalingSuggestions code cause CRASH
                                    Text("Would see this on iPad if I used the device idiom check instead of true == false")
                                }
#else
        Text("Only see this on simulator, when I don't link weakly, otherwise i cannot build to simulator no matter what")
#endif

This did not fix it

You have to conditionalise the Other Linker Flags build setting based on whether you’re building for the simulator or not. Here’s what that looks like in Xcode:


#if canImport(JournalingSuggestions) just does not work.

You need all three checks here:

  • #if canImport(JournalingSuggestions)

  • if isJournalingSuggestionsAvailable

  • if #available(iOS 17.2, *)

Try using the code that sha921 posted.


This is a massive problem.

You’ll get no argument from me on that front! As I mentioned earlier, we have a bug on file requesting improvements in this space (r. 121326851).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

same with me, all provided solutions do not work

Failed to look up symbolic reference at 0x10460f095 - offset 3979 - symbol symbolic _____y_____y_____yACy__________y_____GG_____y_____GG___________y_____yAMGAMGtGG 7SwiftUI6VStackV AA9TupleViewV AA15ModifiedContentV AA5ImageV AA30_EnvironmentKeyWritingModifierV AI5ScaleO AA016_ForegroundStyleL0V AA09TintShapeO0V AA4TextV AA012_ConditionalG0V 21JournalingSuggestions0tU6PickerV in /private/var/folders/dg/m7p8ljs52vv5phhm44r9bb7h0000gn/X/92886C6C-BD41-5654-A4AC-0B95A4D04BC7/d/Wrapper/JournalingSuggestionsCrash1.app/JournalingSuggestionsCrash1.debug.dylib

I was able to narrow it down to ViewBuilders. Here is a sample project. I'm able to fix this by wrapping this inside a non view builder type erased view.

MESSY

This won't let me upload the zip file..

import SwiftUI
#if canImport(JournalingSuggestions)
import JournalingSuggestions
#endif

struct ContentView: View {
    
    var willNotCrash: AnyView {
#if canImport(JournalingSuggestions)
            if true == false {
                //should never run, but still crashes
                return AnyView(JournalingSuggestionsPicker("Picker") { suggestion in
                    
                })
            } else {
                return AnyView(Text("Should always show"))
            }
#else
            return AnyView(Text("Should show on ipad"))
#endif

    }
    
    @ViewBuilder var willCrash: some View {
#if canImport(JournalingSuggestions)
        if true == false {
            JournalingSuggestionsPicker("Picker") { suggestion in
                
            }
        } else {
            Text("Should always show")    
        }
#else
        Text("Should show on ipad")
#endif

    }
    
    var body: some View {
        VStack {
            
//            willCrash
            
            willNotCrash
        }
        .padding()
    }
}

Hi @bryan1anderson and @aleksandr_kulaga ,

Here's steps to conditionalize this correctly in your build settings:

  1. Open build settings for your Xcode project's target
  2. Make sure "All" and "Levels" are selected at the top
  3. Search for "Other linker flags" in the search bar
  4. Expand the "Other linker flags" settings
  5. For "Debug", add a condition for "iOS" by pressing the "plus" button to the right of the word.
  6. Do the same for "Release"
  • both "Debug" and "Release" should now be expanded with "Any iOS SDK" as the conditional for each
  1. In the empty box under your app's name on the right of the "Any iOS SDK" for "Debug" setting, double click to open the add/remove box.
  2. Press the plus button, and add the following four statements on their own lines. You will then have four entries

-Xlinker

weak_framework

-Xlinker

JournalingSuggestions

  1. Now do the exact same thing for the box at the right of the "Any iOS SDK" for "Release setting"

Your ContentView should look like this:

//
//  ContentView.swift

import SwiftUI
#if canImport(JournalingSuggestions)
    import JournalingSuggestions
#endif

#if canImport(UIKit)
    import UIKit
    let isJournalingSuggestionsAvailable = UIDevice.current.userInterfaceIdiom == .phone
#else
    let isJournalingSuggestionsAvailable = false
#endif


struct ContentView: View {
    var body: some View {
        VStack {
#if canImport(JournalingSuggestions)
    if isJournalingSuggestionsAvailable {
        if #available(iOS 17.2, *) {
            JournalingSuggestionsPicker {
                Text("Open suggestions")
            } onCompletion: { suggestion in
                print("suggestion:", suggestion)
            }
        } else {
            Text("Requires iOS 17.2.")
        }
    } else {
        Text("Dynamically not available on this platform.")
    }
#else
    Text("Statically not available on this platform.")
#endif
}
        .padding()
    }
}

Try running your app in the simulator on an iPhone and it will work.

Nah that doesn't fix it

This came up in another thread so I wanted to confirm that things continue to work. Here’s what I did:

  1. Using Xcode 16.1 on macOS 14.7, I created a new test project from the iOS > App template, choosing SwiftUI for the interface.

  2. In the General tab of the target editor, I removed Mac and Apple Vision from Supported Destinations list. We’re focusing on iPhone and iPads here.

  3. In that same tab, in the Minimum Deployments section, I selected iOS 15. This resulting in a deployment target of iOS 15.6.

  4. In the Signing & Capabilities tab, I added the Journaling Suggestions capability.

  5. I replaced the code from ContentView.swift with the code from my colleague’s earlier post.

  6. I started building and running the app on a variety of destinations. The table below summarises my results:

Type                Device?     OS      Result
----                ----        --      ------
iPhone 11           device      15.6    Requires iOS 17.2.
iPhone 16           device      18.1    Open suggestions
iPad Pro (9.7-inch) device      16.7.8  Dynamically not available on this platform.
iPad Air (5th gen)  device      17.6.1  Dynamically not available on this platform.

iPhone 11           simulator   16.4    Statically not available on this platform.
iPhone 16           simulator   18.1    Statically not available on this platform.
iPad Pro (9.7-inch) simulator   16.4    Statically not available on this platform.
iPad Air (5th gen)  simulator   17.5    Statically not available on this platform.

So:

  • I didn’t encounter any build problems.

  • The results were exactly what I’d expect.

  • You should thank my boss for buying me a sufficiently kick **** Mac to run all of those simulators at once without breaking into a sweat (-:

One thing to note here is that I didn’t need to use the conditional build setting trick described earlier. That’s because this project has autolinking enabled, and that seems to do the right thing in all cases [1].

If you continue to have problems with this, please run through the exact steps above [2] to confirm that they work for you. Presuming that they do, compare the setup of your main project with the setup from your test project to see what’s different.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Specifically, in the simulator case I never end up importing the Journaling Suggestions framework and thus I don’t need to link to it.

[2] Well, feel free to use macOS 15 and any variant of Xcode 16, because those changes shouldn’t affect the test.

iPad attempting to import Journaling Suggestions
 
 
Q