ios UI layout

Keywords: iOS UI

To be honest, after experiencing the vertical and horizontal layout of Duilib and the LinerLayout and RelativeLayout of Android, I can only say "egg pain" when I first enter the IOS layout chapter. The UI layout is too tired. But anyway, if the pit is already in, you must summon up the courage to fill it.

The following is the vertical and horizontal layout of the self encapsulated swift version. The principle is the same as that of the Duilib layout

Instructions for use: the layout principle is the same as that of duilib
Horizontal layout: UIHorizontalView. The width or weight of the sub control needs to be set. If it is not set, the default weight=1. The system will adjust the width of the control according to the set value and specific gravity. Height is optional. If the child control has no height set, it will occupy the height of the entire parent space by default
Vertical layout: UIVerticalView. The height or weight of the sub control needs to be set. If it is not set, the default weight=1. The system will adjust the height of the control according to the set value and specific gravity. Width is optional. If the child control has no width set, it will occupy the width of the entire parent space by default
Horizontal scroll layout: UIHScrollView, which supports displaying content larger than the screen width. You need to accurately set the width of each sub control, so as to accurately locate the width required for content display
Vertical scroll layout: UIVScrollView, which supports displaying content larger than the screen height. It is necessary to accurately set the height of each sub control, so as to accurately locate the height required for content display
All the above extended attributes are expressed in lv_ start

View extension property setting method. The following properties only take effect in the above layout

lv_set_width: sets the width of the view
lv_get_width: gets the width of the view
lv_set_height: sets the height of the view
lv_get_height: get the height of the view
lv_set_weight: sets the specific gravity of the view
lv_get_weight: get the specific gravity of the view
lv_ set_ Sign: sets the margin of the view
lv_ get_ Sign: get the margin of the view

Here is an example of displaying a picture in the center of UIHorizontalView:

let uiHorizonView = UIHorizontalView()

let uiImageView = UIImageView()
//uiImageView.lv_set_width(value:200)
uiImageView.lv_set_weight(value:2)

uiHorizonView.addSubview(UIView())
uiHorizonView.addSubview(uiImageView)
uiHorizonView.addSubview(UIView())

Code analysis: at this time, UIHorizontalView adds three objects, two uiviews and one UIImageView. Since the two uiviews have neither width nor weight set, they are processed with weight=1 by default. UIImageView has weight=2 set, so the left and right uiviews account for 1 / (1 + 1 + 2) width respectively, and UIImageView accounts for 2 / (1 + 1 + 2) width. If the width of UIHorizontalView is 1000 and the height is 200, the position of UIImageView is (left:250,top:0,right:750,bottom:200)

The following is the source code:

//
//  UIViewLayout.swift
//  xxxx
//
//  Created by CAir2 on 2021/9/8.
//

import UIKit

enum LVMarignType{
    case Left
    case Top
    case Right
    case Bottom
}

struct AssocKey {
    static var lv_width = "lv_width"
    static var lv_height = "lv_height"
    static var lv_weight = "lv_weight"
    static var lv_marign_left = "lv_marign_left"
    static var lv_marign_top = "lv_marign_top"
    static var lv_marign_right = "lv_marign_right"
    static var lv_marign_bottom = "lv_marign_bottom"
}

extension UIView{
    func lv_set_width(value:CGFloat?){
        objc_setAssociatedObject(self, &(AssocKey.lv_width), value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
    
    func lv_get_width() -> CGFloat? {
        let val = objc_getAssociatedObject(self, &(AssocKey.lv_width))
        if val != nil{
            return val as? CGFloat
        }
        return nil
    }
    
    func lv_set_height(value:CGFloat?){
        objc_setAssociatedObject(self, &(AssocKey.lv_height), value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
    
    func lv_get_height() -> CGFloat? {
        let val = objc_getAssociatedObject(self, &(AssocKey.lv_height))
        if val != nil{
            return val as? CGFloat
        }
        return nil
    }
    
    func lv_set_weight(value:CGFloat?){
        objc_setAssociatedObject(self, &(AssocKey.lv_weight), value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
    
    func lv_get_weight() -> CGFloat? {
        let val = objc_getAssociatedObject(self, &(AssocKey.lv_weight))
        if val != nil{
            return val as? CGFloat
        }
        return nil
    }
    
    func lv_set_marign(type:LVMarignType,value:CGFloat?){
        switch type {
        case .Left:
            objc_setAssociatedObject(self, &(AssocKey.lv_marign_left), value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        case .Top:
            objc_setAssociatedObject(self, &(AssocKey.lv_marign_top), value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        case .Right:
            objc_setAssociatedObject(self, &(AssocKey.lv_marign_right), value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        case .Bottom:
            objc_setAssociatedObject(self, &(AssocKey.lv_marign_bottom), value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        
    }
    
    func lv_get_marign(type:LVMarignType) -> CGFloat? {
        var value:Any? = nil
        switch type {
        case .Left:
            value = objc_getAssociatedObject(self, &(AssocKey.lv_marign_left))
        case .Top:
            value = objc_getAssociatedObject(self, &(AssocKey.lv_marign_top))
        case .Right:
            value = objc_getAssociatedObject(self, &(AssocKey.lv_marign_right))
        case .Bottom:
            value = objc_getAssociatedObject(self, &(AssocKey.lv_marign_bottom))
        }
    
        if value != nil{
            return value as? CGFloat
        }
        return nil
    }
    
    fileprivate func lv_get_marign_x(type:LVMarignType)->CGFloat{
        var marign = lv_get_marign(type: type)
       
        if  marign == nil{
            marign = 0.0
        }
        else{
            marign = marign!
        }
        return marign!
    }
}

protocol UIViewLayoutDelegate:AnyObject {
    func onContentSizeNeed(sender:Any,size:CGSize)
}
//Horizontal layout
class UIHorizontalView: UIView {

    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */
    var _delegate:UIViewLayoutDelegate? = nil
    
    override func layoutSubviews() {
        super.layoutSubviews()
        //Calculate the width and weight of the view
        var total_width:CGFloat = 0.0
        var total_weight:CGFloat = 0.0
        for item in subviews{
            let item_width = item.lv_get_width()
            if item_width != nil{
                total_width += (item_width! + item.lv_get_marign_x(type: .Left) + item.lv_get_marign_x(type: .Right))
            }
            else{
                let item_weight = item.lv_get_weight()
                if item_weight != nil{
                    total_weight += item_weight!
                }
                else{
                    total_weight += 1.0
                }
            }
        }
        
        //Calculate the width of the weight
        var reset_width = self.bounds.width - total_width
        if reset_width <= CGFloat(0){
            reset_width = 0.0
        }
        
        var xoffset:CGFloat = 0.0
        for item in subviews{
            var item_width = item.lv_get_width()
            if item_width == nil{
                let item_weight = item.lv_get_weight()
                if item_weight != nil {
                    item_width = reset_width * item_weight!/total_weight
                }
                else{
                    item_width = reset_width/total_weight
                }
            }
            
            // Calculate margin
            var item_height = item.lv_get_height()
            var yoffset:CGFloat = 0
            if item_height == nil{
                item_height = self.bounds.height - item.lv_get_marign_x(type: .Top) - item.lv_get_marign_x(type: .Bottom)
                yoffset = item.lv_get_marign_x(type: .Top)
            }
            else{
                yoffset = item.lv_get_marign_x(type: .Top)
            }
            
            if item_height! <= CGFloat(0){
                item_height = CGFloat(0)
            }
            //Calculate fram position
            item.frame = CGRect(x: xoffset + item.lv_get_marign_x(type: .Left), y: yoffset, width: item_width!, height: item_height!)
            xoffset += (item_width! + item.lv_get_marign_x(type: .Left) + item.lv_get_marign_x(type: .Right))
        }
        
        if xoffset > self.bounds.width{
            _delegate?.onContentSizeNeed(sender:self,size:CGSize(width: xoffset, height: self.bounds.height))
        }
    }
    
}


class UIVerticalView: UIView {

    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */
    
    var _delegate:UIViewLayoutDelegate? = nil
    
    override func layoutSubviews() {
        super.layoutSubviews()
        //Calculate the height and weight of the view
        var total_height:CGFloat = 0.0
        var total_weight:CGFloat = 0.0
        for item in subviews{
            let item_height = item.lv_get_height()
            if item_height != nil{
                total_height += (item_height! + item.lv_get_marign_x(type: .Top) + item.lv_get_marign_x(type: .Bottom))
            }
            else{
                let item_weight = item.lv_get_weight()
                if item_weight != nil{
                    total_height += item_weight!
                }
                else{
                    total_weight += 1.0
                }
            }
        }
        
        //Calculate the height of the weight
        var reset_height = self.bounds.height - total_height
        if reset_height <= CGFloat(0){
            reset_height = 0.0
        }
        
        var yoffset:CGFloat = 0.0
        for item in subviews{
            var item_height = item.lv_get_height()
            if item_height == nil{
                let item_weight = item.lv_get_weight()
                if item_weight != nil {
                    item_height = reset_height * item_weight!/total_weight
                }
                else{
                    item_height = reset_height/total_weight
                }
            }
            
            // Calculate margin
            var item_width = item.lv_get_width()
            var xoffset:CGFloat = 0
            if item_width == nil{
                item_width = self.bounds.width - item.lv_get_marign_x(type: .Left) - item.lv_get_marign_x(type: .Right)
                xoffset = item.lv_get_marign_x(type: .Left)
            }
            else{
                xoffset = item.lv_get_marign_x(type: .Left)
            }
            
            if item_width! <= CGFloat(0){
                item_width = CGFloat(0)
            }
            //Calculate fram position
            item.frame = CGRect(x: xoffset, y:yoffset + item.lv_get_marign_x(type: .Top), width: item_width!, height: item_height!)
            yoffset += (item_height! + item.lv_get_marign_x(type: .Top) + item.lv_get_marign_x(type: .Bottom))
        }
        
        if yoffset > self.bounds.width{
            _delegate?.onContentSizeNeed(sender:self,size:CGSize(width: self.bounds.width, height:yoffset))
        }
    }
}

class UIVScrollView:UIScrollView,UIViewLayoutDelegate{
    func onContentSizeNeed(sender: Any, size: CGSize) {
        contentSize = size
    }
    
    private var _contentView = UIVerticalView()
    
    override func addSubview(_ view: UIView) {
        _contentView.addSubview(view)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        initView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        initView()
    }
    
    func initView() {
        _contentView._delegate = self
        super.addSubview(_contentView)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        _contentView.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
    }
}

class UIHScrollView:UIScrollView,UIViewLayoutDelegate{
    func onContentSizeNeed(sender: Any, size: CGSize) {
        contentSize = size
    }
    
    private var _contentView = UIHorizontalView()
    
    override func addSubview(_ view: UIView) {
        _contentView.addSubview(view)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        initView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        initView()
    }
    
    func initView() {
        _contentView._delegate = self
        super.addSubview(_contentView)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        _contentView.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
    }
}

Posted by John Canyon on Thu, 09 Sep 2021 23:31:10 -0700