In daily iOS development, the controls provided by the system often cannot meet business functions, so we need to implement some custom controls. Custom controls give us complete control over what the view presents and how it interacts. This article will introduce some concepts related to custom control, explore the basic process and skills of custom control development.

UIView

Before we start, we’ll introduce a class called UIVew, which is absolutely important in iOS apps because almost all controls inherit from UIView. UIView represents a rectangular area on the screen that is responsible for rendering the contents of the area and responding to touch events that occur within the area. Inside UIView there is a CALayer that provides the drawing and display of content, including UIView size styles. UIView’s frame actually returns CALayer’s frame. UIView inherits from UIResponder, which can receive and handle events from the system, and CALayer inherits from NSObject, which can’t respond to events. So the big difference between UIView and CALayer is that UIView can respond to events and CALayer can’t. More detailed information: developer.apple.com/reference/u… www.cocoachina.com/ios/2015082…

There are two implementations

When creating custom controls, there are two main implementations, pure code and XIB. Let’s walk through the steps to create custom controls in each of these ways. We implement a simple demo that wraps a circular imageView as follows.




Use code to create custom controls

To create a custom control with code, start by creating a class that inherits from UIView




Implement the initWithFrame: method. In this method, set the properties of the custom control and create and add subviews:

-(instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
        _imageView.contentMode = UIViewContentModeScaleAspectFill;
        _imageView.layer.masksToBounds = YES;
        _imageView.layer.cornerRadius = frame.size.width/2;
        [self addSubview:_imageView];

    }
    return self;
}Copy the code

To rearrange the subviews, call the layoutSubViews method:

-(void)layoutSubviews {
    [super layoutSubviews];
    _imageView.frame = self.frame;
    _imageView.layer.cornerRadius = self.frame.size.width/2;

}Copy the code

LayoutSubviews is a method for adjusting the layout of subviews

You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want.

Override layoutSubviews whenever you need to resize a subview. LayoutSubviews are called when: Init initialization does not trigger layoutSubviews. AddSubview does trigger layoutSubviews. Setting the view Frame does trigger layoutSubviews. 4. Rolling a UIScrollView triggers layoutSubviews. 5 6. Changing the size of a UIView also triggers the layoutSubviews event on the parent UIView. This custom control provides external interface methods to assign values to the custom control

- (void)configeWithImage:(UIImage *)image {
    _imageView.image = image;
}Copy the code

Finally, add custom controls to the page

    _circleImageView = [[CircleImageView alloc] initWithFrame:CGRectMake(0, 80, 150, 150)];
    [_circleImageView configeWithImage:[UIImage imageNamed:@"tree"]];
    [self.view addSubview:_circleImageView];Copy the code

Running effect




Create custom controls through the XIB

Start by creating a custom control XibCircleImageView that inherits from UIView




Create a XiB file with the same name as the XibCircleImageView class




Configure the imageView properties in the XIB and bind the XibCircleImageView class to the corresponding XIB file




The following code

- (void)awakeFromNib {
    [super awakeFromNib];

    _imageView.layer.masksToBounds = YES;
    _imageView.layer.cornerRadius = self.frame.size.width/2;
    [self addSubview:_imageView];
}

- (void)configeWithImage:(UIImage *)image {
    _imageView.image = image;
}

-(void)layoutSubviews {
    [super layoutSubviews];
    _imageView.layer.cornerRadius = self.frame.size.width/2;

}Copy the code

Called a little differently in the page, the XIB object is created using the loadNibNamed method

_xibCircleImageView = [[[NSBundle mainBundle] loadNibNamed:@"XibCircleImageView" owner:nil options:nil] lastObject]; _xibCircleImageView.frame = CGRectMake(0, 500, 100, 100); [_xibCircleImageView configeWithImage:image]; [self.view addSubview:_xibCircleImageView];Copy the code

When a xiB is used to create a custom control, the initWithFrame: method is not called. Only the initWithCoder: method is called. AwakeFromNib is called after the initialization, and the child control is initialized in awakeFromNib. Because the initWithCoder: method means that the object is resolved from a file and is called, whereas the awakeFromNib method is called after the xiB or storyboard has loaded. Demo address: github.com/superzcj/ZC…

summary

These two ways to create custom controls have their own advantages and disadvantages, pure code is more flexible, maintenance and expansion are more convenient, but it is more troublesome to write. Xib is efficient in development, but difficult to expand and maintain. Xib is suitable for custom controls with stable functions and styles.

Event passing mechanism

In custom controls, you may need to dynamically respond to events, such as buttons that are too small to be clicked and need to be expanded. Next, we will talk about iOS event passing mechanism.

Event response chain

The UIResponder class responds to events like touches, gestures, and remote controls. It is the base class for all respondable events, including the very common UIView, UIViewController, and UIApplication. UIResponder properties and methods are shown below, where nextResponder indicates pointing to a UIResponder object.







So what does the event response chain have to do with UIResponder? Views within an application are organized in a tree hierarchy. A view can have multiple child views, while a child view can have only one parent view. When a view is added to a superview. The nextResponder attribute of each view points to its parent view, and the entire application is then connected in a chain, the response chain, through the nextResponder. The response chain is a virtual chain, not a real one, connected by UIResponder’s nextResponder. The following figure




Hit-Test View

With an event response chain, the next step is to find the specific response object, which is called a hit-testing View. The process of finding the View is called a hit-test. What is hit-test? We can think of it as a detector that we can use to find and determine if a finger is touching on a view. How does hit-test work? Hit-test iterates recursively from the root node of the view until it finds a clicked view. We start by sending a hitTest:withEvent: message from UIWindow to see if the view can respond to touch events, and if it can’t respond to nil, that means the view can’t respond to touch events. It then calls the pointInside:withEvent: method, which determines whether the touch event was clicked at the point within the scope of the view. If pointInside:withEvent: returns no, then hitTest:withEvent: also returns nil. If the pointInside:withEvent: method returns yes, the view sends a hitTest:withEvent: message to all the subViews, which are called from the topmost view all the way to the bottom view, traversing the end of the subViews array. Until a subview returns a non-empty object or all traversals are complete. If a subview returns a non-empty object, the hitTest:withEvent: method returns the object. If all subviews return nil, the hitTest:withEvent: method returns the view itself.

Application of event passing mechanism

Here are a few examples of how event passing can be used in custom controls. Expand the view’s click area. Let’s say a button is 20px and 20px is too small to click. The button subclass hitTest:withEvent: method was overridden to determine whether the point was within 20px of the button. If so, it would return itself to expand the click range.

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (! Self. IsUserInteractionEnabled | | self. Hidden | | the self. The alpha < = 0.01) {return nil; } CGRect touchRect = CGRectInset(self.bounds, -20, -20); if (CGRectContainsPoint(touchRect, point)) { for (UIView *subView in [self.subviews reverseObjectEnumerator]) { CGPoint convertedPoint = [subView convertPoint:point toView:self]; UIView *hitTestView = [subView hitTest:convertedPoint withEvent:event]; if (hitTestView) { return hitTestView; } } return self; } return nil; }Copy the code

Two, pass through the event. So let’s say we have two views, viewA and viewB, and viewB completely overrides viewA, and we want to respond to viewA’s events when we click on viewB. We override the viewA hitTest:withEvent: method to return itself without continuing through its subviews. The code is as follows:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (! Self. IsUserInteractionEnabled | | self. Hidden | | the self. The alpha < = 0.01) {return nil; } if ([self pointInside:point withEvent:event]) { NSLog(@"in view A"); return self; } return nil; }Copy the code

More detailed information: zhoon. Making. IO/ios / 2015/04… www.jianshu.com/p/2f664e71c…

Callback mechanism

In custom control development, a return value is passed back to its parent class. For example, a custom control that holds a button needs to receive a button click event at the top level. We can call back messages in many ways, such as target Action mode, broker, block, notification, and so on.

Target-Action

Target-action is a design pattern that causes one object to send a message to another object when an event is triggered. We’ve seen a lot of this pattern, such as binding click events to buttons, adding gesture events to views, etc. UIControl and its subclasses support this mechanism. Target-action establishes a loose relationship between the sender and receiver of a message. The receiver of the message does not know the sender, and even the sender of the message does not know what the receiver of the message will be. One limitation of target-action delivery is that the message sent cannot carry custom information. In iOS, you can optionally take the sender and the event that triggered the action as parameters. There is no other way to control the content of action messages. For example, we use target-action to add a click gesture to the control.

        UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(refresh)];
        [_imageView addGestureRecognizer:tapGR];

- (void)refresh{
    NSLog(@"Touch imageView");
}Copy the code

The agent

Proxy is a common callback method, which is also recommended by Apple. It is widely used in UIKit, such as UITableView and UITextField. Advantages: 1, the proxy syntax is clear, high readability, easy to maintain; 2. It reduces code coupling and separates event monitoring from event processing. 3, one controller can realize multiple agents, meet the needs of customized development, high flexibility; Disadvantages: 1, the process of realizing the agent is tedious; 2. The coupling of the code is increased when the value is passed across layers, and the hierarchical structure of the program becomes chaotic; 3. When multiple objects transmit values at the same time, it is not easy to distinguish them, resulting in greatly reduced ease of use of proxy;

Block

Blocks encapsulate a piece of code and pass it as a variable, making it easy to group code in different places together and readable. Advantages: 1. Concise syntax, high code readability and maintainability. 2. Cooperate with GCD to solve the multi-pure process problem. Disadvantages: 1. The code in the Block will automatically perform a retain operation, which may leak memory. 2. The default reference in a Block is a strong reference, which is easy to cause circular reference.

notice

Proxy is one-to-one relationship, and notification is one-to-many relationship. Notification can realize a larger span of communication mechanism than proxy. However, if there are too many receiving objects, it is difficult to control them. Sometimes unwanted objects also receive and process messages. Advantages: 1, simple to use, streamlined code. 2, support one-to-many, solve the problem of listening to multiple objects at the same time. 3. It is easy and fast to pass values. Context itself carries the corresponding content. Disadvantages: 1. You need to log off after the notification is used, otherwise it will cause an unexpected crash. 2. The key is not secure enough that the compiler will not detect whether the notification center handled it correctly. 3. Difficult to track during debugging. 4. When users send notifications to the notification center, they do not get any feedback. 5. A third-party object is required to mediate between listener and listener. IO /issue-3-4/ maru-zhang.tk/2015/06/08/… objccn.io/issue-3-4/ Maru-zhang.tk /2015/06/08/…

conclusion

At this point, the development of custom control related knowledge comb again, I hope to help you better understand the development of custom control.