Sometimes, when you're on the go, you just want a smaller photo. Your cell signal isn't very strong, you want to snap and share something unimportant and delete the photo afterwards, or you don't want people on the internet to read notes scattered on your table that you shared on your social network of choice, or maybe you're abroad and want to conserve bandwidth on your tourist SIM card.
iOS will let you resize a photo only when you're sending an email, but don't we share photos on messengers much more often? You can also start an editor, load the photo in it and resize manually to a specific size, then save it to the Camera Roll, go back to Photos.app and share it from there. Sounds a bit tedious.
Low Res Photo is an app with just four buttons: Snap, Select, Save, Share. Yes, “choose” is better, but I want my extra S!
Snap a photo or Select it from the library. Save the smaller version of the selected image or Share its resized version.
There are no size or ratio or scale controls. The image is resized to be 800 px on its largest size, or not resized if it's smaller than that.
After reading “Profiling your Swift compilation times” I decided to try it for our project, as it's about 80% Swift and a couple of Objective-C frameworks. Frankly, dry compile time (after cleaning) is just really bad, pushing 3 minutes. Most of the time compilation is incremental and takes much less time, but the project has to be cleaned several times a day anyway, and the compile times add up.
If the linked post ever goes away, you can profile your project simply by adding the following to your Other Swift Flags in Build Settings for the intended target:
After that you do the following to get the output file culprits.txt with sorted function compile times:
xcodebuild -workspace Project.xcworkspace -scheme Project clean build | grep [1-9].[0-9]ms | sort -nr > culprits.txt
Here's an edited excerpt from our project before I started optimizing:
1172.0ms Views/BeaconMeasurementDataTableViewCell.swift:17:10 func configure(beacon: FilteredBeaconData) 1053.3ms Controllers/ClientViewController.swift:855:20 @IBAction @objc func canvasViewTapped(sender: UITapGestureRecognizer) 994.2ms Helpers/CLLocationDistance.swift:35:17 public func region() -> String? 978.2ms Helpers/CLLocationDistance.swift:35:17 public func region() -> String? 928.3ms Controllers/ClientViewController.swift:855:20 @IBAction @objc func canvasViewTapped(sender: UITapGestureRecognizer) 782.2ms Helpers/CGRectSquare.swift:24:10 func canBeInsetBy(x: CGFloat, y: CGFloat) -> Bool
As you can see, the worst function takes just over a second, and some functions get repeated, so you can edit them once for multiplied benefit. In total, there were about 70 lines with functions taking longer than 100 ms to compile. Here's the before:
$ time xcodebuild -workspace Project.xcworkspace -scheme Project clean build real 15m41.535s user 24m43.824s sys 2m59.478s
I decided to not spend much time on this as we had a different situation than the linked post's author and were not likely to get much improvement. So I edited about 10 worst functions (and found a couple of missed bugs in the process, which is an added benefit). Then I ran the test again:
real 13m7.661s user 22m24.623s sys 2m46.180s
Not much, but that is still a measurable difference: 16.3% for real, 9.4% for user and 7.4% for sys, so about a 10% improvement in total. Not bad for an hour and a half of work.
I also tried comparing the difference in compile time from Xcode when running the app on the simulator, but couldn't come to a definite conclusion: even after cleaning, Xcode compile times vary a lot and the total time stayed around the same 3 minutes. Of course, it's much faster when the libraries and some modules are already cached, usually the build takes no more than 10-15 seconds.
I encourage you to test this for your Swift project if you're disappointed with compile times, it may well be a quick and big win.
I wrote a UISplitViewController category for our app USpace which makes it behave in a slightly different way: it collapses to the right and has a wide “master” portion. When expanded on iPad, it looks like this:
I chose UISplitViewController over a custom solution because the app is universal and has to support everything from the same codebase: iPhone, iPad and iPad split-screen mode, and switch and rotate seamlessly. Another constraint is that it has to be rotation-locked on iPhone (i.e. the app should always be in portrait mode) but at the same time show a specific controller in landscape mode, which prevents us from using the Info.plist interface orientation restrictions.
Most of the custom solutions on GitHub that implement a slide-out horizontal menu do it in a very primitive way, by having a parent controller that slides two views around. USpace view hierarchy is much more variable and complicated to use such a solution, and on top of that, most of them only support iPhone. Besides, I'm a big proponent of rolling your own solution first, to test the water and figure out what features you would actually need. More often than not, you quickly end up with a minimal solution that a) fulfills all your needs b) works perfectly c) is easy to maintain.
We move pretty fast, and my first task was just to create a slide-out controller not unlike one you see on iPad when you swipe from the right side of the screen. It's called Slide Over if I'm not mistaken. So I just presented a full-screen overlay with a thin controller on the right, and with a bit of work it was working both on iPad and iPhone and was customized to our needs. We quickly grew out of it, setting up the expected view hierarchy was finicky, so I decided to switch the whole app to a UISplitViewController. It took a few days to iron out all the problems (did you know you could put a navigation controller inside another navigation controller in a specific way and have two working navigation bars on top of each other?) but finally it worked, and worked beautifully.
Today I was trying to fix one of the last bugs with this approach, and it was… strange. If you hold the iPhone horizontally (remember that the app is rotation-locked, so everything is sideways) and try opening that one controller which should be displayed in landscape mode, the navigation controller you're pops your current visible controller and only then presents the landscape-controller. In code it's just a simple presentViewController(_:). Here's a sample lldb output:
(lldb) thread backtrace * thread #1: tid = 0xb08ed2, 0x00000001859e2008 UIKit`-[UINavigationController popViewControllerAnimated:], queue = 'com.apple.main-thread', activity = 'send gesture actions', stop reason = breakpoint 1.1 * frame #0: 0x00000001859e2008 UIKit`-[UINavigationController popViewControllerAnimated:] frame #1: 0x0000000185bce11c UIKit`-[UINavigationController separateSecondaryViewControllerForSplitViewController:] + 148 frame #2: 0x0000000185e48104 UIKit`-[UISplitViewController _separateSecondaryViewControllerFromPrimaryViewController:] + 360 … frame #27: 0x000000018592dcec UIKit`-[UIViewController presentViewController:animated:completion:] + 184
I quickly figured out that when trying to present the new controller the app window rotated to landscape orientation and it prompted the wrapper UISplitViewController to split its “secondary” view controller — without asking me, apparently! I made a dive into the docs, and lo and behold, right there on the UISplitViewControllerDelegate page it said:
When you return nil from this method [ splitViewController(_:separateSecondaryViewControllerFromPrimaryViewController:) ], the split view controller calls the primary view controller’s separateSecondaryViewControllerForSplitViewController: method, giving it a chance to designate an appropriate secondary view controller. Most view controllers do nothing by default but the UINavigationController class responds by popping and returning the view controller from the top of its navigation stack.
We're very interested in the last sentence, because that is exactly what was happening. This only happened on iPhone in landscape layout, because on iPad it didn't have to split this way, or rather, it did, but it had no effect because the view hierarchy was different when it was allowed.
Still, that was not the end. I returned nil most of the time from the method mentioned in the docs, but this time I had to return a view controller if I didn't want the navigation controller to pop. I could also subclass the navigation controller (but it's not future-proof and cumbersome) or prevent it from popping in the navigation controller delegate, but it would be far from the place where the action originated, and the split view controller delegate approach allowed me to keep control in the same file, so there you go.
What I ultimately did is returned a “dummy” view controller from the “splitting” method and and did a return true for this case in the “collapsing” method. What return true means is that you tell the system that you have done what was necessary and you need no further action on its part. Basically it gives you a view controller and says: “Hey, I want to collapse this!” and you reply “Don't worry, I did everything myself!” while doing absolutely nothing. This way the “collapsing” view controller is just dumped and you never see it again. Again, if you let the system do its job, it will push this controller on top of your navigation controller in the “master” controller.
All in all, it was a fun bug to resolve. Hopefully my explanation maybe helps someone out there :)
That's how many it takes for Xcode (with XVim installed) to become slow on a file.
As you may have guessed, that is a symptom of a Massive View Controller which is begging to be refactored :) When I started that controller just a few months ago, it was a really thin wrapper about a hundred lines long.
There comes a time during the life of your Swift project when the compile times just don't cut it. There are some tweaks and knob twirling you can do to improve them, but honestly, it doesn't make that much of a difference.
I started looking around and found the deck “Swift compile time is so slow” by Masato Oshima where he claims that concatenating all the files together improved his build time sevenfold, from 6 minutes 55 seconds to a bit under a minute. “Impressive!” I thought and decided to test this approach for our project.
Now, concatenating all files to help the compiler out is not a new method, I know for certain that some game developers use it for C++ and the results are no less staggering, so this is a legitimate thing to try.
Let's get to the test results. At the time of writing our project is about 40k lines of Swift code and a dozen of external frameworks. I used the slightly modified concatenate_swift_files.sh from keychain-swift, their version disposed of the newlines we used in some of our strings so I had to edit the script to handle that. Otherwise, no changes.
The time is from tapping on Run in Xcode to the Simulator appearing.
Concatenated files: 2 minutes 8 seconds.
Normal source files (around 300 of them): 2 minutes 32 seconds.
Result: a measly 16% improvement. Not worth pursuing for our project. On top of that, the logger we use (XCGLogger) showed all debug prints originating in the concatenated file (versus a specific source file), which is logical and expected, but annoying and unhelpful.
Another small piece of software I'm sharing with you. This time it's an actual visible control!
YKProgressBar is a simple drop-in progress bar control with rounded edges all-around (ha-ha). Just copy the files YKProgressBar.[hm] into your project and you're good to go.
Here's a demo of YKProgressBar. The floating things on top are scrolled collection view cards.
If you've missed the link in the beginning, it's up on GitHub.
It's often inconvenient to use the full-blown debugger: longer cycles, blocks, whatever. When there's no concern for performance, I just throw in an NSLog.
NSLog, though, is a chore when there is anything but strings involved. I do remember the format syntax but boy is it annoying to put all those %ld, %lu, %g and everything else (which would also complain on architecture change) instead of a simple %@!
So recently I started using an NSNumber literal notation for any ints and floats:
Nothing to remember, no warnings, and gets the job done.
I put up this little category on GitHub. Feel free to use it — if you find any use for it. Below is a copy of the README file.
NSString+Ordinal is an NSString category for spelling numbers as words. Specifically, it's intended for Russian ordinal numbers, for which, as far as I know, there is no direct iOS support. The category is tailored to work with numbers 0—200 in Russian and provides sensible defaults for any other number and locale.
What it does is it spells 1, 2, 3 as первый, второй, третий (first, second, third) in Russian and one, two, three in English, which is by design to be used in a phrase like «день первый» (the first day) in Russian and “day one” in English.
The category is a single method which accepts an integer:
+ (NSString *)ordinalRepresentationWithNumber:(NSInteger)ordinal;
This is probably reinventing the wheel, but I strongly believe in bootstrapped, self-written, simple solutions first. It works well for me because I know exactly how it works.
A recent project I worked on had a focus on typography, so kerning, line spacing and spacing were all important. One of the necessary steps to achieve this was to use non-breaking spaces in strings.
Turns out, it's pretty easy, just use the Unicode representation \u00a0, which looks like this in Objective-C code:
@"This is a non-breaking\u00a0space"
Kerning, line spacing, hyphenation and other typographic features are also really interesting to implement, but they demand a longer post and illustrations, so I'll write about them later.
This is fun.
The other day I've been working on a project that uses controls with patterned textures. I've used the texture on one button, then another, and finally on a different control. When I started the app, for some reason the texture on the different control was backwards.
Have a look. The first button's background is set as:
[view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"some-image.png"]]];
And the second:
[view.layer setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"some-image.png"]].CGColor];
And here's how both of these actually look like. The UIKit one represents the “correct” orientation of the background texture.
Notice the difference: in the latter case I'm using the view's layer to set the background color, and I'm using a CGColor reference for that — where CG stands for Core Graphics. UIKit, of course, uses a coordinate system where (0,0) is at the top left. Not so for Core Graphics:
Some iOS technologies define default coordinate systems whose origin point and orientation differ from those used by UIKit. For example, Core Graphics and OpenGL ES use a coordinate system whose origin lies in the lower-left corner of the view or window and whose y-axis points upward relative to the screen.
If we look at things this way, then the texture in the latter case is positioned exactly right, if we mentally rotate it 90 degrees counter-clockwise.
There's a function to flip the coordinate system used by Core Graphics for use within UIKit, but it's usually applied only to the current context, which is not always convenient, as in this case. So bear this in mind every time you deal with Core Graphics within UIKit.