Today, we'll begin taking a look at how our app can better integrate with it. First up, we're going to learn how to allow users to open files in our app, from the Files.app. Let's get started.
We'll begin by creating a new project with the Single View App template in Xcode. In the future, we'll look at how Document Picker apps work, but for simplicity, we'll use just a plain single view app this time.
Then, we'll head into our AppDelegate.swift file, and add a new function:
This application(_:open:options:)function will be called when the user selects a file inside the Files app and presses the Share ( ) button on it, and selects our app in the UIActivityViewController that appears.
Pro Tip: We can also access this by long-pressing a file in the Files app and selecting Share from the menu:
Next we'll need to tell Xcode that our app supports the type of files we want to our app to be able to open. For this, we'll choose Text Files.
We'll click on our project at the top of the project navigator and click Info at the top. We'll find this screen:
We want that area labeled Document Types (0). We'll click the + button and enter in Text for the Name and public.text for Types.
(This public.text thing is part of a system called Uniform Type Identifiers, Apple uses these to identify specific kinds of file types. Read more here).
Then, we'll add a new key to our Info.plist to make our app work more smoothly. We'll right-click on the keys at the top and select Add Row, then add the key LSSupportsOpeningDocumentsInPlace. We'll make it a Boolean and set its value to YES:
This allows our app to work with the files users open, without needing to first copy them into its own sandbox directory.
Finally, we'll head back to our AppDelegate.swift file and fill out that function we added earlier:
First we setup a do/catch block so we can catch and print any errors that might happen during all of this. Then we call a special function on our inputURL.
Since, we're opening in place, iOS has gives us what Apple calls a "security scoped" URL. (Read more about them here).
First we have to tell the system when we begin using a URL, saving the Bool value that url.startAccessingSecurityScopedResource() returns. If that Bool is true, then we'll also need to call stopAccessingSecurityScopedResource on our URL once we're finished with it.
After that we simply read the Data at the URL, then put it into a String so we can print it out.
Now we can Build & Run the app on our device, open the Files.app, choose an .txt file, share it, and our app appears in the Share sheet:
Tapping our app's icon opens it, and runs our code to print out the contents of the file, neat!
Asset Catalogs have been around for a few Xcode releases. They're a great way for us to organize and configure graphical assets (among many other things) for our app. Today we'll check out the improvements to Asset Catalogs in Xcode 9. Let's dive in.
First up, colors.
Yep, we can now define and organize named colors inside of an Asset Catalog!
We can select "New Color Set", and then use all of the normal features of Asset Catalogs, including new Wide Gamut support.
Then, in our code we can reference these colors like this:
view.backgroundColor=UIColor(named:"wood")
Neat!
The second big addition we're going to look at is "real" vector-based asset support.
In past Xcode releases, we were able to add image assets to our catalogs with a format of PDF. This worked great, but under the hood, Xcode would simply render our asset at the @1x, @2x, and @3x sizes and save non-vector (i.e. png images) into our app's bundle.
In Xcode 9 however, we're given a beautiful new checkbox called "Preserve Vector Data".
This means that if our image is loaded in our code, and we ask it to display at a a larger size than it is by default, it will be scaled up at runtime by the system.
This means we can now write code like this without any quality loss at render time:
Today we're going to talk about corners. Specifically, rounded ones. In iOS 11, Apple has improved the way we can specify and work with the rounding of our views' corners. Let's take a look.
Let's begin with where we were before iOS 11.
Before iOS 11, configuring a UIView to have round corners went like this:
The clipsToBounds ensures the "clipping" takes place, and the next rounds all four corners with a radius of 8:
Pretty straightforward.
This approach works great for rounding all four corners of a view. But what if we wanted to round only some of the four corners? Previously we'd need to drop down and create our own mask layer, giving it a path we created manually. Bummer.
Now we can use the new CACornerMask option set to specify which corners we'd like rounded. Here we've asked for just the bottom two corners to be rounded with an 8 point radius:
Neat!
Before we go, there's one last thing to know about round corners in iOS 11:
They are now completely animatable! 🎉
In previous releases, this next bit of code wouldn't do anything, but in iOS 11 this works exactly as expected:
Continuing our look at the new tidbits and goodies from WWDC 2017, today we'll learn about the changes to screen edge behavior in iOS 11. Let's dive in.
What we're really talking about here is the behavior when a user drags from offscreen. This could be the user trying to pull down Notification Center from the top, or bring up Control Center from the bottom of the screen.
In past iOS releases, the system has looked at the visibility of the status bar to determine how to behave in these cases.
If we configured one of our view controllers to hide the status bar, the system would show a little "tab" UI with an arrow that the user would need to drag a second time before Notification Center or Control Center would be shown:
Keying off of the visibility of the status bar probably isn't the best way for us to "tell" the system what to do here.
In iOS 11, we've been given a wonderful new way to describe how our app should behave when the user performs these gestures.
Now, all we need to do is override this function in our view controlller:
This way, we can describe exactly which edges of the screen we'd like to allow the system gestures to behave "normally", and which we'd like to have defer for a second drag. Neat!
Finally, if the state of our view controller ever changes enough to warrant a change in this behavior, we can call the following new function to update it:
Happy WWDC 2017! Today we're beginning our look at the incredibly large list of updates and improvements announced this week with UIFontMetrics. Let's jump in.
We'll start with the problem we're trying to solve. It's all about Dynamic Type.
Our users can adjust their preferred Dynamic Type value in Settings.app to display the text in our apps larger and more prominently.
This works great when we want to use the system default font, since we can simply write some code like this:
There's always one more fun trick tucked away in UIFontMetrics, and that is scaling arbitrary values. This is great for helping us size our UI elements (for example buttons or headers) to accomodate dynamically sized fonts that live inside:
The iPhone and iPad are awesome devices with beautiful displays. Sometimes though, we might want to show parts of our app on a bigger, externalscreen.
Today we'll look at how we can do just that on iOS, let's dive in.
Before we begin let's break down what exactly we're talking about here:
UIKit exposes any external display we connect to our iPhone or iPadwith an adapter as a UIScreen. The same is also true when we turn-on Airplay Mirroring to an Apple TV.
Now, let's write some code.
We'll eventually use Notifications to learn when a screen is connected/disconnected, but many users may launch our app with a screenalready connected, so let's start by checking for that.
We'll create a couple instance properties to hold our the UIWindow and UIViewController we'll be displaying externally:
It's a cousin to UIImagePickerViewController that exposes just the basic video editing functionality from that class in a standalone, dedicated video editing view controller.
We'll download the video, rename it to something simple, and drag it into Xcode. We'll check the box next to our app in the dialog that appears, so it gets copied to our app target.
Here we've told it our path, and given it a 10 second max duration. (Pro Tip: Default is 10 minutes, set to 0 for no max).
From here we can simply present it like any other view controller:
present(editor,animated:true,completion:nil)
Neat!
The best part is all the functionality is self-contained inside the view controller.
The user can scrub through:
Trim the edges:
Then save the video back to the videoPath we set earlier:
Note:The documentation mentions UIVideoEditorController"only supporting Portrait"orientations, but it seems to work fine in all orientations.
Last but not least, we can (optionally) set a delegate on our UIVideoEditorController to get notified when the user saves or cancels (or a save fails):
UIReferenceLibraryViewController supports many different languages. New languages can be installed using the "Manage" option in the bottom-left:
Finally, it's worth noting that we get definition functionality for "free" in UITextFields via the "Look Up" item in the UIMenu that's shown when a user selects some text:
... and it's also worth noting that the view controller that appears when a user taps one of these "Look Up"menu items in a UITextField appears to be far more advanced than the what we get when presenting a plain ol' UIReferenceLibraryViewController:
The Dictionary results are still present, but we also get results across Music, Wikipedia, Movies, Websites, and even the App Store. Very cool.
(And nope, UIKit does not seem to provide a way to trigger this fancier view controller directly. Anyone interested in this should probably file a Radar.)
That's all for today. Those who want more UIKit B-Sides can check out these bites here.
Building great forms for users to enter information on iOS is tough. There's a lot of small details which are easy to get wrong. Today we'll begin looking at ways to improve this process.
First up, is navigation. 📍
Allowing for quick and simple navigation from one form field to the next can dramatically reduce friction for our users.
Last but not least, we aren't limited to a "standard" set of buttons/items in the toolbar.
We'll use the navigationFieldToolBar optional property that the library adds to UITextField and UITextView to first customize some individual properties directly:
We first covered Today Extensions way back in Bite #36. They're a great way to offer quick, glanceable information or entry-points to our app. Today we'll take a look at some lesser known features of Today Extensions, and how we can use them in our code. Let's begin.
3D Touch Shortcut Widgets
First up, is one of the newest additions to the Today Extension world: 3D Touch Homescreen Widgets.
The coolest bit is, we don't really need to do anything to "get" this. If our app has a Today Extension, the widget will automatically appear when a user 3D Touches our app's icon.
The B-Side comes into play when we have multipleToday Extensions in our app. We'll need a way to tell the system which one to display above our icon. We can do this by adding a new key to our app's Info.plist:
UIApplicationShortcutWidget
(We can also choose "Home Screen Widget" from the keys dropdown).
Neat!
Conditional Display
Today Extensions Widgets don't have to always be visible in the Today view. We can actually tell the system whether or not our widget should be displayed with this one function:
Calling this with false will hide the widget in the Today View until our app calls the function again with a true value.
Opening Our App
This one is a bit of a stretch to truly call a B-side, but it can easily be done incorrectly, so here we are.
It's quite common for a Today Extension Widget to need to open its containing app.
Apple has tightened the reigns in recent OS releases to "validate" when and how apps (and specifically Today Extensions) can open apps. Not to worry, we can use this special function on an NSExtensionContext to open a deep link into our app.
Pro Tip: Opening our own app (i.e. our app that contains the Today Extension) is just fine, but beware if we start trying to open other apps using this function, Apple may scrutinize our Today Extension further during App Review.