In the Apple developer docs chapter "There and Back Again" the watch app's App is written like this:
#main
struct MyWatchApp: App {
#WKExtensionDelegateAdaptor(ExtensionDelegate.self) var extensionDelegate
#SceneBuilder var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
}
}
}
Unfortunately I get a purple runtime warning on my var declaration that says
#WKExtensionDelegateAdaptor should only be used within an extension based process
There must be something in Xcode that explicitly defines the App structure as "extension-based" but I can't find it!
Edit: More clarification... I am trying to handle the special method that gets called after you run the HealthKit method startWatchApp(with:completion:)
The special method for watch extensions is func handle(_ workoutConfiguration: HKWorkoutConfiguration)
I cannot seem to find a way to link this function on the new App structure for watch apps.
Ok, I found it. The solution is to simply replace WKExtensionDelegate with the new WKApplicationDelegate!
You can try this solution, it is simple and universal.
import WatchKit
class ExtensionDelegate: NSObject, WKExtensionDelegate {
static var current: ExtensionDelegate? {
return (WKExtension.shared().delegate as? ExtensionDelegate)
}
...
}
Then access this current instance like below, because it is Singleton, you can access it in the entire project scope.
ExtensionDelegate.current?.dosomething()
Related
I've faced this issue on one of my old projects and I'm sure that there were no runtime issues back then: (current Xcode: 14.1)
The Style is global:
#main
struct TestingApp: App {
var body: some Scene {
WindowGroup {
TestView().environmentObject(Style()) // <- Here
}
}
}
Each page may has access to the style through the EnvironmentObject:
struct TestView: View {
#EnvironmentObject private var style: Style
public var body: some View {
Text("Hello World")
.padding(style.size.inputPadding) // <- Run-time issue on this line
}
}
And the Style itself declared like this:
class Style: ObservableObject {
#Published var size = Size()
}
struct Size {
#ScaledMetric(relativeTo: .body) var inputPadding: CGFloat = 8
}
I'm getting this run-time issue:
Accessing Environment<CGFloat>'s value outside of being installed on a View. This will always read the default value and will not update.
and
Accessing Environment<DynamicTypeSize>'s value outside of being installed on a View. This will always read the default value and will not update.
Can someone explain what is happening and why this is considered as ACCESSING value OUTSIDE of being installed on a View ?
They key part of the issue is where the wrapper is "installed"
#ScaledMetric
Doesn’t work in a regular struct because it conforms to DynamicProperty
https://developer.apple.com/documentation/swiftui/scaledmetric
DynamicProperty
The view gives values to these properties prior to recomputing the view’s body.
https://developer.apple.com/documentation/swiftui/dynamicproperty
Your Size struct isn’t a View so it can’t get values. In prior versions you would get an initial value and some updates but it was a bug, SwiftUI just started warning about the issue.
This is why most SwiftUI wrappers only work in views.
If you remove the wrapper from the Size struct the warnings will go away.
In watching the WWDC 21 videos reference StoreKit 2, there are a few functions that they reference wherein they let a value = WindowScene as follows:
func manageSubscriptions() async {
if let windowScene = self.view.window?.windowScene {
do {
try await AppStore.showManageSubscriptions(in: windowScene)
} catch {
//error
}
}
}
The let line errors out with the message: Type of expression is ambiguous without more context
If I try and provide more context with something like:
if let windowScene = (self.view.window?.windowScene)! as UIWindowScene {
I am told: Value of type 'MyStruct' has no member 'view'
What am I missing, must be something simple, to gain access to this needed UI element?
Thank you
Added:
I'd like to add that I am using a SwiftUI app that was created using a SceneDelegate and AppDelegate, not a simple struct: App, type of structure. So I am guessing I need to access something in the SceneDelegate to get the right object..
Just to provide an answer for anyone interested, with all credit to #aheze for finding it and #Matteo Pacini for the solution, to get this specific method to work when using a SwiftUI app that has an AppDelegate/SceneDelegate structure, this will work:
#MainActor
func manageSubscriptions() async {
if let windowScene = UIApplication.shared.connectedScenes.first {
do {
try await AppStore.showManageSubscriptions(in: windowScene as! UIWindowScene)
} catch {
//error
}
}
}
You can conversely use the view modifier manageSubscriptionsSheet(isPresented:) instead. This is Apple's recommended approach when using SwiftUI and will mitigate the need for getting a reference to the window scene.
Source:
If you’re using SwiftUI, call the manageSubscriptionsSheet(isPresented:)view modifier.
I am writing an iOS App (using xcode 7.3 and swift 2.2) using JavascriptCode framework. Calling javascript methods from swift works perfect, but when I call the swift method from javascript, xcode simply shows a "loading" type of symbol and nothing happens. I need to "force quit" xcode to get out of this state.
I have followed https://www.raywenderlich.com/124075/javascriptcore-tutorial and http://nshipster.com/javascriptcore/ and I am trying pretty simple calls.
Has anyone faced this kind of issue?
My swift code is as follows:
#objc protocol WindowJSExports : JSExport {
var name: String { get set }
func getName() -> String
static func createWindowWithName(name: String) -> WindowJS
}
#objc class WindowJS : NSObject, WindowJSExports {
dynamic var name: String
init(name: String) {
self.name = name
}
class func createWindowWithName(name: String) -> WindowJS {
return WindowJS(name: name)
}
func getName() -> String {
NSLog("getName called from JS context")
return "\(name)"
}
}
I am initializing the context as follows:
runContext = JSContext()
runContext.name = "test_Context"
windowToJs = WindowJS(name: "test")
runContext.setObject(windowToJs.self, forKeyedSubscript: "WindowJS")
If I replace the last two lines in above code with below code without instantiating it, the code simply fails to load.
runContext.setObject(WindowJS.self, forKeyedSubscript: "WindowJS")
And the javascript code is as simple as
function check() {
return WindowJS.getName()
}
I do see the breakpoint being hit in the JS function check and when the WindowJS.getName gets called, xcode simply becomes unresponsive.
The setTimeout could be solved by adding following piece of code to my swift function.
let setTimeout: #convention(block) (JSValue, Int) -> () =
{ callback, timeout in
let timeVal = Int64(timeout)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeVal), dispatch_get_main_queue(), { callback.callWithArguments(nil)})
}
To expose this native code to the JS context, I also added following.
runContext.setObject(unsafeBitCast(setTimeout, AnyObject.self), forKeyedSubscript: "setTimeout")
Things then worked fine.
You're creating a deadlock since you are calling from Swift to JavaScript back to Swift. I'm not sure exactly why it is a deadlock but I had a similar issue with WKWebView on Mac recently.
You need to decouple this and make the communication asynchronous. This obviously means you cannot simply return a value from your JS function in this case.
To decouple, you can break the deadlock by deferring the work the JavaScript function needs to do out of the current runloop iteration using setTimeout:
function myFunction() {
setTimeout(function() {
// The actual work is done here.
// Call the Swift part here.
}, 0);
}
The whole native ↔︎ JavaScript communication is very, very tricky. Avoid it if you can. There's a project called XWebView that may be able to help you as it tries to ease bridging between the two worlds.
I'm not clear on how to use this properly but had seen other people doing this type of thing:
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
manager.sharedInstance.backgroundCompletionHandler = completionHandler
}
In our similar implementation, at this point completionHandler is partial apply forwarder for reabstraction thunk helper...
Where manager is (despite being a singleton) essentially:
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.ourcompany.app")
let manager = Alamofire.Manager(configuration: configuration)
However this causes the following warning to be printed in the console:
Warning: Application delegate received call to -application:handleEventsForBackgroundURLSession:completionHandler: but the completion handler was never called.
I set a breakpoint here and at this point the message is already visible in the console and backgroundCompletionHandler is nil.
We're building against the iOS 9 SDK with Xcode 7.0 and currently using Alamofire 2.0.2
I originally thought this was introduced when we merged our Swift 2.0 branch but I'm also seeing the message with an earlier commit using Xcode 6.4 against the iOS 8 SDK.
Update 1
To address #cnoon's suggestions:
The identifier matches - the configuration and manager are set inside a singleton so there's no way for it to be wrong.
When adding and printing inside of didSet on backgroundCompletionHandler in the Manager class, the message is logged before the warning.
When printing inside of the closure set to sessionDidFinishEventsForBackgroundURLSession on the delegate inside the Manager class, the message is printed after the warning.
When overriding sessionDidFinishEventsForBackgroundURLSession and printing inside of it before calling backgroundCompletionHandler, the message is printed after the warning.
As for verifying I have my Xcode project set up correctly for background sessions, I'm not sure how to do that and couldn't find any documentation on how to do so.
I should note that when trying to upload some screenshots from my phone I was initially unable to reproduce this issue in order to try these suggestions.
It was only after trying to share some photos that I was able to reproduce this again. I'm not sure or the correlation (if any) but it may be related to the photos taking longer to upload.
Update 2
The UIBackgroundModes are set exactly as #Nick described, and calling completionHandler() directly inside of application:handleEventsForBackgroundURLSession:completionHandler: does not display the warning.
Update 3
So, it appears I overlooked an small but important detail. There's a wrapper around Alamofire.Manager that doesn't expose it directly. The relevant part of its implementation looks like this:
private var manager: Manager
public var backgroundCompletionHandler: (() -> Void)? {
get {
return manager.backgroundCompletionHandler
}
set {
manager.backgroundCompletionHandler = backgroundCompletionHandler
}
}
and setting the completion handler in the AppDelegate executes that code path.
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
myManager.sharedInstance.backgroundCompletionHandler = completionHandler
}
I've confirmed that the following change to expose the instance of Alamofire.Manager and access it directly does not produce the warning:
public var manager: Manager
// public var backgroundCompletionHandler: (() -> Void)? {
// get {
// return manager.backgroundCompletionHandler
// }
// set {
// manager.backgroundCompletionHandler = backgroundCompletionHandler
// }
// }
and in the AppDelegate:
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
myManager.sharedInstance.manager.backgroundCompletionHandler = completionHandler
}
Based on this it appears that using a computed property to proxy the completion handler is the cause of the issue.
I'd really prefer not to expose this property and would love to know of any workarounds or alternatives.
It appears as though everything you are doing is correct. I have an example app that does exactly what you've described that works correctly and does not throw the warning you are seeing. I'm guessing you still have some small error somewhere. Here are a few ideas to try out:
Verify the identifier matches the identifier of your background session
Add a didSet log statement on the backgroundSessionHandler in the Manager class temporarily to verify it is getting set
Add a log statement into the sessionDidFinishEventsForBackgroundURLSession to verify it is getting called as expected
Override the sessionDidFinishEventsForBackgroundURLSession on the delegate and manually call the backgroundSessionHandler
Verify you have your Xcode project set up correctly for background sessions
Update 2
Your computed property is wrong. Instead it needs to set the backgroundCompletionHandler to newValue. Otherwise you are never setting it to the new value correctly. See this thread for more info.
I don't know if this is still a bug inherent in Swift, or if I'm getting something wrong, but here goes.
I have a class for creating menus (like a main menu and an options menu) in an iOS app, coded in Swift.
public class GameMenuScreen {
private var _labels: Dictionary<String, SKLabelNode>
// Getter/Setter indirection
internal var labels: Dictionary<String, SKLabelNode> {
get {
return self._labels
}
}
...
}
class MainMenu: GameMenuScreen {
...
}
My issue is that labels is inherited by MainMenu, but when I attempt to access MainMenu.labels in a method, it throws EXC_BAD_ACCESS.
My thinking is that because the call to MainMenu.labels is sent to GameMenuScreen, the return value is GameMenuScreen._labels, not MainMenu._labels, and because there is no instance of GameMenuScreen, accessing GameMenuScreen._labels accesses unallocated memory.
I'd like to know how I structure this such that any call to labels refers to self._labels and not super._labels.
Running Xcode 6.0.1
I think you would access an instance of it as MainMenu().whatever. You need the (). Not sure if that is your problem without seeing the full code. Can you post more?