I’m Zanxinz👋

UICollectionView

注册cell 的时候,是 XX 类型和 YY identifier。 一个 XX 可以对应多个 YY。 collectionView.register(MyFirstCell.self, forCellWithReuseIdentifier: "FirstCell") collectionView.register(MySecondCell.self, forCellWithReuseIdentifier: "SecondCell") 应用场景:同一个 UICollectionView 可以有多种 cell,例如有一个额外的带有 ➕ 的 cell,用于向 UICollectionView 添加新的元素。

January 10, 2025

Runloop

问题: 定时器最好设置为 NSRunLoopCommonModes 在 iOS 中,界面滑动时 RunLoop 会切换到 UITrackingRunLoopMode,而默认情况下 NSTimer 运行在 NSDefaultRunLoopMode,导致滑动时 NSTimer 无法触发或与主线程争夺资源,引发性能下降甚至卡顿。 原因 NSTimer 未被正确配置到适当的 RunLoop 模式中,导致滑动和定时器事件无法同时被处理。 解决方法 将 NSTimer 添加到 NSRunLoopCommonModes,使其在滑动模式下也能正常触发: NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerFired) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; swift 实现 let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in self.timerFired() } RunLoop.current.add(timer, forMode: .common) iOS 中的 RunLoop 使用 模式(Modes) 来区分和管理不同的事件集合。模式决定了 RunLoop 能够处理哪些事件,它们之间是互相独立的。在运行时,RunLoop 会根据当前的模式过滤事件。 常见的 RunLoop 模式 NSDefaultRunLoopMode • 描述: 默认模式,处理大多数普通任务。 • 用途: 用于处理常规的输入事件,例如用户交互、Timer 事件、网络事件等。 ...

December 30, 2024

ViewController 生命周期

1. 实例化阶段:init(coder:) 或 init(nibName:bundle:) 被调用 2. 加载视图阶段:loadView → viewDidLoad 3. 视图即将显示:viewWillAppear → viewWillLayoutSubviews 4. 视图完成布局:viewDidLayoutSubviews → viewDidAppear 5. 视图显示期间:viewWillLayoutSubviews/viewDidLayoutSubviews(根据需要多次调用) 6. 视图即将消失:viewWillDisappear 7. 视图已经消失:viewDidDisappear 8. 内存警告:didReceiveMemoryWarning(可能在任何时候发生) 9. 销毁阶段:deinit class LifecycleViewController: UIViewController { // 1. 初始化 override init(nibName: String?, bundle: Bundle?) { super.init(nibName: nibName, bundle: bundle) print("1. 初始化完成") } required init?(coder: NSCoder) { super.init(coder: coder) } // 2. 加载视图 override func loadView() { super.loadView() print("2. loadView 被调用") } // 3. 视图加载完成 override func viewDidLoad() { super.viewDidLoad() print("3. viewDidLoad 被调用") } // 4-5. 视图显示过程 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) print("4. viewWillAppear 被调用") } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() print("5. viewWillLayoutSubviews 被调用") } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() print("6. viewDidLayoutSubviews 被调用") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) print("7. viewDidAppear 被调用") } // 6-7. 视图消失过程 override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) print("8. viewWillDisappear 被调用") } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) print("9. viewDidDisappear 被调用") } // 8. 内存警告 override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() print("收到内存警告") } // 9. 析构 deinit { print("10. 视图控制器被销毁") } }

December 29, 2024

App 生命周期

未运行(Not Running):应用尚未启动或被系统终止 非活动(Inactive):应用在前台运行但不接收事件,如来电或推送通知时 活动(Active):应用在前台正常运行并可以接收事件 后台(Background):应用在后台运行,可执行有限的任务 挂起(Suspended):应用在后台但不执行代码,可能随时被系统终止 App 启动 (App Starts) • 入口方法: application:didFinishLaunchingWithOptions • 该方法是应用启动时的入口点。 • 通常用来初始化应用程序,例如加载配置文件、设置窗口、配置依赖项等。 • 根据是否有传入的 URL 参数,流程会有所不同: • 有 URL: 转入 application:openURL:sourceApplication:annotation: 处理 URL。 • 无 URL: 继续进入活跃状态。 应用进入前台 (Foreground Run Event Loop) • 入口方法: applicationDidBecomeActive • 应用进入前台并开始响应事件。 • 此时,用户可以与应用正常交互,例如触摸、滑动等。 中断事件 (Interruptions) • 例如接听电话、跳转其他应用。 • 入口方法: applicationWillResignActive • 应用即将进入非活动状态(暂停交互)。 • 适合在这里保存数据或暂停需要持续运行的任务。 进入后台 (Background Run Loop) • 入口方法: applicationDidEnterBackground • 应用进入后台,此时需要确保应用资源的正确管理: • 保存用户数据。 • 如果需要继续后台运行,需设置 info.plist 或开启后台任务。 • 如果应用无法在后台继续运行,则可能被系统暂停或终止。 ...

December 29, 2024

Oc Summary

类和结构体的区别 计算属性 struct Rectangle { var width: Double var height: Double // 这是一个计算属性 var area: Double { get { return width * height } set { // 假设保持宽高比例不变 let ratio = width / height height = sqrt(newValue / ratio) width = height * ratio } } } 关联对象 Category 和 Extension 的区别 Category(分类): 可以在不修改原类源代码的情况下给类添加方法 不能添加实例变量(存储属性),但可以使用关联对象 可以被添加到任何类中,包括没有源码的类 在运行时添加方法 可以有多个分类 Extension(扩展): 只能在原类的实现文件(.m文件)中添加 可以添加实例变量和属性 必须在类的主实现文件中实现所有声明的方法 在编译时添加特性 只能有一个扩展 @interface MyClass () // Extension 1 @property (nonatomic, strong) NSString *property1; @end @interface MyClass () // Extension 2 @property (nonatomic, strong) NSString *property2; @end // 编译后相当于只有一个扩展 @interface MyClass () @property (nonatomic, strong) NSString *property1; @property (nonatomic, strong) NSString *property2; @end OC 可以动态添加属性或方法,但开销较大、类型安全性差、降低代码可维护性和可读性,swift 中限制了这种方式。 ...

December 28, 2024

“小速记” App 介绍

《小速记》各部分功能介绍 主页面,各个功能入口 ToDo 功能 新建 Todo 时,可设定 Todo 的表情 emoji 、时间段。 右滑删除,左滑完成 Todo,长按 Todo 可设定一个在几分钟后的系统通知推送(提醒这个 Todo) 每日打卡 可以新建任务,设定为每日打卡、每周打卡或每月打卡。 打卡任务有进度,任务完成后会将任务归档。 数字记录器 可以为某一件事情添加计数器,用于腐竹记忆生活中的琐碎数字。 数据 Todo、每日打卡、数字记录器是都作为任务,使用 CoreData 存放于本地。 对应的,有 ArchivedTodo、ArchivedDailyTask、ArchivedRecord 作为归档对象,在任务完成后作为记录存放于本地。 演示 App 使用视频演示

July 7, 2023

如何通过点击 UICollectionViewCell 跳转至对应的 UIViewController

跳转部分的实现 我需要从我的 HomeViewController 通过点击不同的 CollectionViewCell 跳至不同的 ViewController 思路: 首先是需要把目标 ViewController 存放起来,在点击 cell 时可以作为目的地进行 present 跳转。 因为我的多个不同的 ViewController 都继承自 UIViewController, 那么我想用它作为父类型存放在cell中。 先把 Main storyboard 存为当前类的属性,以方便初始化各个 collectionView 然后使用 storyboard 自带的动态反射方法 instantiateViewController,通过字符串找到对应的 ViewController 在 dataSource 的实现中,将 cell 的属性绑定为对应的 controllerView 目标。 最后是 didTapCell 方法,是点击后的具体要做的动作,即跳转。这里的 target 类型是 UIController。 发现这样实现不了,原因是第 1 步中 MainChoiceCell 中的 targetController 不能是 weak,若为 weak 那么它在被赋值然后在函数结束时(closure 结束)会释放引用也就是恢复为 nil,所以正确的做法应该是把 weak 去掉。 手指触碰 UICollectionViewCell 但未释放,这属于 Highlight UICollectionViewCell 底层来自 UIView。重写 highlight 的 willSet, 手指点在 UICollectionViewCell 区域会触发 highlight 置为 true ;手指不松开,移动到不属于 UICollectionViewCell 的区域,则会触发 highlight 置为 false。 ...

May 17, 2023

N4000 四网口工控机组网

需要用到的硬件 工控机本体这里称为 A, 一台可以连接 WiFi 或者可插入以太网网线的设备称为 B。 公网的接入端口线称为 C。 一个 Wifi 路由器D。 键盘与HDMI 显示器(用于命令行查看系统信息,可省略)、两条网线。 方法一:”不同网段“接法 软路由的 LAN 接 WiFi 的 WAN,连接 WiFi 的设备和直连软路由的设备不算是同一网段。 WiFi 所在 192.168.1.x 软路由所在 192.168.11.x 关键步骤: 以太网连接,从 C 上接一条网线到 A 的 eth1 端口。D 的 WAN 网口和 A 的 eth0 用网线连接。 使用设备 B,连接 WiFi,然后登入后台管理页面,网址一般在 WiFi 路由器背后有写明。 设备B 需要关键一步:设置为 自动 IP 分配,那么才可以在 B 上访问到 192.168.11.1 关键配置: 登入D 的 后台页面(依据不同品牌而定,我这里是 192.168.1.1)。WiFi 路由器 D 的设置,需要设置为 自动获得 IP 地址,拔掉 WiFi 的电源,重启,它会通过A、公网 C 自动获得 IP。这时,连接到该 WiFi 的设备就可以通过 192.168.11.1 进入软路由的管理页面了。 ...

May 13, 2023

Hugo 的一些问题

若域名有变动 需要修改三个地方 workflow 文件夹里 Github Acction 配置文件 Action.yml 中的 cname: config.yaml 中的 baseURL Github repository setting Pages

March 11, 2023

Hugo 发布一篇文章的过程

安装 windows:确保有 hugo.exe 在工程目录下, 并且 .gitignore 里面写上 hugo.exe,即可 Mac:确保 hugo 已安装就可以 新建 一般,都在 post 文件夹下放 markdown 文件,使用不同文件夹来归类 hugo new post/tool/useHugo/publishArticle.md 书写 设定文章的 title, categories, tags 写入内容。标题大小从 ## 开始 本地预览 hugo server 编译成 html,输出到 /docs (路径与 GitHub Action 对应) hugo 到这里,就完成了写作 Git Push 先 fetch,再 commit,再 push。 Github Page 需要重新填写域名 因为 Github Action 在执行的时候会把 master 分支中的 /docs 内所有内容拷贝到 main 分支,这里面不包括 CNAME 文件。所以在 repository 的 setting 的 pages 重新填写域名。(问题已经解决,在 workflow action.yml 中添加 cname: 1-1.link,所以如果域名有改动,需要在这修改) ...

March 11, 2023