说实话,经历过​​Duilib​​​的​​vertical,horizonal​​​布局和​​Android​​​的​​LinerLayout​​​和​​RelativeLayout​​的布局之后,初入IOS 布局篇,只能说“蛋疼”,UI布局太累了。但是不管怎么样,坑已入,那么就必须鼓足勇气去填坑了。

下面是自己封装的swift版本的​​vertical,horizonal​​​布局,原理同​​Duilib​​布局

使用须知:同duilib的布局原理
绝对布局:如果不指定高度和宽度的话子控件默认是占用该布局高度和宽度,如果有多个控件则采取覆盖式操作。
水平布局:​​UIHorizontalView​​​,子控件需设置宽度或者weight,如果不设置则默认weight=1,系统将根据设置的值和比重进行调整控件的宽度。高度可选,如果子控件没有设置高度,则默认占据整个父空间的高度
垂直布局:​​UIVerticalView​​​,子控件需设置高度或者weight,如果不设置则默认weight=1,系统将根据设置的值和比重进行调整控件的高度。宽度可选,如果子控件没有设置宽度,则默认占据整个父空间的宽度
水平滚动布局:​​UIHScrollView​​​,该控件支持显示大于屏幕宽度的内容。需要精准的设置每一个子控件的宽度,从而精准定位内容展示所需的宽度
垂直滚动布局:​​UIVScrollView​​​,该控件支持显示大于屏幕高度的内容。需要精准的设置每一个子控件的高度,从而精准定位内容展示所需的高度
以上所有扩展属性均以lv_开头

View扩展属性设置方法,以下属性只在以上布局中生效

​lv_set_width​​​:设置view的宽度
​​​lv_get_width​​​:获取view的宽度
​​​lv_set_height​​​:设置view的高度
​​​lv_get_height​​​:获取view的高度
​​​lv_set_weight​​​:设置view的比重
​​​lv_get_weight​​​:获取view的比重
​​​lv_set_marign​​​:设置view的边距
​​​lv_get_marign​​:获取view的边距

下面以在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())

代码分析:此时UIHorizontalView添加了三个对象,两个UIView一个UIImageView。由于两个UIView既没有设置宽度也没有设置weight,所以默认以weight=1处理,UIImageView设置了weight=2,所以左右两个UIView各占1/(1+1+2)的宽度,UIImageView占2/(1+1+2)的了宽度。如果假设UIHorizontalView的宽度为1000,高度为200,那么UIImageView的位置则为​​(left:250,top:0,right:750,bottom:200)​

以下为源代码:

//
// UIViewLayout.swift
// fmspeed
//
// Created by jefcat on 2021/9/8.
//

import UIKit

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

struct AssocKey {
static var own_data = "own_data"
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 set_owndata(value:Any?){
objc_setAssociatedObject(self, &(AssocKey.own_data), value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}

func get_owndata() -> Any? {
let val = objc_getAssociatedObject(self, &(AssocKey.own_data))
return val
}

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)
}
//绝对定位布局
class UIObsolateView: 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()

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 = item_weight!/1.0*self.bounds.width
}
else{
item_width = self.bounds.width
}
}

// 计算高度
var item_height = item.lv_get_height()

if item_height == nil{
let item_weight = item.lv_get_weight()
if item_weight != nil {
item_height = item_weight!/1.0*self.bounds.height
}
else{
item_height = self.bounds.height
}
}
//计算fram位置
item.frame = CGRect(x:item.lv_get_marign_x(type: .Left), y: item.lv_get_marign_x(type: .Top), width: item_width!, height: item_height!)
}
}

//当存在子控件动态 隐藏,展示,删除,添加的时候调用
func refreshLayout() {
layoutSubviews()
}

override var frame: CGRect{
didSet{
refreshLayout()
}
}
}

//水平布局
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()
//计算view的宽度和权重
var total_width:CGFloat = 0.0
var total_weight:CGFloat = 0.0
for item in subviews{
//不可见控件不参与计算
if item.isHidden{
continue
}
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{
total_width += item.lv_get_marign_x(type: .Left) + item.lv_get_marign_x(type: .Right)
let item_weight = item.lv_get_weight()
if item_weight != nil{
total_weight += item_weight!
}
else{
total_weight += 1.0
}
}
}

//计算权重的宽度
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{
//不可见控件不参与计算
if item.isHidden{
continue
}
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
}
}

// 计算边距
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)
}
//计算fram位置
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))
}
}

//当存在子控件动态 隐藏,展示,删除,添加的时候调用
func refreshLayout() {
layoutSubviews()
}

override var frame: CGRect{
didSet{
refreshLayout()
}
}
}


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()
//计算view的高度和权重
var total_height:CGFloat = 0.0
var total_weight:CGFloat = 0.0
for item in subviews{
//不可见控件不参与计算
if item.isHidden{
continue
}
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{
total_height += item.lv_get_marign_x(type: .Top) + item.lv_get_marign_x(type: .Bottom)
let item_weight = item.lv_get_weight()
if item_weight != nil{
total_height += item_weight!
}
else{
total_weight += 1.0
}
}
}

//计算权重的高度
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{
//不可见控件不参与计算
if item.isHidden{
continue
}
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
}
}

// 计算边距
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)
}
//计算fram位置
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))
}
}

//当存在子控件动态 隐藏,展示,删除,添加的时候调用
func refreshLayout() {
layoutSubviews()
}

override var frame: CGRect{
didSet{
refreshLayout()
}
}
}

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)
}

func refreshLayout() {
_contentView.layoutSubviews()
}

override var frame: CGRect{
didSet{
refreshLayout()
}
}
}

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)
}

func refreshLayout() {
_contentView.layoutSubviews()
}

override var frame: CGRect{
didSet{
refreshLayout()
}
}
}