Introduction: For contact with business development, the development of custom View is the most frequent work. But it turns out that some kids still don’t build UI controls with a good specification or even in the wrong way. For this reason, the following table of contents will be used to describe in detail some of the writing considerations for custom Views.
- About the initialization method of custom View
- About addSubview
- About layoutSubviews
- About frame and bounds
First, about the custom View initialization method
Normally we create a private method called createUI to create the child View required by the current custom View. Which method of a custom View should createUI be placed in?
1, the init?
2, initWithFrame?
3. Do you want to call createUI in both init and initWithFrame methods to account for the different ways in which custom views are created externally?
Let’s verify this one by calling the createUI method in the Init method of CustomView.
- (instancetype)init {
if (self = [super init]) {
[self createUI];
}
return self;
}
- (void)createUI {
[self addSubview:self.testView];
}
- (UIView *)testView {
if(! _testView) { _testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; _testView.backgroundColor = [UIColor redColor]; }return _testView;
}
Copy the code
Create CustomView externally as init
CustomView *customView = [[CustomView alloc] init];
customView.frame = CGRectMake(100, 100, 200, 200);
customView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:customView];
Copy the code
Verifiable, CustomView and its subviews display normally. The problem is that if the external is created as initWithFrame, the createUI method cannot be called, so the subview cannot be displayed.
The second form of initialization calls the createUI method in the initWithFrame method alone
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self createUI];
}
return self;
}
Copy the code
The verifiable result is that the CustomView and its subviews are displayed properly regardless of whether the CustomView is externally initialized with init or initWithFrame methods.
Finally, let’s experiment by calling createUI in both init and initWithFrame methods. Debug the number of createUI calls.
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self createUI];
}
return self;
}
- (instancetype)init {
if (self = [super init]) {
[self createUI];
}
return self;
}
- (void)createUI {
NSLog(@"SubViews Add");
[self addSubview:self.testView];
}
- (UIView *)testView {
if(! _testView) { _testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; _testView.backgroundColor = [UIColor redColor]; }return _testView;
}
@end
Copy the code
The external creation of CustomView still uses init to verify that the createUI method was executed twice by printing the result or a breakpoint!
[72346:1989647] Add [52346 :1989647] Add [52346 :1989647] Add TestAddSubview[72346:1989647] SubViews AddCopy the code
All three of these assumptions are related to the question of whether the init method of a custom View calls the initWithFrame method by default.
The answer is yes, through the above code debugging process, we can draw the following conclusions about the code call process (using external init as an example) :
1. Dynamically find the init method of CustomView
2. Call [super init]
[super initWithFrame:CGRectZero] [super initWithFrame:CGRectZero]
4. If Super finds that CustomView implements the initWithFrame method
5. Instead execute the initWithFrame method self(CustomView)
6. Finally, execute the rest of init
A conclusion can also be verified here: Super in OC is actually a way for a class to call its parent’s methods, rather than the parent’s method, and the order of dynamic method calls is bottom-up (which is why createUI is only done in init and not multiple times). Because initWithFrame doesn’t do createUI.
Conclusion: createUI is best called from initWithFrame. The createUI method can be executed externally using init or initWithFrame. Do not override init and initWithFrame and execute the same View layout code in a custom View. Causes the layout code (createUI) to execute multiple times.
addSubview
Ok, so let’s move on to question one: custom View initializer, what happens if you call createUI in init and initWithFrame at the same time?
The obvious thing is that the createUI method is executed multiple times, which means adding self.testView multiple times. Will multiple View layers be added repeatedly?
No, adding the same View multiple times does not create a multi-tiered situation. Let’s look at the documentation for addSubview
This method establishes a strong reference to view and sets its next responder to the receiver, which is its new superview. Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview.
A View has one and only one parent View. If the new parent View is different from the original one, the View will be removed from the original View and added to the new one.
So adding the same View repeatedly to the same superview does not create multiple hierarchies. This can be easily verified in code by looping self.testView in createUI to print the number of child views of the current view
- (void)createUI {
for (NSInteger i = 0; i < 100; i++) {
[self addSubview:self.testView];
}
NSLog(@"subviewsCount = 【%ld】",self.subviews.count);
for (UIView *view in self.subviews) {
NSLog(@"subView 【%@】",view); }}Copy the code
The number of child views of a view is always 1
2019-06-28 16:02:50.420144+0800 TestAddSubview[78991:832644] subviewsCount = [1] 2019-06-28 16:02:50.422151+0800 TestAddSubview[78991:832644] subView [<UIView: 0x7F80A9C09590; frame = (0 0; 100 100); Layer = <CALayer: 0 x600003ff0a40 > > 】Copy the code
As you can verify from the print, CustomView always has only one child view (testView).
If the old and new superviews are the same, we can assume that Apple does something like this:
1. Remove the child view from the old parent view and add the child view to the parent view again
2. Check whether the old and new parent views are consistent. If they are consistent, do not perform any operation.
Because you can’t see the source code of addSubview, guess there may be two cases, I prefer the second processing. (We can rewrite the subview layoutSubviews method, because addSubviews will call layoutSubviews method, we can debug the call times of layoutSubviews, after the test can verify addSubviews do the above two processing)
Conclusion: If the parent view repeatedly adds the same child view, there is no multi-hierarchy situation. Since the testView is created lazy-loaded in this example, self adds the same View each time, but if it is created in createUI as UIView *testView = [UIView alloc] initWithFrame, That will create a multi-tiered View.
Summary: It is best to create child views of custom views in lazy loading mode to avoid other exceptions caused by improper writing
3. LayoutSubviews
At this point, I want to talk about when layoutSubviews are called
1, setNeedsLayout \ layoutIfNeeded
2, addSubview
3, View size changes, unchanged does not call
4, UIScrollView slide
5. Rotating Screen triggers layoutSubviews on the parent UIView
Therefore, we need to pay attention to the following points when using layoutSubviews:
1. The init method of a custom view does not call layoutSubviews
2. Apple says not to call layoutSubviews directly. If you need an update, you should call setNeedsLayout. If you need to update the view immediately, you need to execute the layoutIfNeeded method
3. Because layoutSubviews are called frequently, there is no need to rewrite the layoutSubviews method unless there is a special need (as described in the document) to perform an accurate subview layout.
4. About Frame and bounds
As you know, there are two very important properties of position size in iOS UI controls, frame and bounds
A UI control’s frame means its position relative to the control’s parent view, and bounds means its position relative to the control itself. Frame and bounds are CGRect structure body, composed of CGPoint and CGSize, we can through the frame. The origin/bounds origin and frame. The size/bounds. The size control to return to the top left corner position and size.
You usually animate a View, you can manipulate the Frame directly or you can get Layer set implicit animation.
What happens if we manipulate the View bounds directly?
RedView is added to the current View controller. BlueView is the subview of RedView, and GreenView is the subview of BlueView. The coordinates are (10,10, 200,200), (10,10,150,150), (10,10,100,100), and their coordinate positions are shown in the figure below:
What happens if you change the bounds of BlueView to (0,10,150,150)?
Maybe we normally move the View not through bounds but through frame, and know that the bounds are coordinates relative to themselves, and changing the origin of the bounds has no effect on the bounds itself, but this would be a mistake. If we look at the result of this case, the display of the three Views becomes something like the following:
The position of BlueView has not changed much, but the position of GreenView has been moved up 10 coordinate points due to the modification of BlueView.
Let’s see why. Adjusting the Bounds of BlueView caused the BlueView to move up 10 points relative to its own coordinates, and the GreenView to its parent also moved up 10 points. For GreenView, the top left corner of its superview BlueView is no longer (0,0), but (0,10), so it will have the result shown above.
Summary: Try to do something with the frame in the CustomView, and don’t change the Origin property of the bounds without special needs. This can cause unexpected bugs. (Does not affect the current view, but indirectly affects its children)
Well, temporarily write here, no rules no fangyuan, for developers a good code specification can often get twice the result with half the effort, mutual encouragement!
Jane address book: www.jianshu.com/u/57d9688d4…