Post

Replies

Boosts

Views

Activity

App Clip Keychain Access Fails with App Group, status -34018
We're developing our first App Clip, and having issues with accessing the keychain. At the moment, we've been running the App Clip from Xcode with the environment variable, _XCAppClipURL, set to a URL the app clip responds to. This is the failing call: var addquery: [CFString: Any] = [ kSecClass: kSecClassGenericPassword, kSecAttrLabel: "secretKey", kSecAttrAccount: userID] if let accessGroup = accessGroup { addquery[kSecAttrAccessGroup] = accessGroup } SecItemDelete(addquery as CFDictionary) addquery[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly addquery[kSecUseDataProtectionKeychain] = true addquery[kSecValueData] = key let status = SecItemAdd(addquery as CFDictionary, nil) guard status == errSecSuccess else { throw MercuryError(message: "Failed storing db encryption key: \(status)") } The status that is reported has the value -34018, which I haven't tried to compare to the massive list of constants. The trouble seems to be kSecAttrAccessGroup, to which we're our App Group value, e.g. group.com.bundle.different.string as shown below. Both full app and clip belong to this group. We've also tried setting kSecAttrAccessGroup to the keychain access group setup for the full app, but we can't seem to add a keychain sharing entitlement to the app clip... at least not in the UI. So we're unsure if setting the param to this is supposed to work in this case. If instead we avoid setting kSecAttrAccessGroup altogether, the call to SecItemAdd succeeds in the App Clip. The full app seems to be able to later access the stored key. But this is less safe, right? Given that previous versions of the full app are currently using the App Group for this call, changing it would be a disruption for our users. For completeness, the full app works with the app group passed in or not as always. Setup Stuff Full App Bundle ID is something like: com.bundle.ident App Clip has the default suffix added: com.bundle.ident.Clip App Clip Entitlements: <dict> <key>com.apple.developer.associated-domains</key> <array> <string>webcredentials:domain.com?mode=developer</string> <string>webcredentials:domain2.com?mode=developer</string> <string>applinks:domain.com?mode=developer</string> <string>applinks:domain2.com?mode=developer</string> </array> <key>com.apple.developer.parent-application-identifiers</key> <array> <string>$(AppIdentifierPrefix)com.bundle.ident</string> </array> <key>com.apple.security.application-groups</key> <array> <string>group.com.bundle.different.string</string> </array> </dict> I don't seem to be able to add the keychain-access-groups key to this one, but, given that we're scoping the keychain access to the App Group, it shouldn't matter(?). Relevant Full App Entitlements: <dict> <key>com.apple.developer.associated-domains</key> <array> <string>webcredentials:domain.com?mode=developer</string> <string>webcredentials:domain2.com?mode=developer</string> <string>applinks:domain.com?mode=developer</string> <string>applinks:domain2.com?mode=developer</string> </array> <key>com.apple.security.app-sandbox</key> <true/> <key>com.apple.security.application-groups</key> <array> <string>group.com.bundle.different.string</string> </array> <key>keychain-access-groups</key> <array> <string>$(AppIdentifierPrefix)com.bundle.third.string</string> </array> </dict> What's missing here is the com.apple.developer.associated-appclip-app-identifiers, but I understand that's added automatically when I archive, so that it shouldn't be relevant here.
3
1
560
Apr ’24
Universal Links not opening in app (in dev)
I believe I have everything configured correctly for Universal Links to be working, but every method I try to launch a link just takes me to safari. Any ideas would be appreciated! The app in question is not yet in the app store, but some previous versions are in TestFlight. Entitlements file: <plist version="1.0"> <dict> ... <key>com.apple.developer.associated-domains</key> <array> <string>webcredentials:o.s.com</string> <string>applinks:o.s.com</string> <string>webcredentials:p.d.org:3011?mode=developer</string> <string>applinks:p.d.org:3011?mode=developer</string> <string>applinks:p.d.org?mode=developer</string> </array> p.d.org:3011 (not the real url) is a public DNS record that resolves to the LAN IP of my macbook where a dev instance of our server is running with a valid TLS certificate from Let's Encrypt. o.s.com is the url of the our staging environment, which some TestFlight builds connect to. The AASA file served by both servers is currently a combination of both old and new formats, but I originally just had what I believe to be the new format (from here): { "applinks": { "apps": [], "details": [{ "appIDs": [ "the.real.app.id", // <--- redacted "the.second.app.id" ], "components": [{ "?": "no_universal_link", "exclude": true, "comment": "Exclude any URL with no_universal_link in the query params" }, { "/": "/group/browse", "exclude": true, "comment": "Exclude group browse list" }, { "/": "/invited*", "comment": "Match invited link" }] }, { "appID:": "the.real.app.id", "paths": ["/invited*", "/group/*"] }, { "appID:": "the.second.app.id", "paths": ["/invited*", "/group/*"] }] }, "webcredentials": { "apps": [ "the.real.app.id", "the.second.app.id" ] }, "activitycontinuation": { "apps": [ "the.real.app.id", "the.second.app.id" ] } } I have two phones and an iPad I test on, one phone with the iOS beta installed (currently 15.5 beta 4) and one phone + the iPad with the current public iOS (15.4.1). On all devices, Settings->Developer->Associated Domains Development is ON. Upon install of a Debug build of the.real.app.id, I confirmed that /.well-known/apple-app-site-association was fetched from my local dev server. Settings->Developer->Universal Links->Diagnostics shows the following for https://p.d.org:3011: for https://o.s.com, though, it show a problem, which I'm assuming is due to the debug build not matching a url with ?mode=developer: In the AppDelegate, I've implemented: func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {   logger.info("continue user activity: \(userActivity.activityType, privacy: .public)") return true } and in the SwiftUI App subclass, I also have: var body: some Scene {   WindowGroup {    MainContentView()     .onContinueUserActivity(NSUserActivityTypeBrowsingWeb, perform: launchUrl(userActivity:))     .onOpenURL { url in      print("On open url: \(url)")     }   }  } func launchUrl(userActivity: NSUserActivity) { logger.info("launchUrl continue user activity: \(userActivity.activityType, privacy: .public)") } I've sent myself messages with links to p.d.org:3011 in a number of apps, including iMessage. I've rendered a link into a QR code for use in the camera app. I've even built a second app into which I've hard coded the link to ensure that it calls UIApplication.shared.open(url, options: [:], completionHandler: nil). All of these methods take me to the browser (safari). For completeness, I'm using the link: https://p.d.org:3011/invited?id=... When the web opens, a blank app preview banner appears sometimes. I expect that it's blank because the app in question hasn't been publicly released to the app store yet. The landing page doesn't have enough content to scroll the page, and pulling down on the page doesn't reveal the app preview banner when the banner doesn't show. The logging statements in the app never print. I've also tried building both Ad Hoc and TestFlight versions of the app for o.s.com. Server logs similarly show the AASA being fetched. But the Diagnostics in Settings show the yellow triangle screen for both p.d.org:3011 and o.s.com. I'm not sure what I'm doing wrong here, and I can't find any more information on the web. Can someone help me out ...or put me out of my misery?
9
2
16k
May ’22
Bound preference _ tried to update multiple times per frame.
I'm running into this error in the title, but perhaps there's a better solution for the problem I have. There's too much code to post... but I have a parent view that shows a list of child views: var body: some View { ScrollView(.vertical) {     VStack(spacing: 0.0) {        ForEach(self.messagesModel.messages, id: \.self) { msg in            MessageView(msg: msg)                   }    } } } But some and only some of the MessageView's are a UIViewRepresentable wrapping a WKWebView, which means in order to determine their height, I have to let them render to the screen, wait for them to settle, and then inspect their needed size. We don't want the user watching the view jumping around as this is happening, so we wrap the above ScrollView in a ZStack and plaster an annoying progressView over top of the ScrollView. It'd also be nice to automatically scroll the ScrollView to an appropriate MessageView, and we need to wait for the scrollview's size to stop changing for that to be effective. Both of these actions require the parent view to be informed when all (if any) of the child views have finally settled down. The standard way of communicating this information from the child up to the parent is apparently a PreferenceKey. So we push the UIViewRepresentable one layer deeper, add a callback to be informed when the rendering is done, then update the preference: struct MessageView: View { @State var isWebViewRendering: Int @State var myHeight: CGFloat init (...) { _isWebViewRendering = State(initialValue: 1) } var body: some View { MessageWebView(...) { height in self.myHeight = height self.isWebViewRendering = 0 }.preference(key: RenderedPreferenceKey.self, value: isWebViewRendering)       .frame(width: nil, height: myHeight, alignment: .top) } } struct RenderedPreferenceKey: PreferenceKey {  static var defaultValue: Int = 0  static func reduce(value: inout Int, nextValue: () -> Int) {   value = value + nextValue() // sum all those remain to-be-rendered  } } And then the parent adds the onPreferenceChange: var body: some View { ZStack { ScrollView(.vertical) {      VStack(spacing: 0.0) {         ForEach(self.messagesModel.messages, id: \.self) { msg in            MessageView(msg: msg)                    }    }.onPreferenceChange(RenderedPreferenceKey.self) { remainingToBeRendered in     guard remainingToBeRendered == 0 else { return } // all the fun UI behaviors can go now    } } TwiddleYourThumbsForSwiftUI()... } } The preference change is fired numerous times on the count down to zero, but the Bound preference RenderedPreferenceKey tried to update multiple times per frame error stops it from firing. This is fine unless the error occurs during the final execution when the sum of all the preferences would be zero. Any ideas what to do? I have ensured that the callback from MessageWebView is only called once per view and that all callbacks are properly dispatched on the main thread as needed, so that it seems like multiple views are publishing to the preference key "simultaneously" and causing this problem. But it also seems like it's designed for that...so not sure what the issue is. I have considered trying to pass this responsibility to the view models governing this, but it's such a strictly UI related function that this feels most appropriate. But, if anyone thinks that's the better solution, I'm game. I'd have to capture the ScrollReader and pass it out to the model to make it work, though. At every turn, I wished I had stuck with UIKit. Everything feels like a hack in SwiftUI. I also considered making the parent view a single WKWebView, allowing for only a single preference update, but with the amount of interaction needed with each message, React Native or Cordova would have made more sense, and nobody wants that.
0
1
2.3k
Jan ’22
Returning async result
Is it possible to return the async task result from some calling method without awaiting the task or having the calling method be async itself? e.g. func outerWrapper<S>(arg1: Any, arg2: String = "ignore me") -> AsyncTask<S> { return theAsyncFunc(arg1: arg1, arg2: arg2) } func theAsyncFunc<S>(arg1: Any, arg2: Any) async throws -> S { ... } This kind of thing is common in JS where a function can forward up the Promise returned by an async function without the overhead of awaiting it and fulfilling its own promise.
6
0
2.9k
Dec ’21