UIKit学习笔记2-组件嵌套、滚动视图等

专题1.将十六进制颜色转换成UIColor,并扩展到UIColor里

本质上是十六进制转十进制的移位运算(不用太理解)

Swift 复制代码
extension UIColor {
    convenience init?(hex: String) {
        var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
        hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
        
        var rgb: UInt64 = 0
        
        Scanner(string: hexSanitized).scanHexInt64(&rgb)
        
        let red, green, blue, alpha: CGFloat
        
        switch hexSanitized.count {
        case 3: // RGB
            red = CGFloat((rgb >> 16) & 0xFF) / 255.0
            green = CGFloat((rgb >> 8) & 0xFF) / 255.0
            blue = CGFloat(rgb & 0xFF) / 255.0
            alpha = 1.0
        case 6: // RRGGBB
            red = CGFloat((rgb >> 16) & 0xFF) / 255.0
            green = CGFloat((rgb >> 8) & 0xFF) / 255.0
            blue = CGFloat(rgb & 0xFF) / 255.0
            alpha = 1.0
        case 8: // AARRGGBB
            red = CGFloat((rgb >> 16) & 0xFF) / 255.0
            green = CGFloat((rgb >> 8) & 0xFF) / 255.0
            blue = CGFloat(rgb & 0xFF) / 255.0
            alpha = CGFloat((rgb >> 24) & 0xFF) / 255.0
        default:
            return nil
        }
        self.init(red: red, green: green, blue: blue, alpha: alpha)
    }
    
}

然后就可以在其他页面使用了,注意十六进制的表示方式是#,如#0CB6D6

专题2.组件嵌套功能实现,利用For循环重复使用组件

拆解:

先拆解需要实现的页面,看看是否有可以重复利用的地方。

是否需要点击(决定了使用何种视图)?

有哪些是确定,不会变的,有哪些是随着元素改变会改变的?

拆解:

背景不可点击,而小的元素可以点击。

小的组件之间共享一套内部的排列方式,有着相同的结构(头像与描边,字体位置,背景大小),但文本显示与图片显示都不同。

思路:

1.先把背景和固定的图片用UIImage和UIView写出来

2.在另一页创建一个新的类,它继承自UIControl,用于存放每个组件中的固定元素。

注意命名方式,变量和函数都用小写字母开头+驼峰式命名。

3.创建一个Model(是一个结构体),内容为需要修改的元素属性(如颜色,文本,图片等),注意,能存String、UIColor,就不要传图片,因为图片很耗费内存,将图片导入Assets之后就可以用字符串的方式传图片了。

4.在Model内以函数的方式将个性化数据以数组的形式返回,便于调用(函数调用时,利用索引)。

5.确定控件位置:在FirstViewControl(也就是写了其他东西的页面)创建一个函数。创建一些变量,根据行与列之间的关系,利用数组的索引来控制每个组件的位置(x值与y值)

6.通过for来遍历结构体中的元素,确保每一个元素被返回。

7.配置控件属性:通过调用Model中的函数来配置变动的控件属性。

具体操作:

1.先把背景和固定的图片用UIImage和UIView写出来

上一部分的内容,不多赘述。

固定的图片部分
Swift 复制代码
    lazy var quick: UIImageView = {
        let quick = UIImageView(frame: .init(origin: CGPoint(x:16, y: 15),size: CGSize(width: 82, height: 26)))
        quick.image = UIImage(named: "home_img_personality")
        return quick
    }()
固定的背景部分:因为不需要有任何功能,所以使用UIView。

设置背景的属性

Swift 复制代码
    lazy var mbtiwiki: UIView = {//不需要交互
        let mbtiwiki: UIView = UIView(frame: .init(x: 16, y: 510, width: self.view.frame.width - 32, height: 500))
        mbtiwiki.backgroundColor = .white
        mbtiwiki.layer.cornerRadius = 24
        mbtiwiki.layer.masksToBounds = true
        mbtiwiki.layer.shadowColor = UIColor.black.cgColor // 阴影颜色
        mbtiwiki.layer.shadowOpacity = 0.5 // 阴影不透明度
        mbtiwiki.layer.shadowOffset = CGSize(width: 0, height: 2) // 阴影偏移
        mbtiwiki.layer.shadowRadius = 4 // 阴影模糊半径
        mbtiwiki.addSubview(quick)
        return mbtiwiki
    }()

2.在另一页创建一个新的类,它继承自UIControl,用于存放每个组件中的固定元素。

创建一个继承自UIControl的类,使用UIControl,是因为这个组件要当做按钮使用。

继承自UIControl的类结构,要如何重写初始化器?

Swift 复制代码
class QuickknowBtn: UIControl {
    
    override init(frame: CGRect) {//初始化,类都要初始化
        super.init(frame: frame)//super 关键字来调用UIControl 的初始化方法,确保所有继承自父类的初始化逻辑都被执行
        setupUI()//调用自定义的控件(这是一个函数)
    }
    ...
        required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    //记得最后要加上这一段

为什么要重写父类的初始化器?

当创建一个自定义控件(比如按钮或文本框)时,它通常是从现有的控件(父类)继承来的(这里就是UIControl)。父类有自己的初始化代码来设置控件的基础功能。

为什么要重写初始化器?

  1. 确保基础功能正常 :通过super.init()调用父类的初始化器,你确保它的基本功能都被初始化,比如按钮的外观和行为。
  2. 传递必要的参数:将自定义控件所需的参数(比如大小和位置)传递给父类,让它来处理。
  3. 后续配置 :在调用super.init()之后,继续添加自己的设置,比如调整颜色、添加子视图等(这里就是setupUI()

确保父类的初始化逻辑被执行,这里是写死的(所有重写父类控件的都要这样写)。

super.init(frame: frame)调用确保父类UIControl的初始化逻辑被执行。如果不调用这个方法,父类内部的状态可能不会正确设置,这可能导致运行时错误或未定义行为。所以:在重写父类初始化器的时候必须使用super

使用函数自定义控件

Swift 复制代码
    func setupUI() {//调用一个设置用户界面的方法(其实就是那一小块)
    }

在类中自定义需要的控件

(这个过程和系统的FirstViewController过程是一样的)

注意把需要变动的部分留出来不写,就比如背景颜色,文本等等。

Swift 复制代码
//背景矩形
    lazy var bgrect: UIView = {
        let bgrect: UIView = UIView(frame: .init(x: 10, y: 20, width: 68, height: 52))
        bgrect.layer.cornerRadius = 8
        bgrect.layer.masksToBounds = true
        return bgrect
    }()
//名称
    lazy var mbtinames: UILabel = {
        let mbtinames: UILabel = UILabel(frame: .init(x: 25, y: 50, width: 42, height: 16))
        mbtinames.font = .systemFont(ofSize: 16)
        mbtinames.textAlignment = .center
        return mbtinames
    }()
//头像
    lazy var headimg: UIImageView = {
        let headimg = UIImageView(frame: .init(origin: CGPoint(x: 25, y: 0), size: CGSize(width: 40, height: 40)))
        headimg.layer.cornerRadius = 20
        headimg.layer.masksToBounds = true
        headimg.layer.borderWidth = 2//?设置边框
        return headimg
    }()

在控件函数里添加视图

(这是固定的,能适用于所有组件的部分)这样就可以把很多个小部分组合到一起了。

如果要在其他View里调用它,就可以直接使用QuickknowBtn

Swift 复制代码
    func setupUI() {//调用一个设置用户界面的方法(其实就是那一小块)
//        self.backgroundColor = .brown
        self.addSubview(bgrect)
        self.addSubview(mbtinames)
        self.addSubview(headimg)
    }

3.创建一个Model(是一个结构体)。

内容为需要修改的元素属性(如颜色,文本,图片等),注意,能存成String、UIColor,就不要传图片,因为图片很耗费内存,将图片导入Assets之后就可以用字符串的方式传图片了。

这个其实就是MVC里的M,用写好的Model去渲染页面。

烧烤一下,有16组数据,他们的文本和颜色,图片都不一样,我们需要一个地方去存储这些数据,而且要方便随时调用。

又烧烤,存储几组数据,且可以随时调用,用数组的索引(index)可以做到,所以决定用数组来存储这些数据。

那么具体有哪几组数据需要存储呢?分别有背景颜色(bgColor),文本内容(text), 图片名(imgPath), 文本颜色(textColor)。

于是创建一个结构体,定义这些需要储存的数据作为变量。
Swift 复制代码
struct MBTIbaikeModel {
    var bgColor: UIColor
    var text: String
    var imgPath: String
    var textColor: UIColor }

4.在Model内以函数的方式将个性化数据以数组的形式返回,便于调用(函数调用时,利用索引)。

又烧烤,应该怎么使用数组存这些数据呢?

我们的目的是,根据需求,返回对应的一些我们需要的值。

所以最终决定定义一个函数mbtiNames,需要返回的值定义为names,在函数的返回值中存储这个数组。

Swift 复制代码
static func mbtiNames() -> [MBTIbaikeModel] {
        let names =
        [MBTIbaikeModel(bgColor: UIColor(hex: 0xEEFAF0), text: "INFJ", imgPath: "INFJ_head", textColor: UIColor(hex: 0x339F7B)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xEEFAF0), text: "INFP", imgPath: "INFP_head", textColor: UIColor(hex: 0x339F7B)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xEEFAF0), text: "ENFJ", imgPath: "ENFJ_head", textColor: UIColor(hex: 0x339F7B)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xEEFAF0), text: "ENFP", imgPath: "ENFP_head", textColor: UIColor(hex: 0x339F7B)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xF7F0FF), text: "INTJ", imgPath: "INTJ_head", textColor: UIColor(hex: 0x9B52D0)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xF7F0FF), text: "INTP", imgPath: "INTP_head", textColor: UIColor(hex: 0x9B52D0)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xF7F0FF), text: "ENTJ", imgPath: "ENTJ_head", textColor: UIColor(hex: 0x9B52D0)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xF7F0FF), text: "ENTP", imgPath: "ENTP_head", textColor: UIColor(hex: 0x9B52D0)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xFFF8E7), text: "ISTP", imgPath: "ISTP_head", textColor: UIColor(hex: 0xE99623)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xFFF8E7), text: "ISFP", imgPath: "ISFP_head", textColor: UIColor(hex: 0xE99623)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xFFF8E7), text: "ESTP", imgPath: "ESTP_head", textColor: UIColor(hex: 0xE99623)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xFFF8E7), text: "ESFP", imgPath: "ESFP_head", textColor: UIColor(hex: 0xE99623)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xEBF8FF), text: "ISTJ", imgPath: "ISTJ_head", textColor: UIColor(hex: 0x4386D2)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xEBF8FF), text: "ISFJ", imgPath: "ISFJ_head", textColor: UIColor(hex: 0x4386D2)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xEBF8FF), text: "ESTJ", imgPath: "ESTJ_head", textColor: UIColor(hex: 0x4386D2)),
         MBTIbaikeModel(bgColor: UIColor(hex: 0xEBF8FF), text: "ESFJ", imgPath: "ESFJ_head", textColor: UIColor(hex: 0x4386D2)),
        ]
        return names

因为结构体是值类型,调用的时候需要实例。要想直接调用它,需要使用static,这部分被包含在结构体内。

5.确定控件位置:在FirstViewControl中创建一个函数

现在我们有两部分存储控件属性的模块MBTIbaikeModelQuickknowBtn,需要考虑的就是:

1.如何确定每个控件显示的位置?

2.如何加载这些储存控件属性的模块?

3.如何调用这些控件把所有组件打印排列出来?

确定了要解决的问题之后,考虑在函数里完成这些复杂的操作,所以先定义一个函数loadQuiknow

Swift 复制代码
func loadQuiknow() {}

控件的位置x和y并不是固定的,它会随着每个控件所属的行与列的不同,而有不同的值。既然会变化,那必须要用到计算属性。先定义两个x和y的初始值

Swift 复制代码
func loadQuiknow() {
var startx = 16//这个是x初始值

var starty = 41 + 16 //这个是y初始值
}
//参数在这里并不重要,重要的是函数运行的返回结果,返回的就是我们配置好的控件。

横向每个组件的距离是它本身的宽度加他们之间的间隔

纵向每个组件的距离是它本身的长度加他们之间的间隔

我们计算每个组件的x值和y值的时候需要用到这些数据,分别定义他们,以便使用。

Swift 复制代码
func loadQuiknow() {
var startx = 16//这个是x初始值
var starty = 41 + 16 //这个是y初始值
let xgap = 17
let ygap = 34
let itemSize = 68//这个组件的长宽一致,都是68,所以只定义了一个
}

如果要这部分在页面中显示,必须通过addSubView的方式才能把视图添加到页面上。

确定每个控件的位置:这里是一个4×4的结构,同列的x值相同,同行的y值相同。

根据行与列之间的关系,那么我们可以利用数组的索引来控制每个组件的位置。

既然是要把全部控件都打显示出来,可以使用遍历for...in去控制。那么for的是什么呢?遍历的就是数组的索引,这样可以确保数组里的每个内容都被经历。

既然我们要使用数组,在函数里必须用一个常量先加载它(就是结构体中的函数返回的数组)。

Swift 复制代码
 let quicklyModels = MBTIbaikeModel.mbtiNames()
 
 for idx in 0..<quicklyModels.count {} //idx即为数组的索引

6.通过for来遍历结构体中数组的元素,确保每一个元素被返回。

因为要调用数组中的某个元素来进行渲染,而且是通过索引来找到它的,用一个常量来定义它。

Swift 复制代码
 for idx in 0..<quicklyModels.count {
 let model = quicklyModels[idx]//根据数组的索引来决定要调用的元素
 }

因为有4行和4列,同一行x值递增,y值不变。同一列y值递增,x值不变。

也就是说索引为0-3时,x值递增,y值不变。

考虑用商和余数来控制递增或不变的x值和y值。注意要保持是整数,不能是小数。

简单验证下:

假设idx为3,3 / 4取整数商就是0,余数为1,所以y值不需要递增,x值需要。

取一个第二行的索引,假设idx为5,取整数,商为1,余数为2,x值和y值都需要递增。

Swift 复制代码
 for idx in 0..<quicklyModels.count {
 let model = quicklyModels[idx]//根据数组的索引来决定要调用的元素
            let col = CGFloat(idx % 4 )//计算x值需要的参数
            let row = CGFloat(idx / 4 )//计算y值需要的参数
            let x = startx + Int(col) * (itemSize + xgap)
            let y = starty + Int(row) * (itemSize + ygap)
 }

因为QuickknowBtn类是继承自父类UIControl的,所以可以直接调用UIControl的控件,创建一个实例btn,使用(frame: .init)来表示组件的位置。

Swift 复制代码
 for idx in 0..<quicklyModels.count {
 let model = quicklyModels[idx]//根据数组的索引来决定要调用的元素
            let col = CGFloat(idx % 4 )//计算x值需要的参数
            let row = CGFloat(idx / 4 )//计算y值需要的参数
            let x = startx + Int(col) * (itemSize + xgap)
            let y = starty + Int(row) * (itemSize + ygap)
            let btn = QuickknowBtn(frame: .init(x: x, y: y, width: 68, height: 68))

 }

7.配置控件属性:通过调用Model中的函数来配置变动的控件属性。

现在已经解决前两个问题,需要通过Model来渲染这些组件,为它们添加不同的属性。因为要渲染的对应索引中的元素,仍然使用idx来控制需要渲染的元素,所以这里直接使用model来快速调用

Swift 复制代码
 for idx in 0..<quicklyModels.count {
 let model = quicklyModels[idx]//根据数组的索引来决定要调用的元素
            let col = CGFloat(idx % 4 )//计算x值需要的参数
            let row = CGFloat(idx / 4 )//计算y值需要的参数
            let x = startx + Int(col) * (itemSize + xgap)
            let y = starty + Int(row) * (itemSize + ygap)
            let btn = QuickknowBtn(frame: .init(x: x, y: y, width: 68, height: 68))
            btn.bgrect.backgroundColor = model.bgColor//设置背景色
            btn.mbtinames.text = model.text // 设置文本
            btn.mbtinames.textColor = model.textColor//设置文本颜色
            btn.headimg.layer.borderColor = model.textColor.cgColor//设置描边色
            btn.headimg.image = UIImage(named: model.imgPath) //设置显示的图片

 }

设置好之后把创建的QuickknowBtn的实例btn添加到背景视图mbtiwiki里, 让它可以正常显示。

Swift 复制代码
 for idx in 0..<quicklyModels.count {
 let model = quicklyModels[idx]//根据数组的索引来决定要调用的元素
            let col = CGFloat(idx % 4 )//计算x值需要的参数
            let row = CGFloat(idx / 4 )//计算y值需要的参数
            let x = startx + Int(col) * (itemSize + xgap)
            let y = starty + Int(row) * (itemSize + ygap)
            let btn = QuickknowBtn(frame: .init(x: x, y: y, width: 68, height: 68))
            btn.bgrect.backgroundColor = model.bgColor//设置背景色
            btn.mbtinames.text = model.text // 设置文本
            btn.mbtinames.textColor = model.textColor//设置文本颜色
            btn.headimg.layer.borderColor = model.textColor.cgColor//设置描边色
            btn.headimg.image = UIImage(named: model.imgPath) //设置显示的图片
            mbtiwiki.addSubview(btn) 
            }

因为这本身是是一个函数里的内容,要使用一个函数必须调用它。

注意:必须在添加页面之后再调用这个函数。

Swift 复制代码
        self.view.addSubview(titleImg)
        ...
        //添加的其他页面
        loadQuiknow()
最终效果图:

专题3:添加滚动视图(使用UIScrollView)

现在我们写好了好几个模块,但是屏幕太短了,要看到下面的内容,必须要让模块滑动起来。

UIScrollView视图

用于滚动操作,现在在需要滚动的页面创建一个UIScrollView。

Swift 复制代码
    lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView(frame: self.view.bounds)
        scrollView.contentSize = CGSize(width: self.view.frame.width, height: 1000)
        scrollView.alwaysBounceVertical = true // 开启垂直回弹
        return scrollView
    }()
}

1.通过.contentSize来控制需要滚动的范围。CGSize是一个用于表示宽度和高度的结构体。这里用来创建一个表示内容大小的实例。

self.view.frame.width获取当前视图(通常是视图控制器的主视图)的宽度。

2.通过.alwaysBounceVertical来开启垂直回弹

将需要滚动的视图添加进UIScrollView视图中,并将UIScrollView添加进当前页面的视图中

Swift 复制代码
        self.view.addSubview(scrollView)
        scrollView.addSubview(test_32q)
        scrollView.addSubview(test_72q)
        scrollView.addSubview(mbtiwiki)
        scrollView.addSubview(careerbtn)
        scrollView.addSubview(widgetbtn)

最终可以实现滚动效果

相关推荐
知识分享小能手1 小时前
R语言入门学习教程,从入门到精通,R语言类别比较数据可视化- 完整知识点与案例代码(4)
学习·信息可视化·r语言
蛋白界小百灵2 小时前
纳米抗体技术全解析:从文库构建到亲和力成熟的关键策略
经验分享·科技·学习·健康医疗·业界资讯·卡梅德生物
我是发哥哈2 小时前
主流AI框架生产环境性能对比:5大关键维度深度评测
大数据·人工智能·学习·机器学习·ai·chatgpt·ai-native
nashane2 小时前
HarmonyOS 6学习:RCP远场通信流式返回实战——告别“一次性”数据阻塞
学习·华为·harmonyos
for_ever_love__2 小时前
UI学习:UITableView的基本操作及折叠cell
学习·ui·ios
Alice-YUE3 小时前
【JS高频八股】什么是闭包?
开发语言·javascript·笔记·学习
宵时待雨3 小时前
linux笔记归纳3:linux开发工具
linux·运维·笔记
摇滚侠4 小时前
Java 零基础全套视频教程,面向对象(高级),笔记 105-120
java·开发语言·笔记
tq10864 小时前
程序行为的效应构成:约束、规则与延迟固化的统一视角
笔记