Check if iOS10 or lower during compile time - xcode

I am implementing new notifications, with UNUserNotificationCenter. But I need to keep it backwards compatible, therefore I have checks all over the place:
if #available(iOS 10.0, *) { ... }
else { ... }
Which seems to work fine in iOS10. To be able to use the UNUserNotificationCenter framework, I have to import:
import NotificationCenter
But it crashes the iOS9.3, because it does not know what it is.
It is a compile time action, not a runtime action - so it means I can not put condition on the imports.If I create a separate class, and put
#available(iOS 10.0, *)
class ....
there the imports are also happening before the class implementation.
How should I work around this issue?

Try navigating to Build Phases->Link Binary with Libraries and add NotificationCenter and set the status to "optional" rather than "required".

first of all you must use:
import UserNotifications
for local notifications.
conditions: Xcode 8 beta 6 and Sierra beta6
some test code:
a) iOS 10 only for swift 3:
override func viewDidLoad() {
super.viewDidLoad()
// ADC site:
// Listing 1
// Requesting authorization for user interactions
/* let center = UNUserNotificationCenter.currentNotificationCenter()
center.requestAuthorizationWithOptions([.Alert, .Sound]) { (granted, error) in
}
*/
// swift 3.0:
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
2) support iOS 9: (I have changed deployment target)
// swift 3.0:
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
} else {
// Fallback on earlier versions
}

Related

UITextViewDelegate function shouldinteractwithURL in Xcode 8.0

I have created a UITextView with detectable links however I am unable to use the UITextView delegate function 'shouldinteractwithURL'.
I get a warning that the instance method nearly matches another instance method. I have looked at the delegate functions and the 'shouldinteractwithURL' function has been updated for ios 10.0 however I want to use the version of the function that is compatible with ios 9. However, even if I use the new version and set the deployment target to ios 10, I get the same warnings and the function is still not called (I have sent the textview delegate correctly).
The solutions that Xcode suggests is to either make the function private or to add #nonobjc. Neither of these work so nothing happens when the links are clicked.
Has anyone else had this problem or is this an issue with Xcode 8.0?
With adding #availability attribute, this will compile without errors:
//For iOS 7...9
#available(iOS, deprecated: 10.0)
func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange) -> Bool {
var result = false
//...
return result
}
//For iOS 10
#available(iOS 10.0, *)
func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
var result = false
//...
return result
}

HKWorkoutSessionDelagate protocol conformity

I'm upgrading a watchOS2 to watchOS3 with xCode 8.0 Beta And I'm having trouble with HKWorkoutSessionDelagate. See image.
The fix-it suggestion, crashes xcode - anyone having similar issues or can anyone point me in the direction of the resolution - that would be much appreciated.
Thanks
Just added this delegate, I didn't get any error. Using Xcode 8.0, Swift 3.0.
//
// InterfaceController.swift
// asf WatchKit Extension
//
// Created by Alvin Varghese on 27/06/16.
// Copyright © 2016 Swift Coder. All rights reserved.
//
import WatchKit
import Foundation
import HealthKit
extension InterfaceController : HKWorkoutSessionDelegate
{
func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date)
{
}
func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: NSError){
}
}
class InterfaceController: WKInterfaceController {
override func awake(withContext context: AnyObject?) {
super.awake(withContext: context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}

MAC OS Xcode Swift 2.2 Fullscreen Mode

What Swift code will switch the app to fullscreen?
I found references with example code for IOS.
I am looking for a code which works for a MacOS app.
Updated for Swift 4
override func viewDidAppear() {
let presOptions: NSApplication.PresentationOptions = [.fullScreen, .autoHideMenuBar]
let optionsDictionary = [NSView.FullScreenModeOptionKey.fullScreenModeApplicationPresentationOptions: presOptions]
view.enterFullScreenMode(NSScreen.main!, withOptions: optionsDictionary)
view.wantsLayer = true
}
One way is to override viewDidAppear in NSViewController:
class ViewController : NSViewController {
override func viewDidAppear() {
let presOptions: NSApplicationPresentationOptions = ([.FullScreen,.AutoHideMenuBar])
let optionsDictionary = [NSFullScreenModeApplicationPresentationOptions :
NSNumber(unsignedLong: presOptions.rawValue)]
self.view.enterFullScreenMode(NSScreen.mainScreen()!, withOptions:optionsDictionary)
self.view.wantsLayer = true
}
}
↳ Apple Developer API Reference : viewDidAppear()
An alternative, if you want different behavior, where the menu bar is available when you move your mouse to top is this. However, it starts out as a normal size window then grows, so that may not be desirable depending on what you are doing.
override func viewDidAppear() {
view.window?.toggleFullScreen(self)
}

Can I mix UIKit and TVMLKit within one app?

I'm exploring tvOS and I found that Apple offers nice set of templates written using TVML. I'd like to know if a tvOS app that utilises TVML templates can also use UIKit.
Can I mix UIKit and TVMLKit within one app?
I found a thread on Apple Developer Forum but it does not fully answer this question and I am going through documentation to find an answer.
Yes, you can. Displaying TVML templates requires you to use an object that controls the JavaScript Context: TVApplicationController.
var appController: TVApplicationController?
This object has a UINavigationController property associated with it. So whenever you see fit, you can call:
let myViewController = UIViewController()
self.appController?.navigationController.pushViewController(myViewController, animated: true)
This allows you to push a Custom UIKit viewcontroller onto the navigation stack. If you want to go back to TVML Templates, just pop the viewController off of the navigation stack.
If what you would like to know is how to communicate between JavaScript and Swift, here is a method that creates a javascript function called pushMyView()
func createPushMyView(){
//allows us to access the javascript context
appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
//this is the block that will be called when javascript calls pushMyView()
let pushMyViewBlock : #convention(block) () -> Void = {
() -> Void in
//pushes a UIKit view controller onto the navigation stack
let myViewController = UIViewController()
self.appController?.navigationController.pushViewController(myViewController, animated: true)
}
//this creates a function in the javascript context called "pushMyView".
//calling pushMyView() in javascript will call the block we created above.
evaluation.setObject(unsafeBitCast(pushMyViewBlock, AnyObject.self), forKeyedSubscript: "pushMyView")
}, completion: {(Bool) -> Void in
//done running the script
})
}
Once you call createPushMyView() in Swift, you are free to call pushMyView() in your javascript code and it will push a view controller onto the stack.
SWIFT 4.1 UPDATE
Just a few simple changes to method names and casting:
appController?.evaluate(inJavaScriptContext: {(evaluation: JSContext) -> Void in
and
evaluation.setObject(unsafeBitCast(pushMyViewBlock, to: AnyObject.self), forKeyedSubscript: "pushMyView" as NSString)
As mentioned in the accepted answer, you can call pretty much any Swift function from within the JavaScript context. Note that, as the name implies, setObject:forKeyedSubscript: will also accept objects (if they conform to a protocol that inherits from JSExport) in addition to blocks, allowing you to access methods and properties on that object. Here's an example
import Foundation
import TVMLKit
// Just an example, use sessionStorage/localStorage JS object to actually accomplish something like this
#objc protocol JSBridgeProtocol : JSExport {
func setValue(value: AnyObject?, forKey key: String)
func valueForKey(key: String) -> AnyObject?
}
class JSBridge: NSObject, JSBridgeProtocol {
var storage: Dictionary<String, String> = [:]
override func setValue(value: AnyObject?, forKey key: String) {
storage[key] = String(value)
}
override func valueForKey(key: String) -> AnyObject? {
return storage[key]
}
}
Then in your app controller:
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let bridge:JSBridge = JSBridge();
jsContext.setObject(bridge, forKeyedSubscript:"bridge");
}
Then you can do this in your JS: bridge.setValue(['foo', 'bar'], "baz")
Not only that, but you can override views for existing elements, or define custom elements to use in your markup, and back them with native views:
// Call lines like these before you instantiate your TVApplicationController
TVInterfaceFactory.sharedInterfaceFactory().extendedInterfaceCreator = CustomInterfaceFactory()
// optionally register a custom element. You could use this in your markup as <loadingIndicator></loadingIndicator> or <loadingIndicator /> with optional attributes. LoadingIndicatorElement needs to be a TVViewElement subclass, and there are three functions you can optionally override to trigger JS events or DOM updates
TVElementFactory.registerViewElementClass(LoadingIndicatorElement.self, forElementName: "loadingIndicator")
Quick custom element example:
import Foundation
import TVMLKit
class LoadingIndicatorElement: TVViewElement {
override var elementName: String {
return "loadingIndicator"
}
internal override func resetProperty(resettableProperty: TVElementResettableProperty) {
super.resetProperty(resettableProperty)
}
// API's to dispatch events to JavaScript
internal override func dispatchEventOfType(type: TVElementEventType, canBubble: Bool, cancellable isCancellable: Bool, extraInfo: [String : AnyObject]?, completion: ((Bool, Bool) -> Void)?) {
//super.dispatchEventOfType(type, canBubble: canBubble, cancellable: isCancellable, extraInfo: extraInfo, completion: completion)
}
internal override func dispatchEventWithName(eventName: String, canBubble: Bool, cancellable isCancellable: Bool, extraInfo: [String : AnyObject]?, completion: ((Bool, Bool) -> Void)?) {
//...
}
}
And here's how to set up a custom interface factory:
class CustomInterfaceFactory: TVInterfaceFactory {
let kCustomViewTag = 97142 // unlikely to collide
override func viewForElement(element: TVViewElement, existingView: UIView?) -> UIView? {
if (element.elementName == "title") {
if (existingView != nil) {
return existingView
}
let textElement = (element as! TVTextElement)
if (textElement.attributedText!.length > 0) {
let label = UILabel()
// Configure your label here (this is a good way to set a custom font, for example)...
// You can examine textElement.style or textElement.textStyle to get the element's style properties
label.backgroundColor = UIColor.redColor()
let existingText = NSMutableAttributedString(attributedString: textElement.attributedText!)
label.text = existingText.string
return label
}
} else if element.elementName == "loadingIndicator" {
if (existingView != nil && existingView!.tag == kCustomViewTag) {
return existingView
}
let view = UIImageView(image: UIImage(named: "loading.png"))
return view // Simple example. You could easily use your own UIView subclass
}
return nil // Don't call super, return nil when you don't want to override anything...
}
// Use either this or viewForElement for a given element, not both
override func viewControllerForElement(element: TVViewElement, existingViewController: UIViewController?) -> UIViewController? {
if (element.elementName == "whatever") {
let whateverStoryboard = UIStoryboard(name: "Whatever", bundle: nil)
let viewController = whateverStoryboard.instantiateInitialViewController()
return viewController
}
return nil
}
// Use this to return a valid asset URL for resource:// links for badge/img src (not necessary if the referenced file is included in your bundle)
// I believe you could use this to cache online resources (by replacing resource:// with http(s):// if a corresponding file doesn't exist (then starting an async download/save of the resource before returning the modified URL). Just return a file url for the version on disk if you've already cached it.
override func URLForResource(resourceName: String) -> NSURL? {
return nil
}
}
Unfortunately, view/viewControllerForElement: will not be called for all elements. Some of the existing elements (like collection views) will handle the rendering of their child elements themselves, without involving your interface factory, which means you'll have to override a higher level element, or maybe use a category/swizzling or UIAppearance to get the effect you want.
Finally, as I just implied, you can use UIAppearance to change the way certain built-in views look. Here's the easiest way to change the appearance of your TVML app's tab bar, for example:
// in didFinishLaunching...
UITabBar.appearance().backgroundImage = UIImage()
UITabBar.appearance().backgroundColor = UIColor(white: 0.5, alpha: 1.0)
If you already have a native UIKit app for tvOS, but would like to extend it by using TVMLKit for some part of it, You can.
Use the TVMLKit as a sub app in your native tvOS app. The following app shows how to do this, by retaining the TVApplicationController and present the navigationController from the TVApplicationController. The TVApplicationControllerContext is used to transfer data to the JavaScript app, as the url is transferred here :
class ViewController: UIViewController, TVApplicationControllerDelegate {
// Retain the applicationController
var appController:TVApplicationController?
static let tvBaseURL = "http://localhost:9001/"
static let tvBootURL = "\(ViewController.tvBaseURL)/application.js"
#IBAction func buttonPressed(_ sender: UIButton) {
print("button")
// Use TVMLKit to handle interface
// Get the JS context and send it the url to use in the JS app
let hostedContContext = TVApplicationControllerContext()
if let url = URL(string: ViewController.tvBootURL) {
hostedContContext.javaScriptApplicationURL = url
}
// Save an instance to a new Sub application, the controller already knows what window we are running so pass nil
appController = TVApplicationController(context: hostedContContext, window: nil, delegate: self)
// Get the navigationController of the Sub App and present it
let navc = appController!.navigationController
present(navc, animated: true, completion: nil)
}
Yes. See the TVMLKit Framework, whose docs start with:
The TVMLKit framework enables you to incorporate JavaScript and TVML files in your binary apps to create client-server apps.
From a quick skim of those docs, it looks like you use the various TVWhateverFactory classes to create UIKit views or view controllers from TVML, after which you can insert them into a UIKit app.

How to indicate network activity in status bar

I use Alamofire for networking in my iOS application. I need to run this app in iOS 7+. I want to indicate network activity in status bar, so I created this struct:
struct ActivityManager {
static var activitiesCount = 0
static func addActivity() {
if activitiesCount == 0 {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
}
activitiesCount++
}
static func removeActivity() {
if activitiesCount > 0 {
activitiesCount--
if activitiesCount == 0 {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}
}
}
But I don't know, where to call in code addActivity() and removeActivity() methods. I don't want to write them with every request. I want to, that they will be called automatically with every request.
I tried also use pure NSURLSession and NSURLSessionTask and extend them:
extension NSURLSessionTask {
func resumeWithActivity() {
ActivityManager.addAction()
self.resume()
}
}
public extension NSURLSession {
func OwnDataTaskWithRequest(request: NSURLRequest!, ownCompletionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask! {
return self.dataTaskWithRequest(request, completionHandler: { (data, response, error) in
ActivityManager.removeAction()
ownCompletionHandler!(data, response, error)
})
}
}
Then I used them like this:
var session: NSURLSession = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
session.OwnDataTaskWithRequest(request) { (data, response, error) -> Void in
// some logic here
}.resumeWithActivity()
But this approach doesn't work. In iOS 7 is NSURLSession extension not visible. I created a bug report for this (with sample project).
Can you please give me some advise, how to reach my goal? With or without Alamofire?
If you don't want to call your functions manually for every request and that you want to use Alamofire, I suggest you to improve it to add the network activity indicator feature.
Have a look at the source of Alamofire
You need to register 2 notification observers in your ActivityManager and then trigger the notifications at the relevant places either in Alamofire.Manager or in Alamofire.Request.
Also have a look at the source of AFNetworkActivityIndicatorManager which implement the feature you want in AFNetworking.

Resources