Today Bo Master has an app extension needs, encountered some difficulties, here and we share, hope to progress together.
General overview
Extensions (Extension) are a very large feature point for IOS 8 and OSX 10.10, and developers can provide some additional functionality to system-specific services by providing our extended access points (Extension point). For IOS, there are several extended access points that you can use:
- Today extension-Adds a widget to the "todays" panel in the drop-down notification center
- Share extensions-share websites or photos via app after clicking the Share button
- Action Extension-Click the action button to send the content to the app by judging the context
- Photo editing extensions-the ability to provide photo editing in the system's photo app
- Documentation provides extensions-provides and manages file content
- Custom keyboard-Provides a custom keyboard or input method that can be used in an alternative system keyboard for all applications
The access points provided by the system are still limited, but many of them are already in high demand among developers and IOS users. The function and usability of the system can also be greatly enriched by using these access points to provide corresponding functions. This article will give a general introduction to the common features of the various extensions, and then, with a practical example, focus on the development of the Today extension of the Notification center, with a view to providing a smooth entry for the extended learning of IOS 8.
Apple points out that the center of the developer in IOS 8 should not change, it should still be around the app. Providing great interactivity and useful functionality in the app is now, and will be, the core task of IOS app development in the future. The extension cannot exist in a separate form in IOS, which means that we cannot provide an extended download directly in AppStore, and the extension must be packaged with an app. After installing an app with extensions, users can choose to turn your extension on or off in the notification hub's today interface, or in the system's settings. For developers, the extension is provided by adding the corresponding extended target to the app's project. Because extensions are generally presented at the system level UI or in other applications, Apple specifically points out that extensions should be lightweight, fast, and focused on a single function, without interrupting or interrupting the user's use of the current application. Because users can choose to disable the extension themselves, so if your extension performance is not good, it is likely to be discarded by users, or even cause them to uninstall your app.
The extended life cycle
The lifecycle of the extension and the life cycle of your container app (container app) that contains the extension itself is independent, accurately stated. They are two separate processes, and by default they should not know each other's existence. The extension needs to respond to requests from the host app, the app that calls the extension, and of course, by configuring and some means, we can access and share some of the container app's resources in the extension, which we'll talk about later.
Because the extension is actually dependent on the host app that invokes it, its lifecycle is also determined by the user's behavior in the host app. In general, after the user triggers the extension in the host app, the extended life cycle begins: For example, by selecting your extension in the sharing options, or by adding your widget to the notification hub, and so on. All extensions are defined by Viewcontroller, and when the user decides to use an extension, the corresponding Viewcontroller will be loaded, so you can get to viewDidLoad
the Viewcontroller as you would write a traditional app like This method, and the interface to build and do the corresponding logic. The extension should maintain a single focus on functionality, and quickly process tasks, perform the necessary tasks, or after a background appointment to complete a task, it is generally necessary to return control to the host app as soon as possible through a callback, to the end of the life cycle.
According to Apple, the amount of memory the extension can use is much lower than the memory that the app can use. When memory is tight, the system tends to prioritize extensions rather than killing the host app. Therefore, in the development of the extension, it is also necessary to pay attention to the memory consumption limit. Another point is that, like the notification hubs extension, your extension may coexist with other developers ' extensions, so that if the extension blocks the main thread, it will cause the entire notification hub to become unresponsive. In this case your extension and application will basically say goodbye to the user.
Extending and interacting with container apps
Extensions and container applications themselves do not share a process, but as an extension, it is an extension of the main application function, which inevitably requires the use of the logic and even the interface of the application itself. In this case, we can use the new self-made framework introduced by IOS 8 to organize the code that needs to be reused so that both the app and the extension can use the same code after linking the framework.
Another common requirement is data sharing, where extensions and applications want to access each other's data. This can enable data sharing between two processes by opening the App Groups and configuring it accordingly. This includes sharing with NSUserDefaults
small data, or using NSFileCoordinator
and NSFilePresenter
even CoreData and SQLite for larger files or more complex data interactions.
In addition, the custom URL scheme has always been one of the channels of feedback data and interaction from the extension to the application.
These common tools and strategies will be used in the next demo. A picture can top countless words, and a demo can top thousands pictures. So, let's get started.
Timer Demo
The interface for the initial project to run is probably this:
Simply say the whole project has only one viewcontroller, and when you click the Start button we create an instance by setting the desired timing time Timer
and then call its start
method. This method receives two parameters, one for each remaining time update, and a callback method at the end of the timing (whether it is timed to completion or interrupted by the user). In addition, this method returns a tuple that indicates whether to start success and possible errors.
The remaining time update callback in the Refresh UI UI, the timed-out callback reclaims the Timer
instance, and shows one UIAlertController
. The user can simply call the method to interrupt the timing by tapping the Stop button stop
. Direct and simple, no other trick.
We now plan to do a today extension for this app, to display and update the current remaining time in the notification center, and to display a button after the timing is complete, then go back to the app body and pop up a complete hint.
Add extension Target
The first step, of course, is to add extensions to our app. As mentioned in the overview, the extension is a separate target in the project. In Xcode 6, Apple has prepared a target template for each of the different extensibility points, which makes it easy to add extensions to the app. For the Today extension we want to do now, just click on the menu of File > New > Target ... and select Application Extension in IOS today Extension.
Name the new target in the pop-up menu SimpleTimerTodayExtenstion
and let Xcode automatically generate a new Scheme to facilitate test use. Our project now has an extra folder with the same name as the new target, which contains a. Swift Viewcontroller program file, a MainInterface
storyboard file called and Info.plist. The NSExtension
types and portals of this extension are defined in plist, and the supporting Viewcontroller and StoryBoard are the specific content and implementation of our extensions.
After compiling the link, our subject program generates a package with the suffix .app
, which contains the binaries of the main program and various resources. The extended target will generate a separate .appex
package with the suffix name. This package will be installed with the main program and selected by the user to activate or add (for the Today widget, the edit in the Notification Center today view is deleted, for other extensions, the system settings are used to manage). As we can see, a new extension has been added to the Product of the project now.
If you have a heart to open the MainInterface
file, you can notice that Apple has prepared a default Hello world label for us. As soon as we run the main program, the extension will be installed. Set Scheme as the main program of Simple Timer, and Cmd + R
then click the Home button to cut the app into the background and pull down the notification center. At this point you should be able to find the named item in the Toady view SimpleTimerTodayExtenstion
, showing a tag for Hello world. If not, you can click the Edit button below to see if it is not enabled, if it is not in the Edit menu, congratulations to you encounter the Session video with the speaker of the same bug, you may need to delete the application, clean up the project, and then install try again. Generally uninstall the installation can solve the current beta version of the majority of the problem, if you still encounter problems, you can also try to restart the device (in the case of the SDK in the past few years, it is normal in the beta version, there should be no problem in the official version).
If everything works, the notification hubs you can see should look something like this:
Extensions that run this way cannot be debugged because our debugger does not attach to this extended target. There are two ways to debug the extension, one is to set Scheme to be generated for us before Xcode, SimpleTimerTodayExtenstion
then run from the Today view, and the other is to use the Debug > Attach to Process > in the menu when the extension runs. by Process Identifier (PID) or name, then enter the name of your extension (in our demo is Com.onevcat.SimpleTimer.SimpleTimerTodayExtension) to attach the debugger to the process.
Share data between apps and extensions-app Groups
Since the extension is a viewcontroller, the various connections IBOutlet
, the use viewDidLoad
of life-cycle methods such as how to set the UI is a natural thing. The first difficulty we have now is how to get the remaining time of the timer when the application principal exits. As long as we know how long it will take and when to exit, we will be able to show the correct time remaining in the notification center.
For IOS developers, the sandbox restricts the random reads and writes we have on the device. But for apps and their corresponding extensions, Apple offers us a possibility in IOS 8 that app Groups. App Groups defines a set of domains for the same vender application or extension, where the same group can share some resources. For our example, we only need to use the same group to store the NSUserDefaults
data in the main application when it is inactive, and then read from the same place when the extension is initialized.
First we need to open the App Groups. Thanks to the capabilities introduced in Xcode 5, this became very simple (at least no longer needed to developer the portal). Select the main target SimpleTimer
, open its Capabilities tab, find the App Groups and turn on the switch, and add a group name that you can remember, for example group.simpleTimerSharedDefaults
. Next you need to configure the target in the SimpleTimerTodayExtension
same way, but instead of having to create a new group, tick the group you just created.
Then let's start writing code! The first is to add a program in the main program to ViewController.swift
lose the foreground listening, viewDidLoad
add in:
NSNotificationCenter.defaultCenter() .addObserver(self, selector: "applicationWillResignActive",name: UIApplicationWillResignActiveNotification, object: nil)
Then the method that is called applicationWillResignActive
:
@objc PrivateFuncApplicationwillresignactive() {if timer = =Nil {cleardefaults ()}else {If timer.running {savedefaults ()}else {cleardefaults ()}}}privateFuncSavedefaults() {Let Userdefault =Nsuserdefaults (suitename:"Group.simpletimershareddefaults") Userdefault.setinteger (int (Timer.lefttime), Forkey: "Com.onevcat.simpleTimer.lefttime ") Userdefault.setinteger (int (nsdate (). timeIntervalSince1970), Forkey: "com.onevcat.simpleTimer.quitdate") userdefault.synchronize ()}private func cleardefaults () {let userdefault = NSUserDefaults ( Suitename: "Group.simpletimershareddefaults") Userdefault.removeobjectforkey ( "Com.onevcat.simpleTimer.lefttime") Userdefault.removeobjectforkey ( "com.onevcat.simpleTimer.quitdate") Userdefault.synchronize ()}
This way, when the app is cut to the background, if it's timing, we'll save the current time remaining and the date of the exit NSUserDefaults
. Note here that it may generally be used NSUserDefaults
more often when we use standardUserDefaults
it, but here we need the two data to be extended access, we must use the name defined in the APP Groups NSUserDefaults
.
Next, we can go to the extension to TodayViewController.swift
get the data. In the extension Viewcontroller viewDidLoad
, add the following code:
let userdefaults = Nsuserdefaults (suitename: "Group.simpletimershareddefaults") let lefttimewhenquit = Userdefaults.integerforkey ( " Com.onevcat.simpleTimer.lefttime ") let quitdate = Userdefaults.integerforkey ( "com.onevcat.simpleTimer.quitdate") let passedtimefromquit = nsdate (). Timeintervalsincedate (nsdate (timeIntervalSince1970: nstimeinterval (quitdate))) let lefttime = lefttimewhenquit-int (passedTimeFromQuit) Lbltimer.text = "\ (lefttime)"
Of course, don't forget to drag the StoryBoard label out:
@IBOutlet weak var lblTImer: UILabel!
Run the program again and start a timer, then press the Home key to cut into the background, pull out the notification center, perfect, our extension can interact with the main program for data:
Share code between apps and extensions-Framework
The next task is to refresh our interface by timing it in the Today interface. This part of the code actually we have written (of course. It's exactly what I wrote, you may have just read it, yes, it's the file in the app Timer.swift
. We just need to create one instance in the extended Viewcontroller with the rest of the time Timer
, and then set the label in the updated callback. But the problem is that this part of the code is in the application, how can we use it in the extension?
One of the most straightforward and straightforward ideas is to Timer.swift
add the compiled files to the extension target, which will naturally be used in the extension. But starting with IOS 8, Apple provides us with a better option, which is to make the Framework. Individual files may not feel the difference, but as the number and type of files that need to be shared increases, adding individual files to different target will quickly make things mess. You need to consider the scope of each new or deleted file impact and where they need to be applied, which is a hell of a living. Providing a unified and beautiful framework would be a choice that more people want (and it's almost a matter of fact). Another benefit of using the framework for modularity is that you can benefit from good access control to ensure that you won't be exposed to things you shouldn't use, and then Swift's namespace is module-based, so you no longer need to worry about naming conflicts and so on OBJC times.
Now let's put it Timer.swift
in the framework. First, we create a new framework target. File > New > Target ... Select the Framework & Library, select the Cocoa Touch framework (the other options in the map may not be in your Xcode, ignore them, this is a legacy issue), and then OK. According to Apple's naming convention for the framework, it might be SimpleTimerKit
a good name.
Next, we'll Timer.swift
move from the app to the framework. It's easy to first remove it from the app's target and then add it to the new SimpleTimerKit
Compile Sources.
Confirm that the link to the new framwork in the application, and add in the Viewcontroller.swift and import SimpleTimerKit
then try to compile to see ... A lot of mistakes, basically are Viewcontroller said to find the Timer and so on. This is because the original implementation is in the same module, and the default internal
access level allows Viewcontroller to access Timer
information about and the corresponding method. But now they are in different module, so we need to Timer.swift
make some changes to the access permissions and add the keywords where we need to get outside access public
.
Next, in the extended Viewcontroller is also linked SimpleTimerKit
and added import SimpleTimerKit
, we can use in the extension Timer
. Remove the code that just set the label directly, and replace it with the following:
override func viewdidload () {//... if (Lefttime > 0) {timer = timer (timeinteral: Span class= "Hljs-type" >nstimeinterval (lefttime)) Timer.start (updatetick: {[weak self] lefttick in self!. Updatelabel ()}, StopHandler: nil)} else {//do Nothing now}}private func updatelabel () {lbltimer.text = timer.lefttimestring}
We also create Timer
, given callbacks, and wait for the interface to refresh in the extension as well as within the app. Run a look, first go into the app and start a timing. Then exit to open the Notification center. The Notification center now also starts to clock, and it's really starting from the rest of the time, everything is perfect:
By extending the Startup body app
The last task is that we want to render a "done" button on the extension after the notification center is timed, and then click this button to go back to the app and pop the end alert within the app.
What's really important here is how we're going to start the main container application and pass the data to it. Maybe a lot of students will think of URL scheme, yes we can actually launch a specific app and carry data through URL scheme. But one problem is that in order to launch the app via a URL, we generally need to call UIApplication
the OpenURL method. If the carefully looked at the NS_EXTENSION_UNAVAILABLE
classmate may find that this method is disabled (this is also very makes sense of one thing, because the extension sharedApplication
is actually the host application, the host application to show you what to get Ah!) )。 To do the same, Apple provides a class for the extension NSExtensionContext
to interact with the host app. After the user launches the extension in the host app, the host app provides a context for the extension, the most important of which is to include inputItems
such pending data. Of course, for our current needs, we just need to use its openURL(URL:,completionHandler:)
approach.
In addition, we may need to adjust the size of the extension widget so that we have more space to display the button, which can be done by setting preferredContentSize
. TodayViewController.swift
Add the following methods in the:
Private Func Showopenappbutton () {Lbltimer. Text = "finished" preferredcontentsize = cgsizemake (0, 100) let button = uibutton (frame: cgrectmake (0, 50, 63)) Button.settitle ( "Open", Forstate: uicontrolstate.addtarget (self, Action: "buttonpressed:", forControlEvents: UIControlEvents.addsubview (Button)}
When set preferredContentSize
, the specified width is not valid, the system will automatically process it as the width of the whole screen, so throw a 0 in it. When I add a button here I stole a lazy, I should have used auto Layout and add constraints, but this is not the focus of our demo. On the other hand, for the code to be clear, it is directly on the coordinates.
Then add the action for this button:
@objc private func buttonPressed(sender: AnyObject!) { extensionContext.openURL(NSURL(string: "simpleTimer://finished"), completionHandler: nil)}
We will pass the URL of scheme is simpleTimer
, with the host finished
as a parameter, you can notify the main application timing is complete. Then we need to call to display the button when the timing is complete showOpenAppButton
, update viewDidLoad
the content:
override Func viewdidload () {//... if (Lefttime > 0) {timer = timer ( Timeinteral: nstimeinterval (lefttime)) Timer.start (updatetick: {[self] lefttick in self!. Updatelabel ()}, StopHandler: {[weak self] finished in self!. Showopenappbutton ()})} else {Showopenappbutton ()}}
The final step is to set the appropriate URL Scheme in the target of the main application:
Then AppDelegate.swift
capture this open event in, and detect whether the timing is complete, and then make the appropriate:
func Applicationbool {if url.scheme = " Simpletimer "{if url.host = " finished "{ nsnotificationcenter.defaultcenter (). Postnotificationname ( Taskdidfinishedinwidgetnotification, object: nil)} return Span class= "hljs-built_in" >true} return false}
In this case, we sent a notice. In the Viewcontroller we can listen to this notification from the beginning, and then stop the timer and pop up the prompt after receiving it. Of course we may need some small refactoring, such as adding a manual interrupt or timing to complete the judgment to pop up a different dialog box, and so on, these are simple again not to repeat.
This timer is now available in the app only when the foreground or notification center is displayed, and if you quit the app and then open the app, there's no time in the process. Therefore, the possible improvement after this project is to add a timing decision when returning to the application, to update the remaining time of the timer, or to end the timing immediately if it is already completed.
Other
In fact, in the template files that Xcode generates for us, there is also a piece of code that is important:
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) { // Perform any setup necessary in order to update the view. // If an error is encoutered, use NCUpdateResult.Failed // If there‘s no update required, use NCUpdateResult.NoData // If there‘s an update, use NCUpdateResult.NewData completionHandler(NCUpdateResult.NewData)}
For notification hubs extensions, even if your extension is not currently visible (that is, the user does not pull the notification hub), the system will occasionally invoke the implemented NCWidgetProviding
extension to require an extended refresh interface.
It's worth noting that the template file provided by Xcode (at least now beta 4) has this method in Viewcontroller, but it does not conform this interface by default, so we need to add the class declaration as well NCWidgetProviding
.
Summarize
This Demo mainly involves the addition and general interaction of the Toady widget for notification hubs. In fact, the extension is a pretty big chunk of content, and for other extensions like sharing or Action, the way they're used will be different. But the core concept, the life cycle, and the methods of interacting with the ontology are similar. Xcode provides us with a very good template file when we create an extension, and more often than not, it is very convenient for us to simply fill in our logic in the appropriate method and not be bothered about the configuration.
Http://www.cocoachina.com/ios/20141023/10027.html
http://blog.csdn.net/phunxm/article/details/42715145
iOS Development journal 55-Notification Bar extension (App Extension)