Accessing NSWindow in Mac Catalyst - ios

I am aware that NSWindow is not officially available in Mac Catalyst but I have seen several posts and success stories accessing it via NSClassFromString and valueForKeyPath. However, I have never been able to get this to work, as it always returns nil.
guard let nsWindows = NSClassFromString("NSApplication")?.value(forKeyPath: "sharedApplication.windows") as? [AnyObject] else {
return nil
}
Have also attempted in objective c with
NSArray *const nsWindows = [NSClassFromString(#"NSApplication") valueForKeyPath:#"sharedApplication.windows"];
But the same result - nil - every time.
Is there a trick to getting this to work, or has this way been shut down as a potential way to access it? It successfully can get the NSApplication instance. But the windows property is always nil.

This can be done easily with the help of Dynamic:
extension UIWindow {
var nsWindow: NSObject? {
var nsWindow = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(self)
if #available(macOS 11, *) {
nsWindow = nsWindow.attachedWindow
}
return nsWindow.asObject
}
}

Related

Compile-time test for availability of UIApplication.shared?

I have some shared code that needs to work in both iOS apps and app extensions, and needs to set UIApplication.shared.isNetworkActivityIndicatorVisible — but only if the code is used in an app.
In an app extension, UIApplication.shared gives this compile error:
'shared' is unavailable: Use view controller based solutions where appropriate instead
That’s fine; I don’t want to use it in the app extension. However, I’m unable to find a way to disable that code at compile time. Sadly, if #available doesn’t seem to do the trick; it shuts off the code path, but the compiler still doesn’t like it:
if #available(iOSApplicationExtension 0, *) {
print("This is an extension")
} else {
print("This is an app")
print(UIApplication.shared) // Unreachable in extension, but still doesn’t compile
}
I don’t see any #if check that handles this.
Is there any way in Swift to conditionally compile the code that requires UIApplication.shared?
A possible solution is to avoid explicit usage of UIApplication.shared and use Objective-C selector wrap instead.
Here is an extension that might help (based on https://github.com/ephread/Instructions/issues/21)
extension UIApplication {
static var safeShared: UIApplication? {
guard UIApplication.responds(to: Selector(("sharedApplication"))) else {
return nil
}
guard let unmanagedSharedApplication = UIApplication.perform(Selector(("sharedApplication"))) else {
return nil
}
return unmanagedSharedApplication.takeRetainedValue() as? UIApplication
}
}
Usage
if let app = UIApplication.safeShared {
result = app.applicationState == .active
}
Happy Coding 👨‍💻

Memory leaks Xcode 8 instrument

I do nothing on my view controller and I see the graph that is changing while app is working.
Are this really leaks?
First time it show green check mark then it show 1 leak and then no new leaks.
So it means that there are no leaks or there are still 1 leak but not new ones?
I have actually app delegate which contains strong reference of manager.
class AppDelegate {
var applicationManager = ApplicationManager()
}
and I have few services in ApplicationManager
class ApplicationManager
{
lazy var apiService: APIService = {
let service = APIService()
return service
}()
lazy var facebookService: FacebookService = {
let service = FacebookService()
return service
}()
}
I have a function
func logInUser()
{
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
fatalError()
}
let apiService = appDelegate.applicationManager.apiService
guard let email = emailTextField.text, let password = passwordTextField.text else {
return
}
apiService.loginUserWith(email, password: password) {(result) in
}
could this code leads problems?
Using new tools I also see
that I have one leaked object (it's in another previous project) but with the same implementation of manager and services.
I'm going to suggest that there is in fact no leak. The Xcode 8.2 release notes say:
The Memory Debugger for macOS and the iOS Simulator fixes reporting of false memory leaks for Swift classes containing either fields of type enum, or classes that inherit from certain Objective-C framework classes. (27932061)
You are using Xcode 8.1, so we know that there is "reporting of false memory leaks for Swift classes" in this version (though under what precise circumstances, and whether the bug is completely fixed even in Xcode 8.2, remains unclear to me).
Moreover, I downloaded your github example project and ran it in Xcode 8.2.1 and saw no leak reported, neither in Instruments nor in the memory graph. Here's the Instruments output:

Difficulty reading UserDefaults with Fastlane Snapshot

I'm using Fastlane's snapshot to create screenshots for an app I'm about to submit to the App Store.
It works "as advertised" for the most part, but it doesn't seem to like the way I access the UserDefaults within my app. On one test, it generates an Exit status: 65 error.
UI Testing Failure - com.me.MyApp crashed in (extension in MyApp):__ObjC.NSObject.defaultTime () -> Swift.Float
I find UserDefaults.standard.value(forKey: "defaultTime") to an invitation for a syntax error, so I created an extension to access UserDefaults. Here's what the extension looks like:
class CustomExtensions: NSObject {
/*
This is blank. Nothing else in here. No really...nothing else
*/
}
extension NSObject {
// User Defaults
func defaultTime() -> Float {
return UserDefaults.standard.value(forKey: "defaultTime") as! Float
}
// a bunch of other UserDefaults
}
Wihin the app, whenever I need defaultTime, I just type defaultTime(). Using this method to access UserDefaults values works fine in the Simulator and on the devices I've tested. I only encounter a problem with snapshot.
I've tried adding in sleep(1) within the test, but that doesn't seem to do anything. I welcome suggestions re: alternative means of accessing UserDefaults that enable me to access them easily throughout MyApp.
What's probably happening is that, in your simulator and on device, you're writing a value to user defaults for the key defaultTime before it is ever read. value(forKey: returns an optional, and if you force-unwrap it (or force down-cast as your are doing here), you will crash if the value is nil. Try either returning an optional:
func defaultTime() -> Float? {
return UserDefaults.standard.value(forKey: "defaultTime") as? Float
}
or using a default value:
func defaultTime() -> Float {
return UserDefaults.standard.value(forKey: "defaultTime") as? Float ?? 0.0
}

How can I create instance variable based on OS version

How can I define instance variable based on the iOS verison, for example
CNContactStore is available since iOS9.0 and ABAddressBook is deprecated in 9.0 but I want to create two variable depends upon iOS version. My Approach is
if #available(iOS 9.0, *) {
var addressBookRef: ABAddressBook? = nil
} else {
var contactStore : CNContactStore? = nil
}
If I do this inside a method body, works fine but I want it to be define globally and can be access throughout the class but giving me error if I do it like this
#available(iOS 9.0, *)
var contactStore : CNContactStore? = nil
Need some suggestions. How do I achieve this. I've no idea if I release #available(iOS 9.0, *), it will crash my app on iOS 8.x or below?
In that case I would use a wrapper class mimic even ABAddressBook and CNContactStore behaviour so all the call to their methods should be done through this class.
A quick approach for what you are looking could be like this:
class ContactWrapper {
var contactBook: NSObject?
init() {
if #available(iOS 9, *) {
contactBook = CNContactStore.init()
} else {
contactBook = ABAddressBookCreate().takeRetainedValue() as? NSObject
}
}
}

Check if a function is available in Swift?

I would like to detect if the user has enabled Reduce Transparency. It's simple you just call the func UIAccessibilityIsReduceMotionEnabled() and it returns a Bool. But my app targets iOS 7 and 8 and this function isn't available on iOS 7.
In Objective-C, this is how I checked to see if that function exists:
if (UIAccessibilityIsReduceMotionEnabled != NULL) { }
In Swift, I can't figure out how to check if it exists or not. According to this answer, you can simply use optional chaining and if it's nil then it doesn't exist, but that is restricted to Obj-C protocols apparently. Xcode 6.1 doesn't like this:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled?()
It wants you to remove the ?. And of course if you do so it will crash on iOS 7 because that function doesn't exist.
What is the proper way to check if these types of functions exist?
A proper check for availability has been added in Swift 2. This is recommended over other options mentioned here.
var shouldApplyMotionEffects = true
if #available(iOS 8.0, *) {
shouldApplyMotionEffects = !UIAccessibilityIsReduceMotionEnabled()
}
If you're okay with being a little bit cheeky, you can always open the UIKit binary using the library loader and see if it can resolve the symbol:
let uikitbundle = NSBundle(forClass: UIView.self)
let uikit = dlopen(uikitbundle.executablePath!, RTLD_LAZY)
let handle = dlsym(uikit, "UIAccessibilityIsReduceMotionEnabled")
if handle == nil {
println("Not available!")
} else {
println("Available!")
}
The dlopen and dlsym calls can be kinda expensive though so I would recommend keeping the dlopen handle open for the life of the application and storing somewhere the result of trying to dlsym. If you don't, make sure you dlclose it.
As far as I know this is AppStore safe, since UIAccessibilityIsReduceMotionEnabled is a public API.
You could check to see if you're running in iOS 8 or higher --
var reduceMotionEnabled = false
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
I don't think there's another way to tell. So in theory, if you were able to check, trying to access the function name without the () would give you nil in iOS 7 and the () -> Bool function in iOS 8. However, in order for that to happen, UIAccessibilityIsReduceMotionEnabled would need to be defined as (() -> Bool)?, which it isn't. Testing it out yields a function instance in both versions of iOS that crashes if called in iOS 7:
let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled
// reduceMotionDetectionIsAvailable is now a () -> Bool
reduceMotionDetectionIsAvailable()
// crashes in iOS7, fine in iOS8
The only way I can see to do it without testing the version is simply to define your own C function to check in your bridging header file, and call that:
// ObjC
static inline BOOL reduceMotionDetectionIsAvailable() {
return (UIAccessibilityIsReduceMotionEnabled != NULL);
}
// Swift
var reduceMotionEnabled = false
if reduceMotionDetectionIsAvailable() {
reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
From the Apple Developer docs (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):
Swift code can use the availability of APIs as a condition at
run-time. Availability checks can be used in place of a condition in a
control flow statement, such as an if, guard, or while
statement.
Taking the previous example, you can check availability in an if
statement to call requestWhenInUseAuthorization() only if the method
is available at runtime:
let locationManager = CLLocationManager()
if #available(iOS 8.0, macOS 10.10, *) {
locationManager.requestWhenInUseAuthorization()
}
Alternatively, you can check availability in a guard statement,
which exits out of scope unless the current target satisfies the
specified requirements. This approach simplifies the logic of handling
different platform capabilities.
let locationManager = CLLocationManager()
guard #available(iOS 8.0, macOS 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()
Each platform argument consists of one of platform names listed below,
followed by corresponding version number. The last argument is an
asterisk (*), which is used to handle potential future platforms.
Platform Names:
iOS
iOSApplicationExtension
macOS
macOSApplicationExtension
watchOS
watchOSApplicationExtension
tvOS
tvOSApplicationExtension

Resources