UIPresentationController
API. Now UIPresentationController
is by far not perfect, but it’s a huge step forward coming from UIPopoverController
and makes presenting in mixed environments much simpler and cleaner. The beauty: The view controller hierarchy is now correct, where in the older popover controller there were unconnected gaps, and it wasn’t easy to go from the contentViewController
to the parent presenter. This required inconvenient redirects when a controller inside a popover wanted to dismiss itself. RIP, UIPopoverController
. You won’t be missed.
Sometimes it’s quite useful to know inside the view controller if we are presented as a popover or modally. Of course things like a close button should be managed by the presenter, not the presented object, however there might be other changes (keyboard specifics, moving bar buttons around to have space for the close button, sizing differences). These cannot be determined by the trait classes alone. Initially I was naive and just checked for the existence of .popoverPresentationController
- but since the system is adaptive, this object will also be there when we are in a compact trait collection. Turns out, it’s quite hard to correctly detect if we are presented as a popover. Here’s my first attempt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
The evil detail is that UIViewController
lazily creates a new _UIFullscreenPresentationController
, so simply checking for .presentationController
is not always correct, as it doesn’t include the case where we are embedded as a child view controller. Now you might think I’m doing it wrong, but there are some valid cases for this. (We have a container that basically embeds other view controllers completely and just adds a switch control as top bar).
Turns out, Apple has the same problem, since after building all of that I found the (of course!) private _isInPopoverPresentation
(click for rage-tweet). Now this is interesting - let’s see how they detect this. (Code approximated as I don’t have access to UIKit’s source code)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
The Problem: Apple uses _presentationController
or _originalPresentationController
for the detection, but we only have presentationController
available - which unfortunately here lazily creates a presentation controller when first being accessed. The above is also incorrect - we have a case where we embed a controller full-screen inside another controller, and this code assumes YES, but doesn’t crawl up the parent/child view controller hierarchy, so we end up with a situation where being in a popover is assumed when it’s clearly not:
When we use the above code, everything works:
Notice how we change the background so the popover uses transparency, while modally we use a clear white. The close button is handled externally by the presentation logic. The solution we use is also not 100% correct - cases where we would present inside the context of a popover would likely be incorrectly flagged as modal presentation - but since we don’t use any of that I didn’t investigate further.
Going back, how did we do things before presentation controllers? In PSPDFKit we set a flag on the view controller, however this was prone to error. What Apple and many others are doing is simply looking at the view hierarchy if there is a _UIPopoverView
in the tree . This is a used and proven method, and something Apple uses a lot in UIKit as well, and which has quite some usage and similar implementations inside UIKit. ([UIWebDocumentView inPopover]
, [_UIPopoverView popoverViewContainingView:]
, [UITextField _inPopover]
, …)
Someone from Twitter pointed out that there’s even another way to detect if the UIPopoverPresentationController
is really used to present as popover, as we can look at the arrowDirection
- if that is set to UIPopoverArrowDirectionUnknown
it’s likely modal and not a popover.
Are there even better ways? Am I missing something here? I’m not happy with any of the above solutions. Ideally I should be able to ask the controller what his or the nearest ancestor’s adapted current modal presentation is. I’ve filed rdar://22048335 for an API extension - maybe we get this in iOS 10.
]]>NS_DESIGNATED_INITIALIZER
flag, and also added this to various framework classes. This is likely a byproduct of Swift, where the initializer call order is much more strongly enforced. This goes as far as there’s a new convenience
keyword to mark the separation of convenience vs designated initializers.
This is a good thing. It’s far too easy to break the initializer chain, even though Apple’s documentation on it is superb.
With iOS 8.3, Apple made a modification in UITableViewController
that by now, every one using this class will have seen. initWithStyle:
is now a designated initializer. And while throughout the beta period, this was the only designated one, the GM suddenly added both initWithNibName:bundle:
and initWithCoder:
to the list as well - making this class quite inconvenient to subclass.
Most of your subclasses will have their own designated initializer since they likely depend on some object at initialization time. If that’s the case, you generally want to prevent users from calling the wrong initializer, even if they are marked designated in the superclass.
A common idiom to do this is to declare them unavailable:
The above code belongs to the header and results in compile-time warnings. Since Objective-C is dynamic, this makes calling init
harder, but does not prevent you from shooting yourself in the foot. To be complete, let’s also block this in the implementation:
Combining the two makes it really hard to create objects that are not correctly initialized. So of course I also tried to apply this pattern to UITableViewController
… which results in an assert. Here’s the designated initializer chain I’d expect:
[PSPDFTableViewController initWithAnnotations:]
-> [UITableViewController initWithStyle:]
-> [UIViewController initWithNibName:bundle:]
-> [NSObject init]
.
However, Apple didn’t really play by the rules in UITableViewController
. It calls [super init]
inside initWithStyle:
. init
is overridden in UIViewController
to call initWithNibName:bundle:
since this is the designated initializer per documentation, even though it’s not annotated with the NS_DESIGNATED_INITIALIZER
decoration. This results in following call order:
[PSPDFTableViewController initWithAnnotations:]
-> [UITableViewController initWithStyle:]
-> [UIViewController init]
-> [PSPDFTableViewController initWithNibName:bundle:]
(which asserts!).
This is very unfortunate, and I assume it’s not easy to correct since there surely are apps out there who rely on this call order. We work around this by wrapping our implementation into a clang diagnostic block to ignore “-Wobjc-designated-initializers”, but it doesn’t prevent anyone from creating the controller with an invalid state. Maybe Apple fixes this conditionally in iOS 9. rdar://problem/20549233.
What other ways are there do deal with it? Did I miss something here?
1. To be correct, Clang commit r196314 landed in Xcode 5.1, so technically this already had support for designated initializers by using the objc_designated_initializer
attribute directly.
In UIKit, unlike AppKit on the Mac, there’s currently no public way to detect the first responder. There are several clever and less clever workarounds (like iterating over all views) or using a weak variable and UIApplication’s sendAction:. Of course Apple hit this issue in ResearchKit as well and their solution also uses sendAction:. If you feel like there should be an official firstResponder
method much like there is in AppKit, please help and file a radar or dupe mine (rdar://20549460). (If you wonder, of course this method exists as private API)
Apple uses a nifty macro to ensure the class is of the expected type:
#define ORKDynamicCast(x, c) ((c *) ([x isKindOfClass:[c class]] ? x : nil))
It’s a language feature in Swift and C++ has a whole variety of cast operators built into the language. I’d love to see actual language support in Objective-C as well.
Apple added Dynamic Type in iOS 7 to give the user more control about how large text in apps should be. We’re now less than two months away from iOS 9 yet many apps still ignore this, and almost no app properly reacts to changing this setting at runtime. The system sends a UIContentSizeCategoryDidChangeNotification
, however there’s no easy way to re-build the UI with a different font. Apple’s way of solving this is subclassing common view classes like UILabel
with their ORKLabel
, which fetches the new font and then invalidates its intrinsic content size to trigger a new Auto Layout pass. Similar patters are in ORKAnswerTextField/View
, ORKTableViewCell
and ORKTextButton
. This pattern however makes it hard to set custom font sizes. One could extend these classes to accept a font text style like UIFontTextStyleHeadline
to make this more flexible. Apple instead uses subclasses like ORKTapCountLabel
to customize the font size.
In Apple’s initial release, there are two radars referenced. 19528969
to work around an Auto Layout issue and 19792197
to work around an issue with tinting animated images. Of course there are no detailed entries on OpenRadar but it’s easy to read and at least the workarounds are marked as such. It will be interesting if these radars are a priority on being fixed…
All views are created in code. Apple uses a Storyboard for the example catalog, but that’s it. Apple uses the standard pattern of overriding viewDidLoad
to build UI in combination with Auto Layout and the visual format language, whenever possible.
This was particularly interesting, since my main job is working on PSPDFKit - a PDF framework for iOS and Android.. In there we have code that allows converting HTML to PDF via (ab)using UIWebView
and the printing subsystem. This is marked as experimental as we were under the impression that it’s not intended usage and more likely works by accident. However Apple’s now using the exact same technique (ORKHTMLPDFWriter) in ResearchKit, so this seems to be an acceptable way of converting HTML documents.
It’s really great to see that every class is fully annotated with NS_ASSUME_NONNULL_BEGIN/END
. This makes usage much nicer, especially with Swift, but also is great documentation in general. Time to annotate your classes as well!
Since we’re at Swift… ResearchKit is 100% Objective-C. And I’m sure this was started when Swift was already post 1.0 so time is not the reason. Then again, the example catalog is completely Swift. Objective-C is a great choice for frameworks as you can decide selectively which methods should be public and which ones private - with Swift, this is currently not yet possible.
Update: Access control actually is in Swift since 1.0, so this isn’t the reason they went with Objective-C. Maybe because of the still immature tooling? (and SourceKit crashes)
There’s no clear pattern when Apple uses _Internal
and when _Private
for private class extensions, however it’s great to see that they do try to keep the API small and only expose the necessary parts.
Large text like the consent review language is displayed by view controllers that embed web views. This is all based on UIWebView
- so far no WKWebView
is being used here. For regular text, that’s perfectly ok and probably even preferred since it’s a lot simpler to use and doesn’t spin up a separate process. On the other hand, Apple consistently uses UIAlertController
- there are no references to the legacy UIAlertView
/UIActionSheet
APIs anymore.
It’s great to see Apple adopting secure coding everywhere. They’re using a set of macros to make the code less repetitive but overall there’s nothing special about it.
There’s a bunch of interesting details on how Apple approaches accessibility support here. Notable is the ORKAccessibilityStringForVariables
macro which allows string concatenation, ignoring empty or nil strings. (sample usage)
ResearchKit contains a few checks for iOS 8.2. Why? Because HealthKit really didn’t work before that release. However instead of checking for the foundation version (fast) or using the new isOperatingSystemAtLeastVersion
method on NSProcessInfo
, they’re converting the version to float and then compare - the worst way of version checking. I went ahead and wrote a pull request to fix that. We’ll see if that gets merged :)
Yes, there are unit tests. They don’t use a Host Application, so they’re all purely model-tests. I’d love to see view/integration tests as well, but it’s a start.
If you’re wondering how Apple pulled of these nifty animations and were expecting some advanced path animation code, I have to disappoint - it’s just a set of videos. However, there’s a lot more to it. They are coordinated by ORKVisualConsentTransitionAnimator
which is powered by ORKEAGLMoviePlayerView
- complete with custom shaders. This is a lot of code to tint a video on the fly!
Overall, ResearchKit is very well done. You could critizise some naming inconsistencies, indentation or spacing, but the overall structure is good, and I’m very excited how much better it’ll get once Apple starts merging the onslaught of Pull Requests.. Writing a framework is certainly a challenge - many shortcuts one can do with writing Apps don’t apply. Follow me on Twitter for even more updates. Oh, and if you would love to work on frameworks full-time, we’re hiring.
]]>Here’s my gist with (slightly unordered) thoughts as I went deeper and deeper into the rabbit hole. If you’re curious about UIKit, you’ll find this very interesting to read.
In PSPDFKit we create a few custom windows for various features, like the global progress HUD or the custom text loupe (Dupe rdar://17265615 if you feel like this should be an official API). There’s no easy workaround to not use windows for this features (or rather, this would be a usability regression), so in the end, I’ve came up with a not-extremely terrible workaround that works on iOS 8 and doesn’t do any damage on iOS 7:
This solution “hides” the rootViewController
to basically disable any automatic rotation while the window is hidden, which perfectly solves our issue. I have to admit that I quite enjoy digging through closed source code and trying to understand the pieces bit by bit.
Want to work with me? We’re looking for smart developers over at PSPDFKit. Ping me if you’re interested!
]]>UIPopoverControllerPaintsTargetRect
in some UIKit disassembly - definitely worth investigating! Now that I finally own IDA, I did some research. Turns out there’s a hidden preferences file under /Library/Preferences/com.apple.UIKit
that UIKit queries for these settings.
I’ve used Aspects to swizzle NSUserDefaults
and enable this key when queried. This actually works, but only under iOS 7, since iOS 8 uses the newer UIPopoverPresentationController
and that one doesn’t fully support target rect drawing (or it’s compiled out in our release version of UIKit.)
(Screenshot from PSPDFKit - the Leading iOS PDF Framework. Note the purple overlay on the bookmark icon.)
Digging deeper, I found a bunch of very interesting and useful flags for debugging, which print extensive logging for touches, gestures, animations and more. I’ve listed the most interesting flags in the gist at the end of this article.
The process was easy for UIPopoverControllerPaintsTargetRect
but quite a bit harder for most other flags, as these are protected by a check to CPIsInternalDevice()
which lives in the private AppSupport.framework
. All it does is query libMobileGestalt for a few settings; checking if "InternalBuild"
is true or if alternatively "Oji6HRoPi7rH7HPdWVakuw"
is set to YES.
I’ve tried to use dlsym
to get MGSetAnswer()
and set the values manually, however this doesn’t work - it seems that only a few values are modifiable here. So instead, I’ve used Facebook’s fishhook to redirect all calls from MGGetBoolAnswer
and manually return YES if it’s queried for “InternalBuild”. Granted, we could also hook CPIsInternalDevice
instead; both will work.
Want to try for yourself? Check out my UIKitDebugging repository and add all files to your repository. Remember, that’s just for debugging and to satisfy your curiosity, don’t ship any of that.
Here’s some interesting output. The touch and gesture logging seems very useful.
There are a few other interesting flags like the infamous UISimulatedApplicationResizeGestureEnabled
, I’ve only listed the most interesting ones in the gist…
containsString:
method on NSString
for iOS 7. Apple quietly added this method to Foundation in iOS 8 - it’s a small but great addition and reduces common code ala [path rangeOfString:@"User"].location != NSNotFound
to the more convenient and readable [path containsString:@"User"]
.
Of course you could always add that via a category, and in this case everything would probably work as expected, but we really want a minimal invasive solution that only patches the runtime on iOS 7 (or below) and doesn’t do anything on iOS 8 or any future version where this is implemented.
This code is designed in a way where it won’t even be compiled if you raise the minimum deployment target to iOS 8. Using __attribute__((constructor))
is generally considered bad, but here it’s a minimal invasive addition for a legacy OS and we also want this to be called very early, so it’s the right choice.
After my initial excitement, things have stalled a bit. I shipped Aspects in our PDF framework, and people started complaining that it sometimes freezes the app, basically looping deep within the runtime, when the New Relic SDK was also linked.
Of course I tried to fix this. Contacting New Relic didn’t bring any results at first, even after two paying customers started to report the same issue. After periodically bugging them for over a month I finally got a non-canned response, pointing me to a blog entry about method swizzling.
This basically says that using method_exchangeImplementations
is really bad, and that pretty much everybody does swizzling wrong. And they indeed have a point. Regular swizzling messes not only with your brain but also with assumptions that the runtime makes. Suddenly _cmd
no longer is what it is supposed to be, and while in most cases it does not matter, there are a few cases where it does very much.
This is the swizzling helper that I’ve used during the last few years:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
This is a very common approach, with a small twist that it takes a block and uses imp_implementationWithBlock
to create an IMP trampoline out of it. Usage is as follows:
1 2 3 4 5 |
|
(Yes, Wacom’s framework for stylus support is horrible. There are way better ways to hook into touch handling, such as subclassing UIApplication
’s sendEvent:
.)
Note the cast to objc_msgSend
. While this (by luck) worked without casting in the earlier days, this will probably crash your arm64 build if you don’t cast this correctly, because the variable argument casting specifications changed. Add #define OBJC_OLD_DISPATCH_PROTOTYPES 0
to your files to make sure this is detected at compile time, or even better, use Xcode 6 and enable error checking on this:
This works as expected in most cases, but has the issue that the original implementation will be called with a different _cmd
than it expects. This can be a problem when _cmd
is actually used, such as in the touch forwarding logic. I learned this the hard way after swizzling touchesMoved:withEvent:
to inject additional logic. The app crashed with the popular doesNotRecognizeSelector:
exception.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Somehow UIKit wants to call pspdf_wacomTouchesMoved:withEvent:
on a class that I definitely did not swizzle, and so of course the runtime throws an exception. But how did we end up here? Investigating the stack trace, UIKit’s forwardTouchMethod
looks interesting. Let’s see what this actually does.
The base class for UIView
is UIResponder
, and it implements all basic touch handling:
(Note: I don’t have access to the UIKit sources, so this might not be 100% accurate. The snippets are based on disassembling UIKit and manually converting this back to C.)
1 2 3 |
|
Here it gets interesting. _cmd
is used directly in this C function that (at least the name suggests) then forwards our touches up the responder chain. But let’s keep digging, just to make sure. For curiosity’s sake, I translated the whole function, including legacy behavior. (I don’t remember any announcement where Apple changed this in iOS 5. Is this somewhere documented? Hit me up on Twitter if you know more.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
At this point I was a few hours in, digging through Apple’s touch forwarding code. You can use Hopper to read through _wantsForwardingFromResponder:toNextResponder:withEvent:
. Most of the code seems to track forwarding phases, checks for exclusiveTouch
, different windows and there’s even a dedicated _UITouchForwardingRecipient
class involved. There’s quite a lot more logic in UITouch than I would have expected.
Forwarding using _cmd
is not restricted to touch handling at all - on the Mac it’s used for mouse[Entered|Exited|Moved]:
as well.
Our naive use of method_exchangeImplementations()
broke the _cmd
assumption and resulted in a crash. How can we fix this? New Relic suggested using the direct method override. Let’s try that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
This solves our problem. We preserve the correct selector (there’s no pspdf_wacomTouchesMoved:withEvent:
method anymore) and thus UIKit’s touch forwarding works as expected. The method replacing logic is also simpler.
However, there are downsides to this approach as well. We are now modifying the touchesBegan:withEvent:
method of our custom UIView
subclass. There is no default implementation yet, so we get the IMP from UIResponder and then manually call this. Imagine if at some later point, somebody else would swizzle touchesBegan:withEvent:
on UIView
directly using the same technique. Assuming UIView
has no custom touch handling code, they would get the IMP from UIResponder and add a new method to UIView
. But then our method gets called, which already captured the IMP of UIResponder
and completely ignores the fact that we modified UIView
as well.
There are solutions to this problem, but they are extremely complex, such as CydiaSubstrate’s MSHookMessageEx, but since this requires a kernel patch (and thus a jailbreak), it’s not something you would use in an App Store app.
If you read trough the whole article and are wondering why I’m not simply subclassing the touch handlers, you are right. This is the usual approach. However we recently added stylus support for a variety of vendors, and this is built via external driver classes, so that we don’t have to “pollute” the framwork with the different approaches. Wacom is the only vendor that requires direct touch forwarding, and every vendor has it’s own way to manage touches and views. Integrating all these into a single class would result in a very hard-to-maintain class, and licensing issues would also prevent us from shipping the framework binaries directly. Furthermore, only some companies use the stylus code, so we designed this to be modular. (e.g. Dropbox just uses PSPDFKit as a Viewer, and thus doesn’t need that part.)
Further Reading:
]]>Now Aspects is a great new tool in your toolkit. It allows to call code before, instead or after the original implementation, and there’s no need to manually call super, cast objc_msgSend
or any of that other stuff you have to should do on swizzling. Use it with reason, it has a few great use cases, some are well-explaind on the GitHub page.
It’s also great for hacking and debugging. While testing the example on an iPad that still runs iOS 6, I found this exception:
// *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'On iPad, UIImagePickerController must be presented via UIPopoverController'
Right, Apple fixed that in iOS 7. But I was more curious how this is actually implemented. It’s actually quite tricky to detect if you are inside a popover or not, and sometimes this quite important to know. Has Apple some “secret sauce” they’re using here? I opened Hopper to find out.
That’s roughly their code, converted back from assembly. Interesting that there’s a _UIImagePickerControllerAllowAnySuperview
to disable this check. You have to wonder where they are using that… The check is otherwise quote straightforward. The interesting part is here: [_UIPopoverView popoverViewContainingView:self.view]
.
Let’s look up that as well…
Ha. There’s no secret sauce here. Apple is simply iterating the view hierarchy to find the _UIPopoverView
. Fair enough, it’s a simple solution. Sadly there’s no _UIPopoverView
for us mere mortals, it’s a private class.
Now, let’s test if this disassembly is actually correct! First, we’ll disable Apple’s check:
That’s all - this makes the controller work perfectly where it threw an exception before. The popover restriction was a pure could be a political one, or there are edge cases we don’t know.
Now, we want to implant our own check using Aspects. PLLibraryView
is again private, so we’ll use a runtime class lookup to hook it globally. I also commented out the property check since this would disable our own checking code.
That’s it!
This code isn’t of much use, but it’s interesting how Apple checks these things internally, and that their popover detection really is just subview querying. And while _UIPopoverView
is private, we could easily make this check working without flagging private API by testing for parts of the class name…
UITextView
on iOS 7 is a lot more powerful, since Apple switched over from using WebKit to TextKit for rendering. It’s also very much a 1.0, and has some rather terrible bugs. In fact, they go so far that people started writing replacements for the whole scrolling logic.
Of course, people reported these issues in PSPDFKit as well, so I had to find a workaround. I’m using contentInset
when the keyboard (iPhone) or another view (iPhone/iPad) goes up, which is pretty much completely ignored by UITextView
in iOS 7. This is frustrating mainly because it works perfectly in iOS 6.
At first, my solution was based on a category, but after discovering more and more needed hooks, I moved over to a subclass that automatically forwards all delegate methods. This has the advantage of more shared code, and we might be able to remove all those horrible hacks once iOS 8 comes out. I certainly hope so, and will write a few more radars.
So, what’s fixed in PSPDFTextView
?
UITextView
will now properly scroll down. Previously, you needed to add at least one character for this to happen.contentInset
. UITextView
completely ignored this.contentInset
and will update the scroll position accordingly.To enable these fixes, simply use PSPDFTextView
instead of UITextView
:
https://github.com/steipete/PSPDFTextView
This is working quite well for my use case, but there surely are edge cases where this won’t be enough (like when using rich text).
I also tried using the new textContainerInset
but this didn’t work as intended and didn’t solve my scrolling problems.
I have to give credit to countless people who searched for the same solution – this very much was a community-oriented fix. Sadly, this doesn’t seem to be a priority for Apple, since it’s still broken in iOS 7.1b3.
Please fork the repo and send a pull request if you have any ideas on how to simplify the code or find an even better workaround.
]]>Can’t stand Apple’s missing attention to detail in iOS 7. I’m just going to hack and patch this myself. pic.twitter.com/Nnd176rPlz
— Peter Steinberger (@steipete) January 4, 2014
No, I’m not talking about the offset arrow, the background – I already made peace with that. But the offset label just looks like crap. (rdar://15748568) And since it’s still there in iOS 7.1b2, let’s fix that.
First off, we need to figure out how the class is called. We already know that it’s inside the printer controller. A small peak with Reveal is quite helpful:
So UIPrinterSearchingView
is the culprit. Some more inspection shows that it’s fullscreen and the internal centering code is probably just broken or was somehow hardcoded. Let’s swizzle layoutSubviews
and fix that. When looking up the class via the iOS-Runtime-Headers, it seems quite simple, so our surgical procedure should work out fine:
Let’s run this again:
Ah, much better. pic.twitter.com/8yqoa6lrXU
— Peter Steinberger (@steipete) January 4, 2014
Done! Now obviously this is a bit risky – things could look weird if Apple greatly changes this class in iOS 8, so we should test the betas and keep an eye on this. But the code’s written defensively enough that it should not crash. I’m using some internal helpers from PSPDFKit that should be obvious to rewrite – comment on the gist if you need more info.
Best thing: The code just won’t change anything if Apple ever decides to properly fix this.
]]>Why? Because it’s fun, and it can inspire you to solve things differently. Studying the view hierarchy of complex apps can be rather revealing and it’s interesting to see how others are solving similar problems. This was one thing that took many iterations to get right in PSPDFKit, our iOS PDF framework.
So, how does this work? It’s actually super simple.
Jailbreak your device of choice. I’ve used an iPad 4 here. Make sure it runs iOS 7.0.x. Both arm7(s) and arm64 devices will work now. Don’t jailbreak a device that’s used in production. Otherwise you lose a lot of security features as well. I only jailbreak a clean device and install some apps to inspect.
Open Cydia and install OpenSSH, nano, and Cydia Substrate (previously called MobileSubstrate).
Copy the Reveal library. Find out the device IP address via Settings and execute the following in your terminal (this assumes you have installed Reveal already):
scp -r /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/Reveal.framework root@192.168.0.X:/System/Library/Frameworks
scp /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib root@192.168.0.X:/Library/MobileSubstrate/DynamicLibraries
.
For Spark Inspector, you would use scp "/Applications/Spark Inspector.app/Contents/Resources/Frameworks/SparkInspector.dylib" root@192.168.0.X:/Library/MobileSubstrate/DynamicLibraries
Note: The default SSH password on iOS is ‘alpine.’
SSH into the device and create following text file with nano /Library/MobileSubstrate/DynamicLibraries/libReveal.plist
:
{ Filter = { Bundles = ( "<App ID>" ); }; }
Previously, this worked with wildcard IDs, but this approach has problems with the updated Cydia Substrate. So simply add the App ID of the app you want to inspect, and then restart the app.
Respring with killall SpringBoard
or simply restart the device.
Done! Start your app of choice and select it in Reveal. (This should also work similary for SparkInspector.) Attaching via LLDB is a bit harder and I won’t go into details here since this could also be used to pirate apps. Google for ‘task_for_pid-allow,’ ‘debugserver,’ and ‘ldid’ if you want to try this.
Tweetbot. The action bar is simply part of the cell. And the “No Tweets Found” view is always below the UITableView. pic.twitter.com/Xp6OKlvaJh
— Peter Steinberger (@steipete) December 27, 2013
AppStore.app is super complex. SKUISoftwareSwooshCollectionViewCell. No wonder it took so long to become native. pic.twitter.com/hjFdImyqSP
— Peter Steinberger (@steipete) December 27, 2013
Chrome is basically a full-screen UIWebView with some controls on top. pic.twitter.com/CFfEM7j6vT
— Peter Steinberger (@steipete) December 27, 2013
Surprise. PS Express is all native, even the classes are well structured. “AdobeCleanBoldFontButton”. pic.twitter.com/YW1xMNPSP2
— Peter Steinberger (@steipete) December 27, 2013
Twitter. There’s still a small part of @lorenb in there. (ABCustomHitTestView, ABSubTabBar) (Also: T1 as namespace?) pic.twitter.com/R62JAY4DDQ
— Peter Steinberger (@steipete) December 27, 2013
]]>This is how UISearchDisplayController looks on iOS 7:
Pretty bad, eh? This is how it should look:
Here’s the code to fix it. It doesn’t use private API (although there is some view hierarchy fighting), and it’s fairly clean. It uses my UIKit legacy detector, so this code won’t do any harm on iOS 5/6:
I imagine one could subclass UISearchDisplayController directly and internally forward the delegate to better package this fix, but I only need it in one place so the UIViewController was a good fit.
Note that this uses some awful things like dispatch_async(dispatch_get_main_queue()
, but it shouldn’t do any harm even if Apple fixes its controller sometime in the future.
1 2 3 4 |
|
Now, this used to be three lines and now it’s four lines, because delegate
is usually weak, and once you enable the relatively new Clang warning, -Warc-repeated-use-of-weak
, you will get a warning when accessing self.delegate more than once. All in all, that’s a lot of boilerplate for a simple selector call.
In the past, I’ve used an approach similar to what Apple does with parsing the delegate when it’s being set and caching the respondsToSelector:
calls. But that’s even more boilerplate, cumbersome to update once you change a delegate, and really not worth it unless you call your delegates 100x per second.
What we really want is something like this:
1
|
|
We can use NSProxy to simply forward the method if it’s implemented. This used to be expensive, but with Leopard Apple came Fast Message Forwarding. This “can be an order of magnitude faster than regular forwarding” and doesn’t require building an NSInvocation-object.
Our custom NSProxy is really quite simple and just a few lines of code. The important part is here:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
One sad detail is that if the method is not implemented in the delegate, message forwarding will use the slow path with methodSignatureForSelector:
and forwardInvocation:
, because forwardingTargetForSelector:
interprets both ‘nil’ and ‘self’ as ‘continue with message forwarding.’
We want to make sure that implementing this in our classes is simple. Here’s the pattern I use in PSPDFKit:
1 2 3 4 5 6 7 8 9 |
|
You’ll notice a few things. First, we lie about the actual type of delegateProxy
. It’s a class of type PSTDelegateProxy
, but we declare it as the delegate type so that we don’t have to cast it every time we use it, and so that we also get compiler-checked warnings when there are typos in the selector. Notice that the proxy needs to be strongly retained.
Second, we’re using a macro to simplify things. This macro expands into following:
1 2 3 4 5 6 |
|
We keep the weak reference of the delegate directly in the PSPDFDelegateProxy; no need to save it twice. This macro only works if you name your delegate delegate
, but that should be the common case, and you could expand the macro to cover different cases. We do keep a strong reference of our proxy-object around, but this won’t hurt. Other solutions work with weak-saving NSProxy, but that’s not needed and also buggy on iOS 5.
Now we’ve already covered most of the cases. But there’s one pattern that we still need to take care of, which is optional methods that change a default return value if implemented:
1 2 3 4 5 |
|
If the proxy can’t find a delegate, it will return nil (id), NO (BOOL), 0 (int), or 0.f (float)
. Sometimes we want a different default. But again, we can perfectly solve this with some more NSProxy trickery:
1
|
|
And here’s the relevant code for the subclass that is being returned after calling YESDefault
:
1 2 3 4 5 6 7 |
|
We have to to go down to NSInvocation, which is a bit slower, but again, you won’t notice, except your delegate is called many times per second, which is quite unusual.
All code (including test cases) is on GitHub.
I’ve already implemented this almost everywhere in my iOS PDF Framework and was able to delete a lot of boilerplate code. I’m @steipete on Twitter. Looking forward to your feedback.
]]>The actual hack is mostly based on this blog post about intercepting the keyboard on iOS, and it’s not pretty. I had to modify some constants to make it work on iOS 5/6:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
|
You will also need some swizzling helpers. Here’s what I use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
And it’s actually not that easy to get right. In PSPDFKit version 1, I used the Apple-recommended way (see Apple’s photo example) of subclassing layoutSubviews. This was “good enough” until I added smart zoom in version 2 of my PDF framework. (That is, text blocks are detected and zoomed onto with a double tap, just like you’re used to in the mobile version of Safari). I noticed that the zoomToRect method did not work properly; the final rect was offset, and it was quite obvious why, since the layoutSubview-method moved around the frame of the view that was zoomed in on. And don’t try to compensate this moving – it seems relatively impossible to get right.
The next generation (PSPDFKit 2.2 and upward) used override setContentOffset: to always center the content. This works, but has the drawback that you can’t pan the view around when you’re zooming out more – it’s always fixed centered. This solution also has a very nasty hidden bug, where the UIScrollView can be “locked up” and doesn’t accept any touches anymore until you perform a pinch in/out. I’ve had Apple DTS against this and even the person helping me only came up with useless workarounds (like not using gesture recognizers… right.) It’s hard to reproduce this bug here without open sourcing half of my framework, so please just trust me on that one.
I’ve asked around on Twitter (thanks, everyone!) and most recommendations were about overriding layoutSubviews. Some suggested stuffing the view into another view and doing the centering manually (which I tried) but this has the drawback that you can scroll into the empty space when zoomed in (or you adapt the view and get the same problem as you had in layoutSubviews).
The solution that works best by far is by setting contentInset. It allows you to pan around the view when zoomed out and doesn’t expose the “lock” bug that overriding setContentOffset: had. It also works fine with using zoomToRect. There are a few gotchas when using edgeInsets as well – for example, I have a mechanism that preserves the current view state (page, zoom, contentOffset) and restores that later on. If you set contentOffset to 0,0 this will decenter your image (in contrast to methods one and two). But you can trigger re-centering with following trick:
1 2 3 |
|
I’ve made a small GitHub project that shows off all three centering techniques and zooms to a small rect on double tap (to show off the zoomToRect problem). Hopefully this helps someone save a few hours!
]]>#define UI_APPEARANCE_SELECTOR
In the simplest case, add UI_APPEARANCE_SELECTOR to your properties to inform others that this property can be set via an UIAppearance proxy. There are, however, some gotchas that are not clearly mentioned in the documentation, and it’s always interesting how something like this works behind the scenes. (This is not a tutorial – go ahead and read Apple’s documentation on UIAppearance if you’ve never used it before.)
From looking at the debugger, UIAppearance is quite smart and only applies properties before the view is added to a window:
UIAppearance is mostly for UIView subclasses, with some exceptions like UIBarItem (and UIBarButtonItem), which internally handle their respective views. For those classes, Apple implemented a custom appearance proxy (_UIBarItemAppearance).
When a custom appearance is set, _UIAppearanceRecorder will track the customizations. There are also certain optimized appearance storage classes like (_UISegmentedControlAppearanceStorage) for UISegmentedControl or _UINavigationBarAppearanceStorage for UINavigationBar.
Let’s start with a simple example, converting this class (taken from my iOS PDF SDK) to work with UIAppearance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Simply adding UI_APPEARANCE_SELECTOR will not work here. One gotcha is that UIAppearance swizzles all setters that have a default apperance, and tracks when they get changed, so that UIAppearance doesn’t override your customizations. This is a problem here, since we use setters in the initializer, and for UIAppearance it now looks as though we already customized the class ourselves. Lesson: Only use direct ivar access in the initializer for properties that comply to UI_APPEARANCE_SELECTOR:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
This class now fully works with UIAppearance. Notice that we had to do some ugly state checking (_isInitializing), because UILabel internally calls self.backgroundColor = [UIColor whiteColor] in the init, which then calls the setRectColor, which would already could as “changed” for UIAppearance. Notice the TaggingApperanceGeneralSetterIMP that Apple uses to track any change to the setter:
I’m using the following code to test the customizations:
[[PSPDFRoundedLabel appearanceWhenContainedIn:[PSCThumbnailGridViewCell class], nil] setRectColor:[UIColor colorWithRed:0.165 green:0.226 blue:0.650 alpha:0.800]];
[[PSPDFRoundedLabel appearanceWhenContainedIn:[PSCThumbnailGridViewCell class], nil] setCornerRadius:2];
We can also use the runtime at any point to query what appearance settings there are for any given class. This is only meant to be used within the debugger, since it uses private API to query _UIAppearance:
po [[NSClassFromString(@”_UIAppearance”) _appearanceForClass:[PSPDFRoundedLabel class] withContainerList:@[[PSCThumbnailGridViewCell class]]] valueForKey:@”_appearanceInvocations”]
$0 = 0x0bd08cc0 <__NSArrayM 0xbd08cc0>(
<NSInvocation: 0xbd08a60>
return value: {v} void
target: {@} 0x0
selector: {:} _UIAppearance_setRectColor:
argument 2: {@} 0xbd08210
,
<NSInvocation: 0xbd09100>
return value: {v} void
target: {@} 0x0
selector: {:} _UIAppearance_setCornerRadius:
argument 2: {f} 0.000000
)
That’s it! The class is fully compatible with UIAppearance. When using this inside a framework, you should write custom UIAppearance rules instead of manually setting the property, to allow to override those rules from the outside (remember, manually setting a property will disable it for apperance usage). +load is a good time for that. There are some more gotchas on UIAppearance, like BOOL not being supported (use NSInteger instead), and some honorable exceptions that do support appearance selectors, like DACircularProgress.
Update: As of iOS 8, BOOL is now supported for UIAppearance.
]]>While developing a new version of PSPDFKit, I started using UIMenuController more and more. The first thing you’ll notice is that it’s different from your typical target/action pattern, in that the target is missing:
1
|
|
This is actually pretty genius, in part. iOS checks if the @selector can be invoked calling canPerformAction:withSender:
, which is part of UIResponder. If the object returns NO, the system walks up the UIResponder chain until it finds an object that returns YES or nextResponder
returns nil.
A typical responder chain looks like this: UIView -> UIViewController -> UINavigationController -> UIApplication -> AppDelegate.
Now this is great in theory. For example, if you implementcopy:
on your viewController, the firstResponder can be any subview and it still works. In practice, however, I found this more limiting and annoying. And I’m not the only one.
Especially if your menus get more complex, your code is littered with selectors. So let’s write a block-based subclass. Enter PSMenuItem:
1 2 3 |
|
My naive approach was to just use one common selector internally and execute the block that is saved in the PSMenuItem subclass. The only problem: (id)sender
of the action gets called with the UIMenuController. This is wrong on so many levels, especially since UIMenuController is a singleton anyway. There’s no easy way to know what UIMenuItem has been pressed.
But since I was already committed to writing the subclass, that couldn’t stop me. We just create a unique selector for each UIMenuItem, and catch execution at a lower level.
If the runtime can’t find a selector on the current class, message forwarding is started. (That’s the tech that allows classes like NSUndoManager or NSProxy.)
Lazy method resolution: resolveInstanceMethod:
is called. If this returns YES, message sending is restarted, as the system assumes that the method has been added at runtime. We could theoretically use this and add a method that calls the block at runtime. But this would pollute the object with many new methods – not what we want.
Fast forwarding path: -(id)forwardingTargetForSelector:(SEL)sel
has been added in Leopard as a faster approach to the NSInvocation-based message forwarding. We could use this to react to our custom selector, but we would have to return an object that implements our selector (or does not throw an exception with undefined methods.) Possible candidate, but there’s something better.
Normal forwarding path: This is the “classic” message forwarding that has existed since the old days. And actually, two methods are called here: methodSignatureForSelector:
, followed by forwardInvocation:
. (The method signature is needed to build the NSInvocation.) PSMenuItem hooks into both of those methods. But let’s go step by step through PSMenuItem’s + (void)installMenuHandlerForObject:(id)object
:
1 2 3 4 5 |
|
Note the @synchronized; swizzling is not threadsafe. Also, we add an @autoreleasepool here, as this could be executed from +load or +initialize at a very early time when there’s no default NSAutoreleasePool in place yet.
class_isMetaClass
checks if object_getClass
returns a class or a metaclass. This is needed because “object” can both be an instance of a class or a class object itself, and you can’t just invoke an isKindOfClass on a Class object. If you’re wondering what a metaclass is, it’s basically a class that defines methods available on the class. CocoaWithLove has a great article on that.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Here we test if the class has already been swizzled by us with using class_getInstanceMethod
. Again, because object might be a Class already, we can’t just use respondsToSelector:
. Next, we test if we should add a handler to canBecomeFirstResponder
. This is needed to make the UIMenuController display in the first place.
Note the imp_implementationWithBlock
. This is a new method in iOS 4.3 upward, but has a much nicer syntax and is more compact than classic C functions. There’s another small annoyance: PSPDFBlockImplCast
. The syntax of imp_implementationWithBlock
was slightly changed in yet-to-be released versions of Xcode. Older versions still need the (__bridge void *)
cast; newer versions will complain and only work without.
PSPDFReplaceMethod
is a helper that first adds the new method via our pspdf_ selector name, then swizzles the original implementation with our custom implementation:
1 2 3 4 5 6 |
|
Next up, we swizzle canPerformAction:withSender:
. This is called before the UIMenuController is displayed. If we detect our custom selector (PSIsMenuItemSelector
), we return YES, else we call the original implementation. Note the tricky casting on objc_msgSend
. (We could also build an NSInvocation, but that would be much slower and needs much more code).
PSIsMenuItemSelector
is just shorthand for return [NSStringFromSelector(selector) hasPrefix:kMenuItemTrailer];
:
1 2 3 4 5 6 7 8 9 10 |
|
Next, we arrive at the method that’s called during message forwarding, when the user selects a UIMenuItem. We again check for the selector and return a faked NSMethodSignature
. If we wouldn’t return a signature here, we’d get a selector not implemented exception. "v@:@"
is the selector encoding for -(void)action:(id)sender
. v is the return type (void), the first @ is self, the : is the selector (_cmd), the @ finally is id sender. You can learn more on Apple Developer about objc type encodings.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Finally, the last piece. After methodSignatureForSelector
returns a valid NSMethodSignature, the system builds an NSInvocation object that we can handle (or not). Here we load the selector, loop through all menuItems in the UIMenuController, and finally call the block on the PSMenuItem, if found. Note that we could also extract UIMenuController from the NSInvocation itself, but since it’s a singleton, there’s no need for that.
One simple piece remains. We build up the custom selector in our initWithTitle:block:
1 2 3 4 5 6 |
|
I’ve even used a UUID to allow menu items with the same title; they otherwise would generate the same selector and would potentially call the wrong block.
Also, thanks to Mike Ash for his great Friday Q&A about Objective-C Message Forwarding.
]]>It’s a bit crude, and Xcode won’t show any warnings if you’re doing subscripting on any object now, but you know what you’re doing, right? Furthermore this is only temporary, to make it compile with Xcode 4.4. In Xcode 4.5, this snippet won’t do anything and isn’t needed anymore, since the new SDK already has those methods defined on the classes you care about. Just make sure this is in your global header file (e.g. precompiled headers).
Note that unlike literals, which are really just syntactical sugar and already work great in Xcode 4.4+ (LLVM 4.0+), subscripting actually calls into new methods. So how does this “magically defining headers” actually work?
Thanks to Mike Ash and Cédric Luthi, here’s the answer:
The subscripting compatibility shim is actually part of ARCLite. The ARCLite load function takes care of dynamically adding the four subscript methods with class_addMethod. The implementations of these methods simply call the equivalent non-subscript methods.
If you, for any reason, are not on ARC yet, you really want to force the compiler to link with libarclite using the -fobjc-arc
linker flag. This works all the way back to iOS 4.3.
Also check out the new Xcode Refactoring Assistant to convert your codebase to Modern Objective-C. It’s awesome.
Want to know more tips and tricks about iOS? Follow @steipete on Twitter.
]]>po [[UIWindow keyWindow] recursiveDescription]
is less and less useful if you just see a big bunch of UIViews.
I asked some engineers at WWDC if there’s something like recursiveDescription just for UIViewControllers, but they didn’t have a answer to that, so I finally wrote my own.
Regular output of po [[UIWindow keyWindow] recursiveDescription]
:
Pimped output:
The solution is surprisingly simple. Add the following code somewhere to your project and you’re done. As a bonus, this also lists attached childViewControllers if you run iOS 5 or above, and it will show you if a childViewController has a greater frame than its parentViewController (this is usually an easy-to-miss bug).
Note the usage of __attribute__((constructor))
. This will call the method at a very early stage on app loading (thus we need an explicit @autorelease pool):
This works with or without ARC, iOS 4.3 and above. (imp_implementationWithBlock
requires iOS 4.3, unless you want to perform some dark magic.)
For those if you that are curious, I use a private API call (_viewDelegate
), but that’s obfuscated (it would pass an AppStore review) and you really only should compile that function in debug mode anyway.
Before iOS 5, NSURLCache just saved requests to memory, even if the documentation said otherwise – the diskCapacity property was silently ignored. This led to some open-source subclasses of NSURLCache, which retrofit disk caching. Most popular is SDURLCache and my enhanced, faster fork of it. Even Apple has an example online that shows how to create a simple URLCache.
As of iOS 5, NSURLCache automatically saves responses to disk. I haven’t found anything in the release notes that confirms the change, but I tried it both with iOS 4.3/5.0/5.1 on the simulator and the device, and with every 5.x version, a disk cache file is created and populated. This is great, as many developers probably aren’t aware of this and the system just does the right thing on its own – and it’s fast:
If the Cache-Control headers indicate that this request can be cached, iOS automatically saves it to a local SQLite cache file in AppDirectory/Caches/(bundleid)/Cache.db. For example, public, max-age=31536000
marks that the request cache will be valid for a year, as max-age is listed in seconds.
The SQLite scheme for the cache looks identical to the one used in OS X:
So, why should you care? Well, the Cache.db caches any file that has a correct Cache-Control header set. Thus, if you download a PDF document, it might end up in your disk cache as well, taking up twice the memory.
The default NSURLCache will be used, with a disk limit of 20MB. You can easily test this with GDB/LLDB:
1
|
|
The memoryCapacity
defaults to 40MB, although the cache will clear itself in low-memory situations.
So for downloads that you manually save to disk, you might want to override the NSURLConnection delegate connection:willCacheResponse: and return nil:
When creating NSURLRequest using requestWithURL:cachePolicy:timeoutInterval:, you can define the cachePolicy, but this only allows you to choose if and how the cache will be read.
Available options are: only use the cache value (NSURLRequestReturnCacheDataDontLoad
); try the cache and load if different (NSURLRequestReturnCacheDataElseLoad
); or ignore the cache entirely (NSURLRequestReloadIgnoringLocalCacheData
).
The default option, if not set explicitly, is NSURLRequestUseProtocolCachePolicy
, which most of the time is equivalent to NSURLRequestReturnCacheDataElseLoad
. This uses the cache if the object hasn’t changed. There are a few other options in the enum, but those are unimplemented.
Note: There doesn’t seem a way to force caching of certain requests; connection:willCacheResponse: is only called if the response contains a Cache-Control header, according to Apple’s documentation:
The delegate receives connection:willCacheResponse: messages only for protocols that support caching.
Lastly, Apple suggests that caching can also be fine-tuned with subclassing NSURLProtocol, which indeed allows some interesting use cases, like providing a cache for UIWebView or decrypting files on the fly.
If you’re not yet using AFNetworking, you really should. It’s a big step forward compared to classical NSURLConnection handling, even if Apple recently added a few new fancy shorthands in iOS 5. In AFNetworking, your network operations are indeed subclasses of NSOperation, which allows much better control over what’s currently running, and AFHTTPClient is the perfect base class to implement any API.
]]>