How to master Auto Layout Visual Format Language
Issue #47
No, you don’t
Issue #47
No, you don’t
Issue #46
Hi, here are some materials I found that are useful for learning Swift and iOS
Issue #45
You should mock a location to ensure reliable test
Go to Xcode -> File -> New -> GPX File
It looks like
1 |
|
The gpx
file is very powerful, as it allows you to specify a route with different movement speed.
Provide one or more waypoints containing a latitude/longitude pair. If you provide one
waypoint, Xcode will simulate that specific location. If you provide multiple waypoints,
Xcode will simulate a route visiting each waypoint.
Optionally provide a time element for each waypoint. Xcode will interpolate movement
at a rate of speed based on the time elapsed between each waypoint. If you do not provide
a time element, then Xcode will use a fixed rate of speed. Waypoints must be sorted by time in ascending order.
gpx
file in app target, not UITests target. Go to your app scheme -> Run -> Options
Simulator -> Debug -> Location -> Custom Location
and select that same location, just to make sure. It does not need to be the same, but I see that without Custom Location
, it does not work in UITests
1 | let map = app.maps.element(boundBy: 0) |
The wait
function is from https://github.com/onmyway133/blog/issues/44
You need to specify accessibilityIdentifier
, like
1 | class MyPin: MKAnnotationView { |
and then query for that pin. Not that it is not inside map
, it is inside app
1 | let pin = app.otherElements.matching(identifier: "myPin").element(boundBy: 0) |
accessibilityIdentifier
accessibilityIdentifier
is from UIAccessibilityIdentification
protocol. You should not use accessibilityLabel
, see https://github.com/kif-framework/KIF/issues/243
Given that accessibilityLabel is an outwardly-facing string that is actually used by accessibility screen readers (and should be localized to the device user’s language), Apple now provides an alternate property (iOS 5+) that is specifically intended for UI Automation purposes
Issue #44
Today I’m trying to run some UITest on my app, which uses Facebook login. And here are some of my notes on it.
Safari controller
, we we deal mostly with web view
for now. Starting from iOS 9+, Facebook decided to use safari
instead of native facebook app
to avoid app switching. You can read the detail here Building the Best Facebook Login Experience for People on iOS 9accessibilityIdentifier
or accessibilityLabel
Luckily, you don’t have to create your own Facebook user to test. Facebook supports test users that you can manage permissions and friends, very handy
When creating the test user, you have the option to select language. That will be the displayed language in Safari web view. I choose Norwegian
🇳🇴 for now
Here we use the default FBSDKLoginButton
1 | var showFacebookLoginFormButton: XCUIElement { |
And then tap it
1 | app.showFacebookLoginFormButton.tap() |
When going to safari Facebook form, user may have already logged in or not. So we need to handle these 2 cases. When user has logged in, Facebook will say something like “you have already logged in” or the OK
button.
The advice here is to put breakpoint and po app.staticTexts
, po app.buttons
to see which UI elements are at a certain point.
You can check for the static text, or simply just the OK
button
1 | var isAlreadyLoggedInSafari: Bool { |
But Facebook form is a webview, so its content is a bit dynamic. And UITest seems to cache content for fast query, so before checking staticTexts
, we need to wait
and refresh the cache
1 | app.clearCachedStaticTexts() |
This is the wait
function
1 | extension XCTestCase { |
But a more solid approach would be to wait for element to appear. For Facebook login form, they should display a Facebook
label after loading. So we should wait for this element
1 | extension XCTestCase { |
And call this before you do any further inspection on elements in Facebook login form
1 | wait(for: app.staticTexts["Facebook"], timeout: 5) |
After login, my app shows the main controller with a map view inside. So a basic test would be to check the existence of that map
1 | if app.isAlreadyLoggedInSafari { |
You know that when showing the map with location, Core Location
will ask for permission. So we need to handle that interruption as well. You need to ensure to call it early before the alert happens
1 | fileprivate func handleLocationPermission() { |
There is another problem, this monitor
won’t be called. So the workaround is to call app.tap()
again when the alert will happen. In my case, I call app.tap()
when my map
has been shown for 1,2 seconds, just to make sure app.tap()
is called after alert is shown
For a more detailed guide, please read https://github.com/onmyway133/blog/issues/48
In this case, we need to fill in email and password. You can take a look at the The full source code
section below. When things don’t work or po
does not show you the elements you needed, it’s probably because of caching or you need to wait until dynamic content finishes rendering.
You need to wait for element to appear
You may get Neither element nor any descendant has keyboard focus
, here are the workaround
Simulator -> Hardware -> Keyboard -> Connect Hardware Keyboard
is not checkedwait
a bit after tap1 | app.emailTextField.tap() |
The idea is to move the caret to the end of the textField, then apply each delete key
for each character, then type the next text
1 | extension XCUIElement { |
For my case, I want to test in Norwegian, so we need to find the Norwegian
option and tap on that. It is identified as static text
by UI Test
1 | var norwegianText: XCUIElement { |
1 | wait(for: app.norwegianText, timeout: 1) |
Luckily, email text field is detected by UI Test
as text field
element, so we can query for that. This uses predicate
1 | var emailTextField: XCUIElement { |
UI Test
can’t seem to identify the password text field, so we need to search for it by coordinate
1 | var passwordCoordinate: XCUICoordinate { |
This is the document for func coordinate(withNormalizedOffset normalizedOffset: CGVector) -> XCUICoordinate
Creates and returns a new coordinate with a normalized offset.
The coordinate’s screen point is computed by adding normalizedOffset multiplied by the size of the element’s frame to the origin of the element’s frame.
Then type the password
1 | app.passwordCoordinate.tap() |
We should not use app.passwordCoordinate.referencedElement
because it will point to email text field ❗️ 😢
Go to Xcode -> Product -> Perform Actions -> Test Again
to run the previous test again
1 | import XCTest |
I found these guides to cover many aspects of UITests, worth taking a look
Issue #43
Issue #39
We used to use assertionFailure
to mark programmer error or something that shouldn’t happen.
From assertionFailure
Indicates that an internal sanity check failed.
Use this function to stop the program, without impacting the performance of shipping code, when control flow is not expected to reach the call—for example, in the default case of a switch where you have knowledge that one of the other cases must be satisfied. To protect code from invalid usage in Release builds, see preconditionFailure(_:file:line:).
- In playgrounds and -Onone builds (the default for Xcode’s Debug configuration), stop program execution in a debuggable state after printing message.
- In -O builds, has no effect.
- In -Ounchecked builds, the optimizer may assume that this function is never called. Failure to satisfy that assumption is a serious programming error.
So go to your target settings, and check Optimization Level
, make sure it is not -Onone
for release configuration.
The difference between debug and release is this SWIFT_OPTIMIZATION_LEVEL
. If -Onone
then your configuration is considered debug, and assertionFailure
will crash your apps
Read more
Issue #38
The other day, I was building Anchors which needs to support iOS and macOS. What’s clever way to not use #if os(iOS) || os(tvOS)
in all files? Use typealias
This is the first version. I’m trying to support iOS 8, macOS 10.10
1 | #if os(iOS) || os(tvOS) |
But then because of LayoutGuide
, I need to bump deployment target to iOS 9, macOS 10.11. Which is not what I want. @available
to the rescue, but it will affect everything below it. The solution is to split the platform check, the first as normal, the second with @available
check
1 | #if os(iOS) || os(tvOS) |
Issue #37
I like to write UI in code, and with Auto Layout, it is an easy task. However that leaves ViewController with a lots of code. One way we can do is to separate V
from C
in MVC
, by using a dedicated view
We can do that with generic, that initialises a view and replace the view
, let’s call it root
1 | import UIKit |
Now we can have a UIView
subclass, like LoginView
1 | final class LoginView: UIView { |
And then the LoginController
1 | final class LoginController: BaseController<LoginView> { |
And this is how we declare the LoginController
1 | let loginController = LoginController() |
Issue #36
Usually in an app, we have these flows: onboarding, login, main. And we usually set OnboardingController
, LoginController
and MainController
as the root view controller respectively depending on the state.
I find it useful to have the MainController
as the container for main flow. It can be a tab controller, swipe menu controller or contains just 1 child view controller. The screens are provided by child view controllers, but the MainController
does the following jobs
We usually need to call preferredStatusBarStyle
on the parent controller. See https://stackoverflow.com/questions/19022210/preferredstatusbarstyle-isnt-called
Usually when app is brought to foreground, we need to fetch logged in user profile to see if there’s changes. We do this by listening to app did become active
in MainController
.
This can be anti pattern. But in UI Tests, for laziness, we can just use some launch arguments and check to present some specific screens to test, because MainController
is the root for main flow.
Because things originate from MainController
, things can terminate in MainController
. We can handle logout
, clear states, and tell MainController
to tell AppDelegate
to switch to another root controller
Issue #35
Auto Layout is awesome. Just declare the constraints and the views are resized accordingly to their parent ‘s bounds
changes. But sometimes it does not look good, because we have fixed values for padding, width, height, and even fixed font size.
Read more How to make Auto Layout more convenient in iOS
This can be solved by some degree using Size Class
. The idea of size class
is that we have many sets of constraints, and based on the device traits, we enabled some of them. This is more convenient to do in Storyboard (although very hard to reason about), but if we’re doing in code (my prefer way), then it is a lot of code. And a lot of code means a lot of bugs.
If you take a look at iOSRes, we see the ratio 16:9
(height:width
)
They mostly have the same ratio. So we can have a simple approach, that scale elements based on ratio. Given the fact that the designer usually designs for iPhone 6 size
, we can make that a base.
In this approach, the content will scale up or down depending on its ratio. You may argue that the idea of bigger phone is to display more, not to show the same content bigger. You may be right, in that case you need to create different constraints and different UIs. But if you want simple solutions that work, this is one of them
This is the technique I used when doing Windows Phone development
, but it applies to many platforms as well
1 | class Device { |
We can have a computed property called adjusted
that adjusts the size based on the ratio
1 | extension CGFloat { |
You can adjust as much as you want
1 | label.font = UIFont.systemFont(ofSize: 23.adjusted) |
Issue #34
In an iOS project, we often see this in AppDelegate
1 |
|
But in a Cocoa project, we see this instead
1 |
|
In this case the param is of type NSNotification
Reading Cocoa Core Competencies - Delegation
The delegate of most Cocoa framework classes is automatically registered as an observer of notifications posted by the delegating object. The delegate need only implement a notification method declared by the framework class to receive a particular notification message. Following the example above, a window object posts an NSWindowWillCloseNotification to observers but sends a windowShouldClose: message to its delegate.
So the pattern is that the delegate should strip the NS
and Notification
, like NSWindowWillCloseNotification
to windowShouldClose:
Issue #33
I see that my answer to the question What’s the meaning of Base SDK, iOS deployment target, Target, and Project in xcode gets lots of views, so I think I need to elaborate more about it
Good read
Choosing the latest SDK for your project lets you use the new APIs introduced in the OS update that corresponds to that SDK. When new functionality is added as part of a system update, the system update itself does not typically contain updated header files reflecting the change. The SDKs, however, do contain updated header files.
So, a modern App might use iOS 9 as the Target SDK, and iOS 7 as the deployment target. This means that you can run on iOS 7, iOS 8 and iOS 9, and that you have available to you any iOS 9 calls when actually running on iOS 9.
.
Each .sdk directory resembles the directory hierarchy of the operating system release it represents: It has usr, System, and Developer directories at its top level. OS X .sdk directories also contain a Library directory. Each of these directories in turn contains subdirectories with the headers and libraries that are present in the corresponding version of the operating system with Xcode installed.
.
The libraries in an iOS or OS X SDK are stubs for linking only; they do not contain executable code but just the exported symbols. SDK support works only with native build targets.
So the SDK is just like stub and header only. It means that we can use certain APIs, but on OS that does not have the real symbols for those APIs, it crashes
Swift 2 introduces available
construct that guards against failure when trying to use newer APIs.
Note that available
is runtime, not compile time. All the code is inside your executable
1 | if #available(iOS 9, OSX 10.10, *) { |
Always check to see if you are using deprecated APIs; though still available, deprecated APIs are not guaranteed to be available in the future
1 | #if (arch(i386) || arch(x86_64)) && os(iOS) |
1 | #if os(OSX) |
1 | // All the code gets inserted into executable, but is run depending on the version of the OS |
For example, suppose in Xcode you set the deployment target (minimum required version) to “OS X v10.5” and the base SDK (maximum allowed version) to “OS X v10.6”. During compilation, the compiler would weakly link interfaces that were introduced in OS X v10.6 while strongly linking interfaces defined in earlier versions of the OS. This would allow your application to run in OS X v10.5 and take advantage of newer features when available.
.
None of the (platform) frameworks is really “included in the bundle”. Instead, your app has a reference (“link”) to a framework once you add it to the “Link Binary with Library” build phase. The frameworks are pre-installed on the devices. When you run an app, all the app’s framework references are resolved by the dynamic linker (on the device), which means the framework code is loaded so your app can use it.
Updated at 2020-12-08 06:31:54
Issue #32
iOS 9 introduces new ways for your app to work better, backed by your websites
If the app is already installed on a user’s device, the banner intelligently changes its action, and tapping the banner will simply open the app. If the user doesn’t have your app on his device, tapping on the banner will take him to the app’s entry in the App Store
To add a Smart App Banner to your website, include the following meta tag in the head of each page where you’d like the banner to appear:
1 | <meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"> |
When you support universal links, iOS 9 users can tap a link to your website and get seamlessly redirected to your installed app without going through Safari. If your app isn’t installed, tapping a link to your website opens your website in Safari.
If some or all of your app’s content is also available on your website, you can use web markup to give users access to your content in search results. Using web markup lets the Applebot web crawler index your content in Apple’s server-side index, which makes it available to all iOS users in Spotlight and Safari search results.
Shared web credentials is a programming interface that enables native iOS apps to share credentials with their website counterparts. For example, a user may log in to a website in Safari, entering a user name and password, and save those credentials using the iCloud Keychain. Later, the user may run a native app from the same developer, and instead of the app requiring the user to reenter a user name and password, shared web credentials gives it access to the credentials that were entered earlier in Safari.
Issue #31
I’m very happy to be on open source movement, and it ‘ll be great to hear about what people have achieved
And @merowing_ also mentioned in Writing Xcode plugin in Swift
Attribution
Writing this was much simpler because I was able to look at other people plugins, mostly those related to console, without them being open sourcing it would be more work to figure this stuff out with hopper.
Open source helps us move forward, learn and share together
Luke: Is the dark side stronger?
Yoda: No, no, no. Quicker, easier, more seductive.
It’s a pain to see plagiarism around
Open source softwares are in fact intellectual properties, and the authors should get acknowledgement for the work that they do.
It’s not fair to take the credit of other’s work and not giving any attribution
By its nature, open source software has a unique relationship with intellectual property rights
One thing that’s not up for debate in most circles is that it’s dishonest and disingenuous to take someone else’s project, modify it slightly, and call it your own.
Further, regardless of whether or not a project crosses that line, it must (by the terms of most open source licenses) acknowledge the original work/author.
And the reaction
It’s always sad to see blatant plagiarism, and I think it really hurts the community more than the author itself. It gives people a good reason to keep the sources private.
I often hear people say that
It is easier to find good developer than developer with good attitude
Github also states that
We understand and agree that copying others’ work without permission goes against the spirit of the open source community
Is it MIT ‘s fault? Definitely no
False choice. Giving up freedom does not lead to more security, just less freedom.
Issue #30
Haskell is notorious for currying
, and Swift has currying, too
I love ReactiveCocoa, RxSwift and I always take time to dig into it. The other day, I was practise making Signal based on this talk UIKonf 2015 - Jens Ravens: Functional Reactive Programming without Black Magic
Take a look at my repo Signal
I was making a filter for a Signal
. The idea of filter is that we should update signal if the Event is Next with right filtered value
Signal.swift
1 | public func filter(f: T -> Bool) -> Signal<T>{ |
But having Event
as another monad, I think it should be more encapsulated if that switching logic gets moved into the Event
. Here the filter takes 2 params
Event.swift
1 | func filter(f: T -> Bool, callback: (Event<T> -> Void)) { |
Signal.swift
1 | public func filter(f: T -> Bool) -> Signal<T> { |
With currying, we can make filter
a more abstract function, and defer the decision to pass the callback
param. It is a little carried away but I find it helpful this way
Now filter
accepts 1 param, and it returns a function that takes callback
as its param
Event.swift
1 | func filter(f: T -> Bool) -> ((Event<T> -> Void) -> Void) { |
Signal.swift
1 | public func filter(f: T -> Bool) -> Signal<T> { |
Swift 2 supports curry syntax function
1 |
|
You may want to find out
Issue #29
Here are my notes for working with Push Notification, updated for iOS 9
registerForRemoteNotificationTypes
is deprecated in iOS 8+
1 | UIApplication.sharedApplication().registerForRemoteNotifications() |
If your app displays alerts, play sounds, or badges its icon, you must call this method during your launch cycle to request permission to alert the user in these ways
1 | let types: UIUserNotificationType = [.Badge, .Sound, .Alert] |
You don’t need to wait for registerUserNotificationSettings
to callback before calling registerForRemoteNotifications
Never cache a device token; always get the token from the system whenever you need it. If your app previously registered for remote notifications, calling the registerForRemoteNotifications method again does not incur any additional overhead, and iOS returns the existing device token to your app delegate immediately. In addition, iOS calls your delegate method any time the device token changes, not just in response to your app registering or re-registering
The user can change the notification settings for your app at any time using the Settings app. Because settings can change, always call the registerUserNotificationSettings: at launch time and use the application:didRegisterUserNotificationSettings: method to get the response. If the user disallows specific notification types, avoid using those types when configuring local and remote notifications for your app.
About application:didReceiveRemoteNotification:
Implement the application:didReceiveRemoteNotification:fetchCompletionHandler: method instead of this one whenever possible. If your delegate implements both methods, the app object calls the application:didReceiveRemoteNotification:fetchCompletionHandler: method.
If the app is not running when a remote notification arrives, the method launches the app and provides the appropriate information in the launch options dictionary. The app does not call this method to handle that remote notification. Instead, your implementation of the application:willFinishLaunchingWithOptions: or application:didFinishLaunchingWithOptions: method needs to get the remote notification payload data and respond appropriately.
About application:didReceiveRemoteNotification:fetchCompletionHandler:
This is for silent push notification with content-available
Unlike the application:didReceiveRemoteNotification: method, which is called only when your app is running in the foreground, the system calls this method when your app is running in the foreground or background
In addition, if you enabled the remote notifications background mode, the system launches your app (or wakes it from the suspended state) and puts it in the background state when a push notification arrives. However, the system does not automatically launch your app if the user has force-quit it. In that situation, the user must relaunch your app or restart the device before the system attempts to launch your app automatically again.
If the user opens your app from the system-displayed alert, the system may call this method again when your app is about to enter the foreground so that you can update your user interface and display information pertaining to the notification.
Usually, the use of push notification is to display a specific article, a specific DetailViewController, … in your app. So the good practices are
1 | - func handlePushNotification(userInfo: NSDictionary) { |
Here we create another method `handlePushNotification:`` to handle push notification. When you receive push notification, 3 cases can occur
Loud push
application:didReceiveRemoteNotification:fetchCompletionHandler:
calledSilent push
application:didReceiveRemoteNotification:fetchCompletionHandler:
calledLoud push
application:didReceiveRemoteNotification:fetchCompletionHandler:
calledSilent push
application:didReceiveRemoteNotification:fetchCompletionHandler:
called. If app is suspended, its state changed to UIApplicationStateBackground
application:didReceiveRemoteNotification:fetchCompletionHandler:
calledLoud push
application:didFinishLaunchingWithOptions:
with launchOptions
, application:didReceiveRemoteNotification:fetchCompletionHandler:
calledapplication:didFinishLaunchingWithOptions:
is called with launchOptions
set to nilSilent push
application:didReceiveRemoteNotification:fetchCompletionHandler:
called. If app was not killed by user, it is woke up and state changed to UIApplicationStateInactive
.application:didFinishLaunchingWithOptions:
with launchOptions
, application:didReceiveRemoteNotification:fetchCompletionHandler:
calledapplication:didFinishLaunchingWithOptions:
is called with launchOptions
set to nilSystem alert only show if the payload contains “alert”
1 | { |
For now I see that silent push must contain “sound” for application:didReceiveRemoteNotification:fetchCompletionHandler:
to be called when app is in background
1 | { |
Issue #28
The idea of Signal may originate from Elm Reactivity, and it has now been widely adopted in iOS
I once asked What are examples of hot and cold signal in ReactiveCocoa?
Whether it is hot vs cold, Signal vs Signal Producer, Observable vs Enumerable, … it’s good to understand how it gets implemented, so that to have a good sense of how they work
Basically, Signal and its Result are just monads, which are thing that can be mapped and chained.
Signal makes use of deferred execution callback blocks, and push vs pull
is just how the Signal updates its value and the order the callbacks are called
Execution callback block is that we pass a function to another function, and it will get called when appropriated
Monad can be in either sync or async mode. Sync is easier to understand, but async is somewhat you’re already familiar and used in practice
Basically,
return
Here is an example of a simple function
1 | // Sync |
Here is an example of Event
1 | // Sync |
Take a look at my Push Signal, called Signal, it is like how Promise A+ Then works
1 | public final class Signal<T> { |
1 | let signal = Signal<String>() |
Given a chained signals like this
A -(map)-> B -(flatMap)-> C -(flatMap)-> D -(subscribe)
Take a look at my Pull Signal, called Future
Here operation
is a task, when called and completed, will notify its completion
1 | public struct Future<T> { |
1 | let _ = Future<String> { completion in |
Given a chained signals like this
A -(map)-> B -(flatMap)-> C -(flatMap)-> D -(subscribe)
Issue #27
Stretchy header is cool. People are familiar with changing frames to achieve this, like Design Teardown: Stretchy Headers. But with Auto Layout, we can achieve this with much nicer declarative constraints
The demo project is StretchyHeader
I use SnapKit to make it clear what constraints we need
The scrollView
should pin its 4 edges to the ViewController 's view
1 | func setupScrollView() { |
The scrollViewContentView
must pin its 4 edges to the scrollView
to help determine scrollView
contentSize
The height of scrollViewContentView
is determined by its subviews. The subviews inside must pin their top
and bottom
to the scrollViewContentView
1 | func setupScrollViewContentView() { |
The header
must pin its top to the scrollView
parent, which is the ViewController 's view
Read the title
section, you ‘ll see that in order to make header
stretchy, it must be pinned top and bottom
But if we scroll up, there will be a constraint conflict between these pinned top
and bottom
constraints
So we must declare headerTopConstraint
priority as 999, and headerLessThanTopConstraint
1 | func setupHeader() { |
The title
must pin its top to the scrollViewContentView
to help determine scrollViewContentView
height
The title
must also pin its top the header
bottom in order to make header
stretchy
1 | func setupTitleLabel() { |
The header is always pinned to the top, unless you adjust it, here in scrollViewDidScroll
Here I use Constraint
, which is a class from SnapKit
, but the idea is to change the constant
of the NSLayoutConstraint
1 | func scrollViewDidScroll(scrollView: UIScrollView) { |
By the way, did you just learn the story of One Piece :]
Issue #26
When working on Scale I think it’s good to have a way to group the digit so that it is easier to reason
Luckily, Swift already supports this. See The Swift Programming Language - Numeric Literals
Numeric literals can contain extra formatting to make them easier to read. Both integers and floats can be padded with extra zeros and can contain underscores to help with readability. Neither type of formatting affects the underlying value of the literal
1 | let paddedDouble = 000123.456 |
Talking about grouping digits after the decimal point, it is interesting too Convention of digit grouping after decimal point
So now we have
1 | public enum MetricUnit: Double { |
Issue #25
We know Ant Man is Hank Pym
1 | struct AntManSuit { |
Everytime HankPym is created, he always uses the Ant Man suit. This time he is so coupled to the role Ant Man
Well, he does not have to be too dependent on the Ant Man suit. We know Hank Pym is a genius scientist, he has more suits to use. Let’s make it decoupled
Using Dependency Injection
1 | protocol Suit { |
Now Hank Pym can be more flexible on which suit to use.
The technique we just saw is called Dependency Injection
, in which Hank Pym does not need to create the Suit, it will be provided through constructor or property.
In the first example, Hank Pym is dependent on the concrete implementation of the Suit
In the second example, both Hank Pym and the suits are dependent on the Suit protocol. This way Hank Pym only knows about the Suit protocol, and future suits must be crafted to that it conforms to the Suit protocol
This way the dependency is inverted
High level modules should not depend upon low level modules. Both should depend upon abstractions.
What is the high level policy? It is the abstractions that underlie the application, the
truths that do not vary when the details are changed
You may ask yourself Why is Inversion of Control named that way?
People said “the framework calls you but you call the library”
See What is Inversion of Control?
For example, in an old school menu, you might have:
1 | print "enter your name" |
thereby controlling the flow of user interaction.
In a GUI program or some such, instead we say
1 | when the user types in field a, store it in NAME |
You how have a brief understanding of how IoC means
In the 2nd example of the Suit protocol, you can see how there is a inversion of control. What if there is a container that contains all the Suit conformances?
Let’s use my Resolver
1 | let resolver = Resolver() |
Quite helpful, right? :]
Actually, IoC container helps you more than that.
There are some IoC containers in Swift
1 | let container = Container() |
Swinject requires explicit type declaration. It has SwinjectStoryboard, which helps configuring the dependency for your view controller
Dip leverages generic and encourage protocols
1 | container.register { ServiceImp() as Service } |
You ‘ll learn a lot just by reading Dip source code, on how factory and factory type are stored and checked using generic
1 | public func resolve<T, F>(tag tag: Tag? = nil, builder: F->T) throws -> T { |
You may have discovered, that the idea of all those framework is to use closure as factory method
1 | let factory = { |
All we have to do is to store these factories closure
Take a look at my gist SimpleResolver.swift
1 | class SimpleResolver { |
Issue #24
There is Lighter View Controllers, and there is Lighter AppDelegate, too
Since working with iOS, I really like the delegate pattern, in which it helps us defer the decision to another party.
The iOS application delegates its event to AppDelegate, which over time will be a big mess. Usually, the AppDelegate is where you put your root view controller setup, crash tracking, push notification, debugging, … and we just somehow violent the Single Responsibility principle. Moreover, it makes us hard to reason about code in AppDelegate
I like to think of each task in AppDelegate as a service. And the AppDelegate distributes the events into each service via ServiceDispatcher. Simple plain old composition and looping
I tend to have RootService as a place to setup root view controllers
It looks like this
ServiceDispatcher.swift
1 | class ServiceDispatcher : NSObject, UIApplicationDelegate { |
RootService.swift
1 | class RootService : NSObject, UIApplicationDelegate { |
AppDelegate.swift
1 |
|
I have more services like DebugService, PushNotificationService, CrashTrackingService, …
The downside to this approach is that in real life, there will be dependencies between those services, like that UserService must be called before RootService? In this case, I have to use comment to explain why I have that decision, which is hard for newcomers to understand at first. Take a look at How to Move Bootstrapping Code Out of AppDelegate for how dependencies are managed
JSDecoupledAppDelegate comes with another approach, in which service events are named according to the functions, like appStateDelegate
, appDefaultOrientationDelegate
, watchInteractionDelegate
, …
But for me, Service and ServiceDispatcher suit my need
Issue #23
Returns whether the constraints impacting the layout of the view incompletely specify the location of the view.
This method randomly changes the frame of a view with an ambiguous layout between its different valid values, causing the view to move in the interface. This makes it easy to visually identify what the valid frames are and may enable the developer to discern what constraints need to be added to the layout to fully specify a location for the view.
This returns a string describing the whole view tree which tells you when a view has an ambiguous layout.
The name that identifies the constraint.
https://forums.developer.apple.com/thread/63811
Issue #22
In the beginning, people use frame and Autoresizing Mask, then they use Auto Layout, then iOS 9 encourages them to use NSLayoutAnchor
, UILayoutGuide
and UIStackView
For more convenient Auto Layout, check How to make Auto Layout more convenient in iOS and Anchors
The NSLayoutAnchor class is a factory class for creating NSLayoutConstraint objects using a fluent API. Use these constraints to programmatically define your layout using Auto Layout.
It has 3 subclasses
NSLayoutDimension
NSLayoutXAxisAnchor
1 | // This constraint generates an incompatible pointer type warning |
NSLayoutYAxisAnchor
1 | // This constraint generates an incompatible pointer type warning |
Previously, we used dummy views to aid constraints. Now we use UILayoutGuide
See full gist
1 | let space1 = UILayoutGuide() |
See the full gist
1 | let container = UILayoutGuide() |
Margins are now represented as layoutMarginsGuide
, a subclass of UILayoutGuide
In the container example, we saw how we must use NSLayoutConstraint
with the topLayoutGuide
. topLayoutGuide
and bottomLayoutGuide
are object conforming to UILayoutSupport
protocol
The layout guide defines a rectangular space in its owning view’s coordinate system. This property contains a valid CGRect value by the time its owning view’s layoutSubviews method is called.
In the above container example, the container layout guide frame is
1 | (16.0, 40.0, 343.0, 21.0) |
See Piano on Github on how to create a Piano using UILayoutGuide
, NSLayoutAnchor
and UIStackView
Issue #20
The other day I was doing refresh control, and I saw this Swift Protocols with Default Implementations as UI Mixins
1 | extension Refreshable where Self: UIViewController |
Protocol extension is cool but somehow I’m not a fan of it. I always consider composition first, to extract the specific task to one entity that does that well. It looks like this
1 | class RefreshHandler: NSObject { |
It is a bit Rx
, we can use block if we like, but the idea is we can declare this RefreshHandler
and use it everywhere we want
1 | refreshHandler = RefreshHandler(view: scrollView) |
Issue #19
We need to care about security nowadays, here are some links I find useful to read more about this matter
Detecting languages and framework
iOS Security
Private frameworks
Private frameworks
Updated at 2021-02-23 10:56:28
Issue #18
There are many classes that designed to be used as singleton, like UserDefaults.standard
, FileManager.default
, NotificationCenter.default
or even our own classes like UserManager
, Storage
, … Singleton is a design patter and has its own use case, sometimes we still need to use it. But if we are to use singleton, we should just use 1, and group all other singleton under this single singleton. Thanks to Vadym for showing this to me
Swift makes it extremely easy to make singleton, let name it App
then we have a single point of control for all the singletons
1 | struct App { |
This is where we have configurations for staging and production environment, those can be client key, Firebase
configuration, analytics keys, …
I use Compass to do central navigation, and there should be 1 Navigator
that does the job
Sometime we rely on a singleton to do our job, to make dependencies clear and testing easier, we need to inject this singleton, and leverage Swift default parameter, thanks to John for showing this to me
Here is an example of a ViewModel
that relies on networking
1 | class ProfileViewModel { |
Issue #17
I always forget how to write correct #available(
or #if swift(>=3.0)
or just lazy to write required init?(coder aDecoder: NSCoder)
every time I make a subclass. That’s why I made SwiftSnippets to save time for these tedious tasks. Installation is easy with script, so you should give it a try.
I can’t recommend this enough, it saves me tons of time