One, foreword
Swift, version 4.0
Xcode version 9.2
I was going to write about something else this week, but I couldn’t help but use Storyboard (SB) or Interface Builder (IB) when building the Demo project, so I thought about writing an article about it.
Here do not discuss the use of this way is good or bad, we have different opinions, different wisdom, Maoshen’s article link in the postscript, my point of view and he consistent.
Storyboard basics
This part is aimed at readers who have never used SB at all, extremely basic, familiar straight skip!
2.1 An overview of goals achieved
Here is what I need to accomplish in this section, a display of the game and adding the game.
2.2 Interface Recognition
To create a Demo project, click main. storyboard and the following screen will appear:
First of all, we need to understand several important areas in SB. Here are the names as I understand them, just to briefly explain the purpose of the areas, we will use these areas in detail later, as shown in the figure above:
- 1. In the menu navigation area, add controllers and jump between controllers
Segue
And the controls and layout on the controller and so on are displayed here. - 2. Work display area: you can add controls and preview layout controls to each controller here.
- 3, configuration area: can be here
SB
Associate with code files and view the associated information. You can also configure control properties directly here, and so on. - 4, layout area: the surface has a lot of models to choose, can choose models and direction directly, zone 2 will be according to the selection of model and the automatic layout preview directly controls the effect of the display and layout, add and subtract in the middle of the symbol can zoom in and out the contents of the area 2, in the top right corner of the buttons can be automatic layout operation.
- 5, control area, we can directly select the control here, and then drag into area 2.
The observant reader will notice that in region 1 the controller is underneath a scene, and in SB the scene corresponds to a controller. There is also a gray arrow in area 2, which indicates that the controller is the entry point to the current SB file, more on this later.
2.3 Adding Controls
Drag a UIView control directly from the control area onto the controller, and then directly configure the background color to gray in the Attributes Inspector area (click the wedge-shaped button in the Configuration area). If the color you want is not displayed in the menu bar, click Other. There are several ways to configure the color, such as RGB and hexadecimal color.
There are other properties that can be configured in this area, such as the UILabel control font, font color, etc., but I won’t go into the details.
Readers will have noticed that when you drag and drop the control, the controller has an auxiliary dotted line that reminds you of its position relative to other views. One of the lines shown in the figure is the center line of the superview.
2.4 Layout Controls
Explain the automatic layout operations shown above:
- 1. Select the control and click
Align
Button, checkHorizontalliy in Container
和Vertically in Container
Then add the two layouts horizontally and vertically centered relative to the superview - 2. Then click
Add New Constraints
Button, AddWidth
和Height
The constraints are both 200.
At this point the layout is complete, because the size and location have been determined. Looking at the figure above, when I add only the Align constraint, the interface appears red, which means the constraint is incomplete. A small red dot with an arrow appears at the top of the menu bar, which you can click to see which constraints are not completed. There are other constraint options that readers can try for themselves.
- 3. At the end of the picture, I am
Size inspector
Area (click the small ruler button in the configuration area) double-click the width constraint to enter the details configuration interface, where you can modify the constraint twice. Click on the menu bar constraint to enter the same interface.
Here’s another way to do automatic layout, as shown:
Hold Down Ctrl, then select the gray View, and move the mouse pointer to bring up a line, drag it to the control you want to lay out relative to it, select the superview in the image, and a menu appears for you to select constraints. The same can be done directly from the menu bar. Even if there are many controls on the controller and it is not easy to select, you can drag them directly from the controller to the control on the menu bar.
This part of the operation is very simple, but requires knowledge of automatic layout.
2.5 Start a TableView interface
Select the View Controller Scene from the menu bar and hit delete on the keyboard to delete the Controller we tinkered with.
Drag a UITabBarController from the control area to the work show area:
In the blank area of the work display, double click the left mouse button and single click the right mouse button, you can zoom in and out of the display content.
As shown, UITabBarController (which, like UINavigationController, is a container controller) will have two child controllers with two arrows pointing to them from the TabBarController, The term for this arrow is a Segue, and this is a Relationship Segue, which is the Relationship between controllers.
Delete the item1 controller, drag out a UITableViewController, and make it a child of UINavigationController, Make UINavigationController a child of UITabBarController as shown in the following figure:
You can also drag a UINavigationController and hold down Control drag and select View Controllers. But I find it easier to click on the Editor.
Let’s go to the UITableViewController and see that there is a Prototype Cell that corresponds to Static Cells, which, as the name suggests, is Static. Cannot be recycled and can only be used on UITableViewController.
In the red box, they are the same as the four types of cells officially provided in our code implementation, but here we need to customize them. Here is what they look like after completion.
This may be a bit of a hassle for readers who have not been exposed to IB, so describe it in detail.
Select the Cell and change the height of the Cell to 120 in the Size Inspector area in the upper right corner. The height setting here is only convenient for us to layout, but not the actual height shown.
Drag a UIImageView control into the Cell to layout it.
In iOS8, there are new features that allow cells to adjust their own height, so not only do we need to determine our position and size, but we also need to feed our size back to the Cell to adjust its height, which will be used in detail later.
Relative to the superview:
20 to the right, 10 to the top and 100 to the width and height, we have our position and size determined, but to let the Cell know how high we are, we need to set a distance from the bottom. This way the Cell knows how high it needs to be displayed. There is a slight problem with the setting of the bottom distance based on our target appearance, which will be corrected later.
Continue dragging a UILable into the Cell.
Relative to the superview: 15 to the left, 10 to the top
Relative to UIImageView: 10 to its left
And then there’s the rub. Drag a UILable into the Cell.
Relative to the superview: 15 to the left, 10 to the bottom.
Relative to Game Name Label: 10 away from its bottom.
Relative to UIImageView: 10 to its left.
Logically speaking, this is fine, because there are constraints on the upper, lower, left, and right. Why?
Let’s click on the little red dot in the red box to see:
Controls like UILabel and UIButton have a feature that ADAPTS its size to its content.
As shown in the figure, when the two labels feed back their size to the Cell, the Cell will also feed back its size to the two labels, which will cause two problems:
-
1. If the Cell height is larger than the required content feedback height, which part of the content should be stretched?
-
2. If the Cell height is smaller than the required height for content feedback, which part of the content should be compressed?
This is where Content Hugging and Content Compression Resistance in AutoLayout come in.
- 1.
Content Hugging Priority
: The higher the value of this property, the less likely it is to be stretched. - 2.
Content Compression Resistance
: Corresponding to the second case above, the higher the value of this attribute, the less easily it can be compressed.
Obviously, the reason for the error is that the height of the Cell is larger than the height of the content of the two labels. In the first case, we let the Game Name Label not stretch. Increase its Content Hugging Priority (default: 251) than the other Label (to 252).
This problem was solved, but a new one arose:
Because the Game Detail Label is stretched, the content is centered, which looks weird. I’ve seen some discussions about putting labels on top. But that’s not the solution here. Remember earlier when you said you could edit constraints twice? Select the Bottom constraint of the Game Detail Label, select it in the menu area or find it in the small ruler icon area and double-click it to display the following interface:
Here is the Relation option, click on it and you can see:
That’s right we chose to make this constraint greater than or equal to 10:
It looks like it’s done. Back when I first added the UIImageView constraint, I said there was a slight problem, the UIImageView constraint forced the Cell to be 120 height. The second case above occurs when the Label content is too many newlines over 120, and the Cell height is not complete enough to display the content, which is obviously not the desired result. So change the UIImageView’s Bottom constraint to be 10 or more away from the Bottom, that’s where the layout ends, and don’t forget to set the identifier:
To associate SB with the code, create a gamevc. swift file inherited from UITableController, a GameCell. Swift file inherited from UITableViewCell, and a data model game. swift file. Then select file associations in SB in turn:
Continue to associate properties and code in SB:
You can also directly select the Control from the controller and hold down Control and drag the wire, so I’m not going to do any more examples here, it’s not just property wire, like UIButton can directly wire a click-response method and so on. The connected inspectors can be viewed on the Connections Inspectors (button with a circle containing an arrow) :
Note: associating a control property multiple times or with other error associations can cause runtime crashes. This is the most common problem for beginners. If the name is incorrectly written, you need to cancel the previous association and re-associate it.
In the game. swift file:
struct Game {
let name: String
let detail: String
let pictureName: String
static func getData(a)- > [Game] {
return [
Game(name: "绝地求生",
detail: "Fight of the Gods.",
pictureName: "game_one"),
Game(name: League of Legends,
detail: League of Legends (LOL) is a MOBA competitive online game developed by Riot Games in the US and operated by Tencent's game agency in mainland China. The game has hundreds of character heroes and features such as a ranking system and rune system. "Hero alliance" is committed to promoting the development of the global esports, in addition to the linkage of each division to develop professional league, build system of e-sports, be held each year "season in the championship" "global finals" All Star all-star game "three world-class competition, won the love of hundreds of millions of players, formed its own unique e-sports culture",
pictureName: "game_two")]}}Copy the code
In the Gamevc.swift file:
class GameVC: UITableViewController {
var games: [Game] = []
override func viewDidLoad(a) {
super.viewDidLoad()
games = Game.getData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return games.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell".for: indexPath) as! GameCell
cell.game = games[indexPath.row]
return cell
}
}
Copy the code
In the Gamecell. swift file:
class GameCell: UITableViewCell {
var game: Game! {
didSet {
gameNameLabel.text = game.name
gameDetailLabel.text = game.detail
gameImageView.image = UIImage(named: game.pictureName)
}
}
@IBOutlet weak var gameImageView: UIImageView!
@IBOutlet weak var gameNameLabel: UILabel!
@IBOutlet weak var gameDetailLabel: UILabel!
override func awakeFromNib(a) {
super.awakeFromNib()
}
}
Copy the code
After preparation, run Demo:
There was a mistake, which careful readers would have noticed earlier, and that was the entry arrow:
Run after the configuration is complete:
2.6 Jump between Interfaces
Add a title to the controller:
Then drag the Game controller to the first location of UITabBarController:
Go ahead and Add a UIBarButtonItem and set the style to Add:
Add a UITableViewController and make it a child of UINavigationController, hold down Control, click Add Item, drag it to the new controller, it’ll pop up and say jump, Present Modally, which corresponds to the Present method in our code.
Show here means that if it’s a child of UINavigationController it’s going to do Push, if it’s not, it’s going to do Present.
There’s an extra line with an arrow between the two controllers, and that’s kind of an interface switch Segue, and a interface switch Segue can only go one way.
Set the title of the new controller to Game Add, Add a Cancle Item to the left, and a Done Item to the right. Then continue to add categories at the bottom of Gamevc.Swift.
// MARK: - IBActions
extension GameVC {
@IBAction func cancelToGameVC(_ segue: UIStoryboardSegue){}@IBAction func saveGameDetail(_ segue: UIStoryboardSegue){}}Copy the code
This is an unwind Segue that goes back to the target controller. Directly above:
I’m going to select the TableView in Game Add. And I’m going to go ahead and lay it out with static cells,
- 1.
content
Select theStatic Cells
,Style
Select theGrouped
. - 2. Will appear
Section
In theCell
Delete to only one, SettingsCell
的Selection
为None
, direct copySection
, so there are two of them containing oneCell
的Section
. - 3. To
Section
Set the title (SB
In theHeader
) forGame Name
和Game Detail
. - 4. Be the first
Section
Let’s make the height 50 and the second one 200. Drag aUITextField
To the firstCell
, layout 0 bottom 0 left 10 right 10, drag oneUITextView
To the secondCell
The top and bottom of the layout are all 10’s. - 5. Create inherited from
UITableViewController
的GameAddVC.swift
File, and then delete the method inside to onlyviewDidLoad
And associate thisSB
. - 6. Set the parameters in Step 3
UITextField
和UITextView
Attachment to theGameAddVC.swift
In-file generation@IBOutlet
Properties.
@IBOutlet weak var gameNameTextField: UITextField!
@IBOutlet weak var gameDetailTextView: UITextView!
Copy the code
The reason why properties in cells can be wired directly to controllers is because static cells are not reused.
The configuration is as follows:
Here you skip the step of adding images and set a default image.
-
Select the Segue that you just added to the Done Item, and then set its Identifier to AddGameDetail.
-
Override the superclass method in addGamevc.swift and add code:
var game: Game?
// This method is called when 'Done' is clicked
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "GameAddDetail".let name = gameNameTextField.text {
game = Game(name: name, detail: gameDetailTextView.text! , pictureName:"game_default")}}Copy the code
- in
GameVC.swift
Add the following code:
// Add the contents of the method
@IBAction func saveGameDetail(_ segue: UIStoryboardSegue) {
guard let gameAddVC = segue.source as? GameAddVC.let game = gameAddVC.game else {
return
}
games.append(game)
let indexPath = IndexPath(row: games.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
Copy the code
Run Demo as follows:
2.7 Storyboard Reference
In SB, Git conflicts can easily occur if more than one person makes changes to the same place (for example, the same controller) at the same time, and this is one of the reasons why opponents of SB object to its use. With The addition of Storyboard Reference, however, this can be avoided in development.
The Demo has a small number of controllers, but in a real project, it would be scary if multiple people were only working on the main.storyboard. Previously, without Storyboard Reference, jumps between SB’s could only be done in code. Now look at the Storyboard Reference.
- 1. Hold down the left mouse button, then circle in the circle you want to escape
Main.storyboard
Controller, just like the desktop with the mouse to select multiple files. - 2. Click Editor>Refactor to Storyboard.
- 3. Name it
Game.storyboard
, select which folder to create under, and then confirm.
Once done, we can see that the controller in main. storyboard has become a storyboard Reference and the other controllers have been moved to our newly created Game.storyboard. Git conflicts are basically avoided when multiple people are developing, each operating their own business SB.
Similarly, we can create the SB file directly, then drag a Storyboard Reference from the control area and associate it with our newly created SB file.
Here is the end of this section, I think is to write more wordy, but this is no way to choose, this part of the knowledge is more interface operation, if you do not write clearly, it is not easy to explain clearly!
3 Storyboard advanced usage
The so-called advanced usage here is my wishful thinking.
3.1 @ IBInspectable
If you haven’t seen this before, you’ve probably been puzzled by the fact that some properties aren’t exposed in IB’s Settings panel. The purpose of @ibInspectable is simply to allow our custom attributes to be selected directly in IB, as suggested in The article of Maogod:
- Set a localized string for a view that displays text:
extension UILabel {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
text = NSLocalizedString(newValue, comment: "")}get { return text }
}
}
extension UIButton {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
setTitle(NSLocalizedString(newValue, comment: ""), for: .normal)
}
get { returntitleLabel? .text } } }extension UITextField {
@IBInspectable var localizedKey: String? {
set {
guard let newValue = newValue else { return }
placeholder = NSLocalizedString(newValue, comment: "")}get { return placeholder }
}
}
Copy the code
IB can be set directly:
- As a
image view
Set rounded corners (this can be expanded directly hereUIView
)
@IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0}}Copy the code
IB can be set directly:
Using @ibinspectable alone does not show property Settings in real time, and another keyword is needed to help.
3.2 @ IBDesignable
It can render some drawing code and the @ibInspectable property of UIView and its subclasses into IB in real time.
- 1. The combination of
@IBInspectable
Use, createUIView
A subclassCustomView
. Drag and drop oneUIView
To anotherItem
On the controller, center the layout up and down, height 200, and then associate them. As shown in the figure:
- In 2.
CustomView.swift
To add code, note@IBDesignable
Location:
@IBDesignable
class CustomView: UIView {
@IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0}}@IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
layer.borderColor = borderColor.cgColor
}
}
@IBInspectable var borderWidth: Int = 1 {
didSet {
layer.borderWidth = CGFloat(borderWidth)
}
}
}
Copy the code
Then look at the results:
- 3. Add the drawing code again and add the above
Corner Radius
Set to zero:
override func draw(_ rect: CGRect) {
let path = UIBezierPath(ovalIn: rect)
UIColor.green.setFill()
path.fill()
}
Copy the code
The result is shown below:
3.3 Custom Segue jump animation
We all know there are four kinds of public animations by default when Presnet switches, and if we want to customize it, we need to subclass UIStoryboardSegue.
class CustomAnimationPresentationSegue: UIStoryboardSegue.UIViewControllerTransitioningDelegate.UIViewControllerAnimatedTransitioning {
override func perform(a) {
destination.transitioningDelegate = self
super.perform()
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
if transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) == destination {
// Presenting.
UIView.performWithoutAnimation {
toView.alpha = 0
containerView.addSubview(toView)
}
let transitionContextDuration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: transitionContextDuration, animations: {
toView.alpha = 1
}, completion: { success in
transitionContext.completeTransition(success)
})
}
else {
// Dismissing.
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
UIView.performWithoutAnimation {
containerView.insertSubview(toView, belowSubview: fromView)
}
let transitionContextDuration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: transitionContextDuration, animations: {
fromView.alpha = 0
}, completion: { success in
transitionContext.completeTransition(success)
})
}
}
}
Copy the code
A simple custom fade animation, but I don’t want to go into the details of the custom jump animation here (at the end of the queue for what I want to write). Then we in the IB associated to jump to add game Segue and Cancle&Done unwind CustomAnimationPresentationSegue Segue. Demo effect:
3.4 Using r. swift tripartite framework
R.s wift making address
This is not really an advanced use of IB, it can scan resource files (such as image names, View Controllers, segue identifiers, etc.) throughout the project and generate a type-safe fetch.
let icon = UIImage(named: "settings-icon")
let viewController = UIStoryboard(name: "Main",
bundle: nil).instantiateViewController(withIdentifier: "myViewController") as! MyViewController
Copy the code
let icon = R.image.settingsIcon()
let viewController = R.storyboard.main.myViewController()
Copy the code
4. Postscript and Demo
The Demo making address
This is all I know about the operation of IB at present. If you have better use skills, you can comment and share.
Recently, I picked up my microblog, because many iOS industry seniors like microblog sharing technology, I also follow a lot of, and the income is great. For example, the OC project ZHNCosmos Github address, clean code, clear logic, I am a rookie ready to learn.
In addition, attached is my microblog, I will forward some of the big guy’s technical trends every day, please follow it:
My Weibo address
Refer to the article
The Cat Blog takes a look at some of the debate over storyboards
WWDC2015 What’s New in Storyboards
English Storyboards Tutorial for iOS: Part 1
English Storyboards Tutorial for iOS: Part 2