Created
June 27, 2019 01:50
-
-
Save tail-call/40f7fc21c2c96e5ecb7458546810dfca to your computer and use it in GitHub Desktop.
Dumped articles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Source: https://medium.com/@hooliooo/yet-another-ios-post-about-creating-views-programmatically-9249a7ab1e93 | |
Date: Thu, 27 Jun 2019 08:49:27 +0700 | |
#[1]publisher [2]Medium | |
[3]Become a member | |
[4]Sign in | |
(BUTTON) Get started | |
Yet Another iOS Post About Creating Views Programmatically! | |
[5]Julio Miguel | |
[6]Julio Miguel | |
(BUTTON) Follow | |
[7]Nov 21, 2016 ? 7 min read | |
I know, I know. There are numerous similar (read: identical) posts | |
about why creating views programmatically is superior or obsolete | |
(depending on the author) versus making them via Storyboards/Xibs. | |
Luckily for you, I won't go over why programmatic views are superior to | |
Storyboards or Xibs. Why? | |
Because all three methods accomplish the same thing: creating the user | |
interface of an iOS application. | |
There will be endless debates on the topic. Each method has its own | |
strengths/weaknesses, and everyone has a preference. But more | |
importantly, each method will be around for as long as iOS is alive. | |
As my finance professor used to say: "There is more than one way to get | |
to Nassau Street." | |
Why I like Programmatic Views | |
Personally, I learned more about UIView and its instance/class methods | |
when I switched to creating views programmatically more than I ever did | |
using Storyboards or Xibs. Storyboards/Xibs didn't really teach me | |
about the inner workings of UIKit, like when it calls certain UIView | |
methods. Everything was magic. As a software developer, not knowing how | |
things work is a HUGE no-no. Personally, it annoys me to no end if I | |
don't know how things work in software development. I lose sleep over | |
it. | |
After scouring the Apple Documentation, Google, StackOverflow, and the | |
[8]iOSProgramming subreddit, I really improved my understanding of: | |
1. UIKit/UIView | |
2. AutoLayout | |
3. Swift | |
4. Object Oriented Programming | |
In addition, you have finer control over what you can do with the user | |
interface if you create views programmatically. (Who doesn't like | |
absolute control over what they make?) | |
Lastly (and also what I think is most important), every single property | |
of the UIView subclasses you create are all visible in one .swift file. | |
You don't have to click each UIView in Interface Builder to see what | |
the properties are set to. Everything you modified is explicit. | |
Why did I bother making a post about a topic that already has numerous | |
articles about it? | |
I've read many articles/tutorials on programmatic views in iOS but none | |
really adhered to the Model-View-Controller pattern. | |
The tutorials were brief, and the UIView code was located in the view | |
controller (you should never be making views in the view controller). I | |
basically had to guess what to do to separate the UIView code from the | |
view controller but luckily after searching far and wide on Google, I | |
found a bit of advice that helped me get started: | |
A single UIView subclass should be created for each view controller, | |
essentially acting as a Xib that's written in Swift instead of XML. | |
The Goal Of This Post | |
To give the reader (you) an idea of how to write views programmatically | |
(using SnapKit) without breaking the Model-View-Controller design | |
pattern and preventing the creation of the Massive View Controller. | |
This isn't a beginner friendly tutorial | |
I'm assuming you know the basic project structure of a Single View | |
Application in Xcode for Swift and know what [9]Cocoapods is and how to | |
install pods using it. You should have some familiarity with retain | |
cycles and an understanding of value types vs. reference types. | |
With that said, let's get started! | |
The Tutorial! -- Basic Setup | |
Let's start our journey into programmatic views with everyone's | |
favorite view: the Login Authentication screen! | |
But before we get started, let's start with the basic setup. | |
1. Create a new project: Single View Application | |
2. Delete Main.storyboard (put in trash and permanently delete) | |
3. Go to info.plist and delete "Main storyboard file base name" | |
4. Rename ViewController.swift to LoginVC.swift | |
5. Rename class ViewController to LoginVC | |
IFRAME: [10]https://medium.com/media/d8d821f8ecd88c3e16a25e677d33f47d | |
6. Go to your AppDelegate.swift file, and add the following in the | |
didFinishLaunchingWithOptions method | |
IFRAME: [11]https://medium.com/media/f7bff3031a340b23c3d78e9e42ac44ce | |
Run the simulator. It shouldn't crash, and you should see a green | |
screen | |
7. Initialize your Podfile using pod init in the terminal at the | |
directory of the project you're using to follow along, and install | |
[12]SnapKit | |
After installing SnapKit, open up the .xcworkspace file of the project, | |
and build the project using cmd + b. That should index and compile the | |
project, and take out any false errors when interacting with Snapkit | |
pod | |
8. Create a new .swift file called LoginView, and type the following | |
boilerplate code. (When you start to override initWithFrame | |
(Objective-C jargon) initializer this is the boilerplate code the Swift | |
compiler types for you) | |
IFRAME: [13]https://medium.com/media/c097683c093296c71161e2c092a18831 | |
Now maybe you maybe thinking at this point, I'll start doing something | |
like this: | |
IFRAME: [14]https://medium.com/media/eb1856772d3b3f3bf7a1b370b2e28489 | |
You're half correct. I'm actually going to use closures to set these | |
subviews up. | |
IFRAME: [15]https://medium.com/media/120454d23f20d9b67fc763c7f33f4bbb | |
The setUpKeyboard method is a custom method I have that speeds up | |
modifying a UITextField's keyboard properties. It is an extension to | |
UITextField: | |
IFRAME: [16]https://medium.com/media/3704b7ea453971e7f4b5c9df15e78a63 | |
I also have another helper function called set(cornerRadius:_). All it | |
does is set the cornerRadius properly on a UIView instance | |
IFRAME: [17]https://medium.com/media/7144d44332e1a01e132ea588bb58a17d | |
Now you might be wondering about the closures. | |
The reason why I use them is because they perfectly encapsulate each | |
subview's properties away from other subviews' properties. This awesome | |
separation of logic is what we need to make our code readable. | |
They are also set once and only once when the subview is instantiated | |
in LoginView. How do I know this? From [18]this. | |
Another thing you might be wondering about is the lazy declaration in | |
the stackView property. The lazy keyword allows you to use self in the | |
closure. Without it we can't add other subviews in the stackView | |
closure. Also, the capture list with unowned self is important because | |
of a possible retain cycle since closures are reference types. The | |
unowned declaration ensures that doesn't happen. | |
Also you may be wondering about the various properties I'm modifying in | |
each subview, and how that might violate the DRY principle. My answer | |
is each of these properties also exist in Interface Builder, and I | |
probably am violating DRY considering these two textfields are | |
identical aside from the placeholder text. | |
You can create a helper function that takes in a string for the | |
placeholder text and sets all the other properties to identical values | |
but my personal preference is to show and explicitly modify each | |
subview's properties within the closure so a teammate looking at the | |
code knows what each property is being set to. | |
You can probably abstract the "AvenirNext-Regular" magic string I | |
created to a global string enum called Font which has all of your | |
application's fonts but for the sake of convenience I didn't do that. | |
Finally, before we can start constraining the subviews with AutoLayout | |
we have to do one more thing: add the subviews as subviews to | |
LoginView. | |
We haven't done that yet! :) | |
I typically use these helper functions to make this easy for myself: | |
IFRAME: [19]https://medium.com/media/9f21278fba2c8d2cf69ce53941ed7f7f | |
So in LoginView's initWithFrame initializer add the following: | |
IFRAME: [20]https://medium.com/media/d2ae552a582b69583890b4764c5f4948 | |
What about the other subview? They're already subviews of stackView so | |
by that logic they're in LoginView's view hierarchy already :) | |
If you don't add stackView as LoginView's subview you'll get an instant | |
crash. | |
Also I'm well aware that SnapKit takes care of the | |
[21]translatesAutoresizingMaskIntoConstraints property for you. But I | |
think it's good practice to change it yourself. | |
Now for the constraints! | |
9. In LoginView's initWithFrame initializer put this code in: | |
IFRAME: [22]https://medium.com/media/32fd3a91b58f29a74141e9548c142b88 | |
SnapKit makes it easy to create constraints. What constraints did we | |
put? | |
1. The stackView is centered in the screen | |
2. The stackView's leading edge (left edge) is constrained to the | |
superview (LoginView's left edge) and has an offset of 40.0 points | |
(which means it's 40.0 points away from LoginView's left edge) | |
3. The stackView's trailing edge (right edge) is constrained to the | |
superview (LoginView's right edge) has an inset of 40.0 points | |
(which means it's 40.0 points away LoginView's right edge) | |
The emailTextField's height is equal to 7.5% of the superview's height | |
and because the stackView's distribution property is fillEqually. | |
passwordTextField and loginButton's heights are also 7.5% of the | |
superview's height | |
You may be wondering why we're laying out the constraints code in the | |
initializer. The answer is because most of the time we need to only do | |
this once and to make sure it's only done once, do it in the | |
initializer. | |
If you have more than one of the same constraint, your application will | |
build up its memory footprint causing you problems in the future. This | |
is especially true if it involves a table or collection view full of | |
cells with duplicate constraints. | |
If you want to be extra safe use remakeConstraints instead. (It's the | |
same as makeConstraints except it removes all existing constraints of | |
the UIView before creating any. | |
We can't see what the view looks like yet because we haven't connected | |
it to LoginVC. Let's do that now. | |
10. In LoginVC, add the following code: | |
IFRAME: [23]https://medium.com/media/001d0958247948ebaeb62e9085917c26 | |
LoginVC's [24]loadView method is the key to connecting LoginView to | |
LoginVC. | |
This method is also one of the few methods in UIKit where you don't | |
call the super's implementation. | |
Now we can run simulator. Your simulator should look something like | |
this: | |
[1*LSLRhjtCvyCEDB0St26FQQ.png?q=20] | |
[1*LSLRhjtCvyCEDB0St26FQQ.png?q=20] [1*LSLRhjtCvyCEDB0St26FQQ.png] | |
11. Making the equivalent of IBOutlets. | |
So if you used storyboards/xibs you would have had to make code like | |
this at one point: | |
@IBOutlet weak var emailTextField: UITextField! | |
and connected it from the storyboard/xib | |
Programmatic Views can do something similar and more akin to Object | |
Oriented Programming :) | |
Add this to inside LoginVC (The equivalent of putting IBOutlets in the | |
view controller): | |
IFRAME: [25]https://medium.com/media/be0c6e987def72b2e40facad8c67dfa3 | |
Oh no! A forced downcast! | |
Don't worry. | |
We instantiated LoginVC's view property as an instance of LoginView in | |
the loadView method therefore, this forced downcast is safe and will | |
succeed. We also make them weak references to avoid any [26]retain | |
cycles. | |
What about IBActions? Can we make those programmatically? The answer: | |
Yup. | |
IBActions are just [27]Target Action mechanisms | |
12. Create a function to be associated with loginButton within LoginVC | |
IFRAME: [28]https://medium.com/media/b45ca00c4b3468b88049145aa2029e96 | |
So loginButtonPressed will be the action that is invoked when the | |
loginButton is pressed by the user. How do we connect this function to | |
the loginButton? | |
Doing this in viewDidLoad: | |
IFRAME: [29]https://medium.com/media/437085a9b7fd21a4e86d8fbadfa01249 | |
Congrats! You connected a function to a UIControl programmatically! | |
Now run it in the simulator and it should work :) | |
So the final code in LoginVC should be this: | |
IFRAME: [30]https://medium.com/media/856130fb88a22bdb2d370d5976b0e067 | |
Conclusion! | |
I hope you found this tutorial enjoyable and informative. I definitely | |
had fun making it. | |
Here's the [31]link to the repo for future reference! | |
If you have any questions please comment away. This was my first | |
attempt at making an iOS tutorial. Please let me know on what I can | |
improve on. | |
(BUTTON) 462 | |
* [32]Swift | |
* [33]iOS | |
* [34]Programming | |
* [35]Mobile App Development | |
(BUTTON) 462 claps | |
(BUTTON) | |
(BUTTON) | |
[36]Julio Miguel | |
Written by | |
[37]Julio Miguel | |
(BUTTON) Follow | |
Finance analyst turned iOS Developer. Trying to learn anything and everything | |
about software development in this lifetime. | |
(BUTTON) Follow | |
See responses (6) | |
References | |
Visible links: | |
1. https://plus.google.com/103654360130207659246 | |
2. https://medium.com/osd.xml | |
3. https://medium.com/membership?source=upgrade_membership---nav_full------------------------ | |
4. https://medium.com/m/signin?operation=login&redirect=https%3A%2F%2Fmedium.com%2F%40hooliooo%2Fyet-another-ios-post-about-creating-views-programmatically-9249a7ab1e93&source=--------------------------nav_reg- | |
5. https://medium.com/@hooliooo | |
6. https://medium.com/@hooliooo | |
7. https://medium.com/@hooliooo/yet-another-ios-post-about-creating-views-programmatically-9249a7ab1e93 | |
8. http://reddit.com/r/iOSProgramming/ | |
9. https://cocoapods.org/ | |
10. https://medium.com/media/d8d821f8ecd88c3e16a25e677d33f47d | |
11. https://medium.com/media/f7bff3031a340b23c3d78e9e42ac44ce | |
12. https://github.com/SnapKit/SnapKit | |
13. https://medium.com/media/c097683c093296c71161e2c092a18831 | |
14. https://medium.com/media/eb1856772d3b3f3bf7a1b370b2e28489 | |
15. https://medium.com/media/120454d23f20d9b67fc763c7f33f4bbb | |
16. https://medium.com/media/3704b7ea453971e7f4b5c9df15e78a63 | |
17. https://medium.com/media/7144d44332e1a01e132ea588bb58a17d | |
18. http://stackoverflow.com/questions/31515805/difference-between-computed-property-and-property-set-with-closure | |
19. https://medium.com/media/9f21278fba2c8d2cf69ce53941ed7f7f | |
20. https://medium.com/media/d2ae552a582b69583890b4764c5f4948 | |
21. https://developer.apple.com/reference/uikit/uiview/1622572-translatesautoresizingmaskintoco | |
22. https://medium.com/media/32fd3a91b58f29a74141e9548c142b88 | |
23. https://medium.com/media/001d0958247948ebaeb62e9085917c26 | |
24. https://developer.apple.com/reference/uikit/uiviewcontroller/1621454-loadview | |
25. https://medium.com/media/be0c6e987def72b2e40facad8c67dfa3 | |
26. https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html | |
27. https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html | |
28. https://medium.com/media/b45ca00c4b3468b88049145aa2029e96 | |
29. https://medium.com/media/437085a9b7fd21a4e86d8fbadfa01249 | |
30. https://medium.com/media/856130fb88a22bdb2d370d5976b0e067 | |
31. https://github.com/hooliooo/swift-programmatic-views | |
32. https://medium.com/tag/swift | |
33. https://medium.com/tag/ios | |
34. https://medium.com/tag/programming | |
35. https://medium.com/tag/mobile-app-development | |
36. https://medium.com/@hooliooo?source=follow_footer--------------------------follow_footer- | |
37. https://medium.com/@hooliooo?source=follow_footer--------------------------follow_footer- | |
Hidden links: | |
39. https://medium.com/ | |
40. https://medium.com/m/signin?operation=register&redirect=https%3A%2F%2Fmedium.com%2F%40hooliooo%2Fyet-another-ios-post-about-creating-views-programmatically-9249a7ab1e93&source=post_sidebar-----9249a7ab1e93---------------------clap_sidebar- | |
41. https://medium.com/m/signin?operation=register&redirect=https%3A%2F%2Fmedium.com%2F%40hooliooo%2Fyet-another-ios-post-about-creating-views-programmatically-9249a7ab1e93&source=post_sidebar--------------------------bookmark_sidebar- | |
42. https://medium.com/m/signin?operation=register&redirect=https%3A%2F%2Fmedium.com%2F%40hooliooo%2Fyet-another-ios-post-about-creating-views-programmatically-9249a7ab1e93&source=post_actions_footer-----9249a7ab1e93---------------------clap_footer- | |
43. https://medium.com/p/9249a7ab1e93/share/twitter?source=follow_footer--------------------------follow_footer- | |
44. https://medium.com/p/9249a7ab1e93/share/facebook?source=follow_footer--------------------------follow_footer- | |
45. https://medium.com/m/signin?operation=register&redirect=https%3A%2F%2Fmedium.com%2F%40hooliooo%2Fyet-another-ios-post-about-creating-views-programmatically-9249a7ab1e93&source=post_actions_footer--------------------------bookmark_sidebar- | |
46. https://medium.com/p/9249a7ab1e93/responses/show?source=follow_footer--------------------------follow_footer- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment