Controller, easy! We take out the code in UIView
Do you have a big UIViewController? For many, yes. On the one hand, it works with data, on the other - with the interface.
The tasks of separating logic from an interface are described in hundreds of articles about the architecture: MVP, MVVM, VIPER. They solve the problem of data flow, but do not answer the question of how to work with the interface: in one place there is the creation of elements, layout, configuration, input processing and animation.
Let's separate the view from the controller and see what loadView () will help us with.
The iOS app interface is a hierarchy
If you look at the methods of the class
The life cycle
We can override the method and specify our class.
The controller
But while the compiler does not know about the class and believes that there is normal
Now you can see the variables
Fonts, colors, constraints and hierarchy can be customized directly in the CustomView designer:
The best place for a manual layout is the method
If there is time, then I make the
Encapsulation advantage: internal logic is hidden behind the interface. For example, the validity of an object may be indicated by the color of a region, not a square, but the controller will not know anything about it.
If you use Interface Builder, it is often
Subclass for
You need to select an object
If you select the control inside
If you create an interface in the code, then all objects are available after
For my taste, the controller should leave all user actions. From standard:
At the same time
In Objective-C, you can fully replace the type
In the GitHub example, you can look at the division of classes for a simple task: the color of the square depends on its position (in the green area it is green, outside it is red).
The more complex the screen, the better the effect: the controller decreases, the code is transferred to its places. The code is simply transferred to
I used this code in different projects and with different people, each time the team welcomed the simplification and picked up the practice. I hope you enjoy it too. All lungs
The tasks of separating logic from an interface are described in hundreds of articles about the architecture: MVP, MVVM, VIPER. They solve the problem of data flow, but do not answer the question of how to work with the interface: in one place there is the creation of elements, layout, configuration, input processing and animation.
Let's separate the view from the controller and see what loadView () will help us with.
The iOS app interface is a hierarchy
UIView
. Tasks of each view
: to create elements, set up, put into place, animate. This can be seen from the methods that are in the class. UIView: addSubview(), drawRect(), layoutSubviews().
If you look at the methods of the class
UIViewController
, you can see that it manages the management view:
loads, reacts to the loading of screens and user actions, shows new screens. Often the code that needs to be in UIView
, we write in subclasses UIViewController
, which makes it too big. Separate it.loadView ()
The life cycle
UIViewController
begins with loadView()
. A simplified implementation looks like this:// CustomViewController.swift
func loadView() {
self.view = UIView()
}
We can override the method and specify our class.
super.loadView()
no need to call!// CustomViewController.swift
override func loadView() {
self.view = CustomView()
}
Implementing CustomView.swift
// CustomView.swift
final class CustomView {
let square: UIView = UIView()
init() {
super.init()
square.backgroundColor = .red
addSubview(square)
}
}
The controller
CustomView,
will add it to the hierarchy, set it up .frame
. The property .view
will be the class we need:// CustomViewController.swift
print(view) // CustomView
But while the compiler does not know about the class and believes that there is normal
UIView
. Let's fix this with a type conversion:// CustomViewController.swift
func view() -> CustomView {
returnself.view as! CustomView
}
Now you can see the variables
CustomView
:// CustomViewController.swift
func viewDidLoad() {
super.viewDidLoad()
view().square // Работает
}
Simplify using associatedtype
Руслан Кавецкий предложил убрать дублирование кода с помощью расширения протокола:
protocol ViewSpecificController { associatedtype RootView: UIView } extension ViewSpecificController where Self: UIViewController { func view() -> RootView { returnself.view as! RootView } }
Для каждого нового контроллера нужно только указать протокол и подкласс для егоUIView
черезtypealias
:
// CustomViewController.swift final class CustomViewController: UIViewController, ViewSpecificController { typealias RootView = CustomView func viewDidLoad() { super.viewDidLoad() view().square // Работает } }
Code in a subclass of UIView
Creating and configuring controls
Fonts, colors, constraints and hierarchy can be customized directly in the CustomView designer:
// CustomView.swift
init() {
super.init()
backgroundColor = .lightGray
addSubview(square)
}
layoutSubviews ()
The best place for a manual layout is the method
layoutSubviews()
. It is called every time the size changes view
, so you can rely on the size bounds
for correct calculations:// CustomView.swift
override func layoutSubviews() {
super.layoutSubviews()
square.frame = CGRect(x: 0, y: 0: width: 200, height: 200)
square.center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
}
Private controls, public properties
If there is time, then I make the
property
controls private, but I manage them through public variables or “in the knowledge” functions. Easier by example:// CustomView.swift
private let square = UIView()
var squarePositionIsValid: Bool {
didSet {
square.backgroundColor = squarePositionIsValid? .green : .red
}
}
func moveSquare(to newCenter: CGPoint) {
square.center = newCenter
}
Encapsulation advantage: internal logic is hidden behind the interface. For example, the validity of an object may be indicated by the color of a region, not a square, but the controller will not know anything about it.
What remains in viewDidLoad ()?
If you use Interface Builder, it is often
viewDidLoad()
empty. If view
you create in code, you need to bind their actions through the target-action pattern, add UIGestureRecognizer
or link delegates.Customizable via Interface Builder
Subclass for
view
can be configured through Interface Builder (hereinafter IB). You need to select an object
view
(not a controller) and set its class. Writing your own is loadView()
not necessary, the controller will do it himself. But I UIView
still have to bring the type .IBOutlet in UIView
If you select the control inside
view
, the Assistant Editor will recognize the class UIView
and offer it as the second file in Automatic mode. So you can transfer IBOutlet
to view
.If not working
Открыть класс
CustomView
вручную, написать IBOutlet
. Теперь можно потащить за маркер и навести на элемент в IB.If you create an interface in the code, then all objects are available after
init()
, but when working with IB, access to IBOutlet
appears only after the interface is loaded from UIStoryboard
in the method awakeFromNib()
:// CustomView.swift
func awakeFromNib() {
super.awakeFromNib()
square.layer.cornerRadius = 8
}
IBAction in UIViewController
For my taste, the controller should leave all user actions. From standard:
- target-action from controls
- delegate implementation in
UIViewController
- block implementation
- reaction to
Notification
At the same time
UIViewController
controls only the interface. Everything related to business logic should be taken out of the controller, but this is a choice: MVP, VIPER, etc.Objective-c
In Objective-C, you can fully replace the type
UIView
. To do this, you need to declare a property with the required class, override setter
it getter
, specifying the class:// CustomViewController.m@interfaceCustomViewController@property (nonatomic) CustomView *customView;
@end@implementation
- (void)setView:(CustomView *)view{
[super setView:view];
}
- (CustomView *)view {
return (CustomView *)super.view;
}
@end
the end
In the GitHub example, you can look at the division of classes for a simple task: the color of the square depends on its position (in the green area it is green, outside it is red).
The more complex the screen, the better the effect: the controller decreases, the code is transferred to its places. The code is simply transferred to
view
, but encapsulation makes it easy to interact and read the code. Sometimes view
you can reuse with another controller. For example, different controllers for the iPhone and iPad react differently to the appearance of the keyboard, but this does not change the code view
. I used this code in different projects and with different people, each time the team welcomed the simplification and picked up the practice. I hope you enjoy it too. All lungs
UIViewController
!