I am trying to understand doing Quick Actions (3D Touch) for iOS 9.
I wanted the user to select 1 of 4 filter to be applied to image, so if I select item 1, I will set the NSUserDefaults.standardUserDefaults() to the filter, then show the correct picture with the applied filter.
In AppDelete.swift:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
var filterType:Int
switch (shortcutItem.type) {
...set filterType
}
NSUserDefaults.standardUserDefaults().setInteger(filterType, forKey:"filterType")
NSUserDefaults.standardUserDefaults().synchronize()
}
In ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"setDefaultFilter", name: UIApplicationWillEnterForegroundNotification, object:nil) // Handle enter from background
setDefaultFilter()
}
func setDefaultFilter() {
filterType = defaults.integerForKey("filterType")
...
imageView.image = filterImage(defaultImage!, filter:filters[filterType])
}
However, when enter the app from the menu, it will always show the last selection (not the current selection). If I select item 1, nothing happened. I select item 3, item 1 will appeared.
I have also try passing parameters via appDelegate and the result is the same. I believe there are some issues with life cycle.
Any ideas?
NSUserDefaults write data on flash, which may not be so fast.
You can wait a little longer, like observe UIApplicationDidBecomeActiveNotification other than UIApplicationWillEnterForegroundNotification.
Or you can use other ways to pass params, e.g., as an instance variable in AppDelegate.
didFinishLaunchingWithOptions method is always called before calling performActionForShortcutItem method to response to the quick action.
So, I think that you need to check what kind of quick action is selected in didFinishLaunchingWithOptions method. If the app is not launched from quick action, you just continue to your normal app launching process.(default filter)
And if you decide to handle quick action in didFinishLaunchingWithOptions, you have to return NO in didFinishLaunchingWithOptions.
You could get more idea from my demo project:
https://github.com/dakeshi/3D_Touch_HomeQuickAction
Related
I am writing a Today Widget for an iOS app. The widget has a few action buttons. I want to receive the click event when someone clicks on it. However, it should not launch the app.
I've already tried this but to no avail.
My current implementation is to define a URL Scheme, and call openURL on those button presses like so:
Button 1 links to myApp://button1
Button 2 links to myApp://button2
Button 3 links to myApp://button3
I am receiving these events in the AppDelegate's
application(_:open:options:)
Here's the Code in TodayWodgetController
#IBAction func widgetClicked(sender: UIButton){
if sender == button1 {
let u = NSURL(string: "myApp://button1")
self.extensionContext?.open(u! as URL, completionHandler: nil)
}
...
}
and here is the code I'm using in the host app's AppDelegate
func application(_ app: UIApplication,
open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if url.absoluteString.range(of: "button1") != nil{
print ("Button 1 Pressed")
}
....
return true
}
However, like I said, it also launches the host App. I want it to just send me the click event without launching the App.
Any help would be appreciated.
I don't think there is a way to do that from an app extension.
Definition of open(_ URL: URL, completionHandler: ((Bool) -> Void)? = nil) as described by Apple:
Asks the system open a URL on behalf of the currently running app
extension.
Each extension point determines whether to support this method, or
under which conditions to support this method. In iOS 8, only the
Today extension point (used for creating widgets) supports this
method.
Important: Apple allows a widget to use the open(_:completionHandler:) method to open the widget’s own containing
app.
I have a simple game app, which is programmed with SpriteKit. The Problem is, when a push notifications (SMS,iMessage etc) appears, the game stutters because the update:forScene: method is not called.
To avoid this i want to implement a simple pause menu, which will be shown as soon as a push message comes in.
How can i detect if a push message interrupts the app? In AppDelegate application:willResignActive is also not called.
It would be the best if the game continues when the message comes in, if there is another solution to force the update method to be called.
Had anybody the same Problem?
You should not try to resume your game when an interruption is happening, you should pause it, otherwise its not a good user experience.
For iMessages, phone calls etc you usually use the method you said doesn't work.
I use NSNotificationCenter to pause my games, you can google about it, there is plenty tutorials.
Essentially in your game scene add a NSNotificationCenter observer.
Also create a property for that observers name to avoid typos later on.
let pauseGameKey = "PauseGameKey" // above class so you can access it anywhere in project
class GameScene: SKScene {
// add this in didMoveToView
// in #selector add the method you want to get called
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(yourPauseGameMethod), name: pauseGameKey, object: nil)
}
Than create the willMoveFromView method so you can remove the observer when you transition to another scene (good practice).
override func willMoveFromView(view: SKView) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Than in app delegate post the notification when the application will resign.
func applicationWillResignActive(application: UIApplication) {
NSNotificationCenter.defaultCenter().postNotificationName(pauseGameKey, object: nil)
}
For local and remote UINotifications you can additional use these 2 methods in app delegate.
/// Local notification
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
}
/// Remote notification
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
}
Hope this helps
My app (prefix "AAS") is basically a game where users lose points every day they don't play. I use UILocalNotifications to alert the user that they've lost points, and invite them back to play. One of my view controllers displays when the points have changed, and it's pretty simple to send out an NSNotification when a UILocalNotification is fired while the app is open).
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
if notification.userInfo != nil {
if let notificationName = notification.userInfo![AASNotification.ActionKey] as? String {
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil, userInfo: nil)
}
}
}
When the app is reopened after being inactive, one of the classes calculates how many points are lost. Great. Bulletproof, except when the user disallows my app to use NotificationCenter, the app will not be updated if it's open when the notification is supposed to fire. For this case, I wrote my own implementation of a timed notification queue that would mimic UILocalNotification to a certain extent while my app is open. But I thought, someone must have had this problem before, and maybe there is a cocoapod for it.
So my question to the community is, does someone know of a library that dispatches timed NSNotifications? Or a different approach to this problem? Here's my solution, which is barebones and works for the purpose I need:
https://github.com/JamesPerlman/JPScheduledNotificationCenter
I'd love to use one that was coded by a professional and is well tested and feature rich. (I was made aware that this request is off topic for SO.)
Edits:
I want to be able to queue up any amount of NSNotifications to be fired at arbitrary dates. Obviously the NSNotifications can only be received by my app while it is open, that's fine. I do not know the expense of using one NSTimer for each NSNotification (could be hundreds of NSTimers all on the run loop), so my solution only uses one NSTimer at a time. I want the ability to schedule and cancel NSNotifications just like you can do with UINotifications.
You could try NSTimer (NSTimer class reference). In your AppDelegate you can create a method similar to your didReceiveLocalNotification method to execute when the timer is triggered. Also, create an NSUserDefault to store the next time you need to trigger the timer. Finally, at the point where you want to begin the countdown, get the time interval from the current time until the time you want to trigger the event, and set the timer.
So in your AppDelegate, register the default and implement the notifyPlayer:
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
let userDefaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.registerDefaults(["alertTime": NSDate()]) //initial value
return true
}
func notifyPlayer() {
// Calculate points and notify relevant viewcontroller to alert player.
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
let lastNotificationTime = defaults.objectForKey("alertTime") as! NSDate
let nextNotificationTime = lastNotificationTime.dateByAddingTimeInterval(86400)
defaults.setObject(nextNotificationTime, forKey: "alertTime")
}
}
Now set the timer wherever it makes sense, probably in your app's initial view controller.
class InitialVewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
let savedTime = defaults.objectForKey("alertTime") as! NSDate
let countDownTime = savedTime.timeIntervalSinceNow
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
NSTimer.scheduledTimerWithTimeInterval(countDownTime,
target: appDelegate,
selector: #selector(AppDelegate.notifyPlayer()),
userInfo: nil,
repeats: false)
}
}
It's not perfect, as I haven't tested it, but I think the concept will work for you.
Edit: Just to clarify, this would solve your problem of alerting the user while he is using the app, but won't do anything when the app is not in use. I don't know of any way to send users notification center notifications when permission hasn't been granted.
So I have been trying to make an app for my Mobile Apps Development class and I need to find a solution to a problem that I'm having when I save a global array called "events".
Here I tried to reload the saved event class in AppDelegate but it didn't change in the main screen view controller:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Override point for customization after application launch.
//Load the events that were saved when application was terminated
let eventKey = NSUserDefaults.standardUserDefaults()
Global.events = (eventKey.arrayForKey("savedEvents") as? [Event])!
return true
}
Here is the function that is called when someone quits the app:
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
let myEvents = Global.events
NSUserDefaults.standardUserDefaults().setObject(myEvents, forKey: "savedEvents")
NSUserDefaults.standardUserDefaults().synchronize()
}
This might just be an error in the viewcontroller that displays this array but if you see something wrong with the code please let me know.
Two problems:
In iOS, there is no such thing as "someone quits the app", and thus what you're doing is unlikely to work, because applicationWillTerminate is never called. Save when the application is deactivated or backgrounded.
An array of Event cannot magically be saved in user defaults, because Event is not a Property List type. You will need to make Event archivable and archive the array.
I create a UILocationNotification with two action button one call sleep and wake up now. So once the user sees the notification if they pressed wake up now the app will launch and execute some code for some reason the app launches then refuse to execute the codes.
FYI : The code for the UILocalNotification were implement and they are working, the only problem is when I pressed the wake up now button.
func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {
if notification.category == "options" {
if identifier == "Sleep"{
println("sleep more lazy bumm")
}
else if identifier == "wakeup"{
var object = ViewController()
object.wakeupnow()
}
}
Second Approach I took but it still not working
func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {
if notification.category == "options" {
if identifier == "Sleep"{
println("sleep more lazy bumm")
}
else if identifier == "wakeup"{
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("wake"), name: UIApplicationWillEnterForegroundNotification, object: nil)
}
}
fun wake(){
var alertview = UIAlertView()
alert.message = "Good job you are up now, so lets get to work"
alert.addButtonWithTitle("ok")
alert.cancelButtonIndex = 0
alert.show()
}
Remember that application:handleActionWithIdentifer:forLocalNotification:completionHandler: gets called in a background thread. If you're doing anything in the UI you'll need to do it on the main queue.
Also, you have to call the completionHandler block as soon as you can, or the system will kill your app.
Please see the Apple documentation about this: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:handleActionWithIdentifier:forLocalNotification:completionHandler:
Also, in your first example you are instantiating a view controller and calling a function on it but you aren't actually presenting the view controller - what do you want to happen with that view controller? You'll need to do something to make it appear, which will depend on how your app is structured. You may want to present it modally over your root navigation controller, for example. Or maybe you mean to be calling that function on your already-existing ViewController, in which case you need to keep a reference to it somewhere instead of instantiating a new one when the notification action is triggered.
In your second example, you are adding yourself as an observer to the NSNotificationCenter instead of actually posting a notification, so of course your function will never get called. If you want to take that approach, you need to call addObserver sometime earlier - in applicationDidFinishLaunching:, for example:
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("wake"), name: "wakeup", object: nil)
Then, in your handleActionWithIdentifier: function, call:
NSNotificationCenter.defaultCenter().postNotificationName("wakeup", object: nil)
That should result in your wake function being called. You still have the problem of trying to show an alert view from a background thread, though, so you would need to wrap your call in a dispatch_async:
func wake(){
var alertview = UIAlertView()
alert.message = "Good job you are up now, so lets get to work"
alert.addButtonWithTitle("ok")
alert.cancelButtonIndex = 0
dispatch_async(dispatch_get_main_queue(),{
alert.show()
})
}
Incidentally, UIAlertView is deprecated and should be replaced by UIAlertController for iOS 8 and up.