In a previous post I described how to programmatically set the height of a UIWebView to fit the height of its HTML content.
This is a different approach using a Storyboard (and a little code). To make things a little bit more interesting I added an UIView that sits on top of the UIWebView and that should scroll out of view when the user scrolls the UIWebView. To make that happen we need an UIScrollView that contains the UIView and the UIWebView:
The UIView could be something like an iAd that you want to display on top of the web content but that should be scrolled out of the view when the user scrolls the web content.
Here is how to make it work:
Connect the UIWebView from the nib to an outlet in your view controller.
Disable scrolling in the UIWebView.
Set the constraints on the UIScrollView, the UIView and the UIWebView:
The UIScrollView needs a top, a bottom, a leading and a trailing constraint to the UIViewController’s view.
The UIView needs a top, a leading and a trailing constraint to the UIScrollView. It also needs a width constraint that is equal to the UIScrollView’s width to avoid horizontal scrolling (See this post for an explaination). I also add a height constraint, because I want to have the UIView to have a constant height of 100pt.
The UIWebView needs a top constraint to the UIView’s bottom, a leading, a trailing and a bottom constraint to the UIScrollview. It also needs a height constraint that we will later set to the height of the HTML content
The constraints should look like this:
Connect the UIWebView‘s height constraint to an outlet in your view controller.
Set the view controller as UIWebViewDelegate.
In webViewDidFinishLoad set the height constraint’s constant to the height of the contentSize of the scroll view inside the web view.
Start Key-Value Observing on the contentSize to change the height, when height of the web view has to change because segments of the webpage change their size without reloading the page (like accordeons, or menus). Don’t forget to stop observing when the view controller gets deallocated.
When you have a UITableView with a lot of sections a section index can be very useful to jump quickly between sections. A good example for this is you iPhone’s address book.
But sometimes you want to hide the section index until the user scrolls the table view. The problem is that you can neither hide or show the section index directly nor officially access the UIView that holds the section index.
However there is a little trick to fade the section index in and out. UITableView has two methods that allow you to set the background and the text color of the section index. When you change the alpha of those two colors according to the UITableView’s contentOffset you can fade the section index when the user scrolls:
Some people still seem to struggle when it comes to using Apple’s Auto Layout in a UIScrollView. There are a lot of questions on StackOverflow like “Why is my UIScrollView not scrolling when using AutoLayout?”
So here is a short explanation on how to use Auto Layout with a UIScrollView that should scroll vertically:
There are just a few things you have to take care of:
1. The topmost subview must have a top constraint with the UIScrollView
2. All other subviews must have a top constraint with the bottom constraint of the subview above them
3. The bottommost subview must have a bottom constraint with the UIScrollView
To ensure that the UIScrollView only scrolls vertically you have to make sure that its subviews don’t become wider than the UIScrollView.
Do not rely on left and right constraints to define the width of a subview. If for example you have a UILabel that has a lot of text and should break into several lines, it just won’t, even if you set its numberOfLines property to 0. That’s because the UIScrollView will give it enough space by allowing horizontal scrolling. So if you just set a left and right constraint on the UILabel the UIScrollView will scroll horizontal and the label will be very wide and have only 1 line.
Instead you should define a left and a width constraint. Set the width constraint to the width of the UIScrollView and the UILabel will not become wider than the UIScrollView. It will wrap into multiple lines instead.
If you follow those steps you don’t have to set the UIScrollView’s contentSize property any more to make the UIScrollView scroll. Auto Layout will handle that for you.
To make it more clear, here is an image with the constraints that you have to set:
If you are using Masonry or SnapKit here is a code example on how to set the constraints programmatically:
I just stumbled upon a bug that seems to be introduced in iOS9. When a user tries to buy a product via In-App Purchase and then cancels the process when he is asked if he really wants to buy the product a wrong error type is returned by Apple.
When the user presses “Cancel” in the UIAlertView the SKPaymentTransactionObserver method paymentQueue:updatedTransactions: gets called. The cancellation is handled like an error with the error type SKErrorPaymentCancelled. You can check for this error type because you don’t really want to show an error page when the user cancelled the purchase process. It’s not really an error but the user’s free will. So you only show an error message for the other error types:
So far, so good. Unfortunately under iOS9 when the user cancels the error type SKErrorUnknown is returned from Apple. That is unfortunate as it results in an error message being displayed when the user pressed “Cancel”. Somehow the correct error type SKErrorPaymentCancelled got lost in iOS9.
That is unfortunate, because we really don’t want to show the error message when the user cancels the purchase process. I do not see a solution for this problem as we cannot suppress all error messages of type SKErrorUnknown.
I filed a radar. Would be nice if Apple would fix that soon. Well, we know how seriously Apple treats radars, but hey, one can dream…
UPDATE: This is now fixed with the iOS9 release version
One of the new operators that comes with Swift is the Nil Coalescing Operator. In the documentation it is explained as: The nil coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil.
This operator already exited in C but for Objective-C developers it is kind of new (more about that at the bottom of this post).
With this operator assigning values to a property with a fallback to a default value becomes really concise:
Instead of doing this:
You can simply do this:
Pretty cool, isn’t it?
Actually something like this was already possible with Objective C:
When you develop a Share Extension for your iOS App many times the Extension is not suitable for all places where it could be shown to the user. For example if your Share Extension can only share URLs it does not make sense to offer it to the user when he is using the Photos App.
This is why Apple implemented the NSExtensionActivationRule field in the Extension’s Info.plist. That’s quite straightforward and not exactly rocket science, but still many tutorials on Extension do not mention this and it took me a while to find out how this works, so I’ll share this with you.
So, when you create a new Extension Xcode creates a Info.plist in the ‘YOUR_EXTENSION/Supporting Files’ folder. In there you’ll find a Dictionary with the key NSExtension.
The interesting part is the String with the key NSExtensionActivationRule. By default Xcode sets its value to TRUEPREDICATE. That’s just for development purposes and makes sure that your Extension is shown as a sharing option everywhere. This has to be removed before you submit your app the the AppStore of your app will be rejected.
So in our case we can only share an URL so we want to make sure that it does not get added to the sharing options in Apps that do not offer an URL to share (like the Photos App).
So you have to change the type of the NSExtensionActivationRule field from String to Dictionary and add one or more keys that Apple provides to define what data types your Extension supports:
Sometimes you want to display some text that contains an email address, an URL, a phone number, an address or a date. Of course you want to make these text parts interactive so that a user can tap on an URL and directly go to the linked webpage, or make a phone call when he taps on a phone number, etc.
Implementing that is really easy. You just use an UITextView and set dataDetectorTypes = UIDataDetectorTypeAll. And voilá: the UITextView now automatically detects phone numbers, links, calendar events and addresses and makes them tappable. Really nice!
However, there is one caveat: UITextView adds some insets around the text it displays. So if you want to align an UITextView with another UI element (like an UILabel) and you set both elements to the same origin.x, you will see that the text of the UILabel and the text of the UITextView are not aligned. The text of the UITextView is shifted a bit to the right. If you try to align them horizontally you will notice that it is also shifted a bit to the bottom. When you add background color to the UITextView you see that it adds some padding around it’s text. That is nice, if your UITextView has a border or a background color, or you use it to display some editable or scrollable text, but in our case it is not ideal.
Luckily there is a solution for that. UITextView has a property called textContainerInset. The default value for this inset is (top = 8, left = 0, bottom = 8, right = 0). So setting this inset to UIEdgeInsetsZero should get rid of the top and the bottom padding.
Works as expected. But there is still some padding to the left and the right of the text. The solution to get rid of that is not as obvious as setting the insets to zero. UITextView uses a NSTextContainer object to define the area in which the text is displayed. And this NSTextContainer object has a property called lineFragmentPadding. This property defines the amount by which the text is inset within line fragment rectangles, or in other words: the left and right padding of the text. The default value for this is 5.0, so setting this to 0 removes the padding.
So, now the text in the UITextView aligns beautifully with the text of the UILabel.
Creating a Share Extension in iOS8 is really easy:
In your app choose File > New > Target
Choose Application Extension > Share Extension
Enter a name for your Share Extension and press Finish
That’s all! Xcode creates everything you need for you and puts it in it’s own folder. You’ll find a ShareViewController that is a subclass of SLComposeServiceViewController. That class’ UI looks a lot like the SLComposeViewController that you’ve probably worked with when using iOS’s Social Framework (to share your app’s content to Twitter or Facebook).
In ShareViewController Xcode creates some stubs for you where you have add the code to actually share the content to your service. In my case I want to build an extension for Safari that allows the user to share the page’s URL on my service. So when the user selects my share extension from Safari I need to retrieve the page’s URL to send it to my server.
When the user starts my Share Extension and presses the “Post” button the method didSelectPost is called on the ShareViewController.
Let’s have a look at the stub method that Xcode created:
// This is called after the user selects Post.
// Do the upload of contentText and/or NSExtensionContext attachments.
// Inform the host that we're done, so it un-blocks its UI.
// Note: Alternatively you could call super's -didSelectPost,
// which will similarly complete the extension context.
That’s a lot of code for just an URL. And the worst thing: It’s not working! The completion handler of the loadItemForTypeIdentifier:options:completionHandler: is never called.
As it turns out calling completeRequestReturningItems:completionHandler: at the bottom of the method causes this problem. In Apple’s documentation it says that you are supposed to call this method to ‘Tell the host app to complete the app extension request with an array of result items’. Calling this methods dismisses the ShareViewController and deallocates it. So the NSItemProvider that contains the URL is also destroyed before it can access the URL.
So the solution is quite simple: The call to completeRequestReturningItems:completionHandler: has to be done AFTER retrieving the URL and sending it to the server. So it has to go into the completionHandler block:
The funny thing is that with an UIAlertSheet the behavior is opposite: passing an empty string as title results in an empty title section on top of the UIActionSheet. Passing nil for the title fixes this.
That was quite useful when some feature in your app could not be used, because the user disabled something in his settings. In that case you could inform the user that he had to change his settings if he wanted to use that feature and offer to open the settings directly from your app.
Unfortunately Apple decided to not longer allow this after the introduction of iOS 5.1
Good news: This feature is back with iOS8! Apple has added a NSString constant to UIKit that you are supposed to use. To avoid crashes on iOS versions below iOS8 it is a good idea to check if the constant exists on the system version the device is running. You could check if the user is running iOS8 or higher but I prefer to check directly if the constant exits rather than checking the iOS version. Who knows if Apple will decide to remove this constant again in the future…
So, to open the settings, you can use the following snippet:
If your app has it’s own settings bundle, the settings will be opened showing your app’s settings. If your app does not have a setting bundle, the main settings page will be shown.
Unfortunately there does not seem to be a way to go directly to subpages of the settings (except your own settings bundle). If you’ve found a way to do that, please let me know in the comments!
Update: Linking to a subpage of the Setting seems to work again for system versions greater or equal than iOS8. It is not officially documented by Apple but it seems to work, although I not sure how reliable this is. It works on my iOS 9.1 iPhone but not in the Simulator. So I would not recommend to use this in production.
So, with caution, you can try this to open the Keyboard Settings: