1、SimpleClock
(1)Timer Class
Timer与Run loop是紧密相关的。Run loop负责维护对其计时器的强引用,因此在将计时器添加到Run loop后,不必维护自己对计时器的强引用。
要有效地使用计时器,应该知道Run循环是如何运行的,参阅线程编程指南。
重复计时器和非重复计时器的比较:
- 重复计时器会触发,然后在相同的Run loop中重新调度自己。
- 非重复计时器触发一次,然后自动使自身无效,从而防止计时器再次触发。
- 重复定时器总是根据计划的触发时间而不是实际的触发时间来调度自己。
例如,如果计时器计划在特定时间和之后每5秒触发一次,则计划的触发时间将始终落在原始的5秒时间间隔内,即使实际的触发时间被延迟。如果触发时间延迟到超过一个或多个计划的触发时间,则计时器在该时间段内仅触发一次;然后在触发后将计时器重新调度为未来的下一个计划的触发时间。这里我是这么理解的:比方说,有一个每隔1小时就触发一次的重复计时器,假设30分钟后,用户将APP缩小到桌面,2小时后重新打开APP(假设APP没有被干掉),那么在30分钟后,重复计时器才会第二次触发,中间没有在运行循环中的时间就不算了。
创建计时器有三种方法:
- 使用
scheduledTimer(timeInterval:invocation:repeats:)
或scheduledTimer(timeInterval:target:selector:userInfo:repeats:)
类方法创建计时器,并将其安排在默认模式下的当前运行循环上。 - 使用
Init(TimeInterval:Invoocation:Repeats:)
或init(timeInterval:target:selector:userInfo:repeats:)
类方法创建Timer对象,而不在Run loop中调度它。(创建计时器后,必须通过调用相应RunLoop对象的Add(_:forMode:)
方法手动将计时器添加到Run loop中。) - 分配计时器并使用
init(fireAt:interval:target:selector:userInfo:repeats:)
方法对其进行初始化。(创建计时器后,必须通过调用相应Run loop对象的Add(_:forMode:)
方法手动将计时器添加到Run循环中。)
总的来说,如果使用`scheduledTimer`方法初始化Timer,则自动添加到默认的Run loop中,而使用`init`方法初始化的Timer则需要手动指明要加到哪个Run loop中。
一旦计划运行循环,计时器就会以指定的时间间隔触发,直到其失效。非重复计时器在触发后立即使其自身失效。但是,对于重复计时器,您必须通过调用Timer对象的invalate()
方法来使其无效。调用此方法会请求从当前Run loop中移除计时器;因此,应该始终从安装计时器的同一线程中调用invalate()
方法。举个栗子🌰:
1 |
|
2、CustomFont
(1)自定义字体
导入字体步骤:
- 下载ttf文件,加入项目中。
- 在info.plist中,添加一个字段:Fonts provided by application。
- 再添加item,值写入字体的名字。
- 然后就可以通过名字使用了。
1 |
|
3、PlayLocalVideo
(1)AVPlayer
概述:
AVPlayer是用于管理媒体资源的回放和计时的控制器对象。您可以使用AVPlayer播放本地和远程基于文件的媒体。
AVPlayer一次只能播放一个视频资源,但可以使用replaceCurrentItem(with:)
方法重复使用该播放器实例来播放其他媒体资源。
AVPlayer拥有一个名为AVQueuePlayer
的子类,用于创建和管理按顺序播放的媒体资源的队列。(这个还没看,但是估计和AVPlayer差不多,里面应该有一个类型为AVPlayerItem
的数组)。
AVPlayer可以播放AVFoundation使用AVAsset
类建模的媒体资源。
但AVAsset仅对媒体的静态方面进行建模,如其during
或creatTime
,并且其本身不适合使用AVPlayer进行回放。要回放资源,您需要创建在AVPlayerItem
中找到的其动态副本的实例。
AVPlayer是一个状态不断变化的动态对象。有两种方法可以监控AVPlayer的状态,分别是一般状态观察和定时状态观测,二者分别使用 Key-value observing (KVO) 和addPeriodicTimeObserver(forInterval:queue:using:)
or addBoundaryTimeObserver(forTimes:queue:using:)
来实现。
可视化:
AVPlayer和AVPlayerItem是非可视化的对象,也就是说,这些对象无法直接在屏幕上显示,可以通过两种方法将它们显示出来:
- AVKit:使用AVKit框架的AVPlayerViewController类。(官方推荐最佳方法)
- AVPlayerLayer: 这种方法可以将资源直接加到图层上,因此可以作为背景。
初始化:
AVPlayer有两种初始化方法,分别是基于URL
和基于AVPlayerItem
:
1 |
|
AVPlayer 的一些属性和方法:
1 |
|
简单来说,AVPlayer可以用于控制播放🎵各个细节,比如调整播放速率、播放时间,以及暂停⏸️、恢复播放,调整声音🔊、静音🔇等…
和AVPlayerItem之间的关系:
AVPlayerItem存储对AVAsset对象的引用,该对象表示要播放的媒体。
可以通过传递AVPlayerItem
来初始化AVPlayer
:
1 |
|
同样的,可以通过currentItem
这个只读属性来查看当前的AVPlayerItem
。
AVPlayerItem可以通过将所需的keys
传递给其init(asset:automaticallyLoadedAssetKeys:)
初始值设定项来自动加载所需的数据。这个我还没太看懂是什么意思,给一段官方的代码:
1 |
|
AVPlayerItem 的一些属性和方法:
1 |
|
按照目前的理解,AVPlayer
是在实际播放的过程中,控制播放速率、暂停播放,等等,而AVPlayerItem
则更多的是关注于资源本身的能力,比如,能否支持倍速播放,能否支持快进,等等。
AVPlayerViewController:
首先,通过名字就可以看出,它是一个controller(废话),既然是ViewController类型,必然有一个控制的对象。就像ViewController
负责控制一个View,AVPlayerViewController
控制的是一个player: AVPlayer?
。同时,AVPlayerViewController
还可以遵循一个AVPlayerViewControllerDelegate
类型的协议,这个协议可以用来对视频播放状态的改变做出相应,比如开始画中画,结束画中画,
开始全屏,结束全屏,需要注意的是⚠️,AVPlayerViewControllerDelegate
协议中所有的方法都是可选的,也就是说,如果不需要自定义协议中的方法,实际上AVPlayerViewController
无须遵循AVPlayerViewControllerDelegate
协议。
插播一条小知识:
今天在使用Bundle寻找本地资源时,总是显示添加资源为空:
1 |
|
搜了一下发现需要将资源手动先添加到Bundle Resources
中,步骤如下:
- 点击 Project
- 点击 target
- 选择 Build Phases
- 展开 Copy Bundle Resources
- 点击 ‘+’ 并添加文件
完整代码:
1 |
|
4、WelcomeView
(1)UIScrollView
ScrollView允许滑动和缩放其包含的视图。UITableView
、UITextView
、UICollectionView
都是它的子类。
关于响应手势:
因为ScrollView没有滚动条,所以它必须知道触摸是否表示要滚动,而不是要跟踪内容中的子视图。为了进行此确定,它通过启动计时器来临时截获触碰事件,并在计时器触发之前查看触摸的手指是否有任何移动。如果计时器触发而位置没有显著变化,则ScrollView将跟踪事件发送到内容视图的被触摸的子视图。如果用户在计时器到期之前将手指拖得足够远,ScrollView将取消子视图中的任何跟踪,并自动执行滚动。子类可以重写TouchesShouldBegin(_:with:in:)
、isPagingEnabled
和TouchesShouldCancel(in:)
方法(滚动视图调用这些方法),以影响滚动视图处理滚动手势的方式。
ScrollView还处理内容的缩放和平移。当用户做出缩小或缩小手势时,ScrollView会调整内容的偏移量和比例。当手势结束时,管理内容视图的对象根据需要更新内容的子视图。(请注意,手势可能会结束,而手指可能仍然向下。)当手势正在进行时,ScrollView不会向子视图发送任何跟踪调用。
UIScrollView类可以遵循UIScrollViewDelegate协议。要使缩放和平移起作用,代理必须同时实现viewForZoom(in:)
和scllViewDidEndZooming(_:with:atScale:)
。此外,maximumZoomScale
和maximumZoomScale
必须不同。
一些属性和方法:
1 |
|
代码举栗🌰:
1 |
|
(2)UIPageControl
UIPageControl是一个相对简单的控件,主要作用就是显示一系列水平圆点,每个圆点对应于数据模型实体中的一个页面。
当用户点击其中一个圆点时,该控件会通过调用func pageViewController(UIPageViewController, willTransitionTo: [UIViewController])方法
向代理发送valueChanged
事件。
代码举栗🌰:
1 |
|
5、PictureBrowse
(1)UICollectionView
应该可以这么理解:UICollectionView的DataSource负责数据,Layout负责布局,Delegate负责事件
UICollectionView管理有序的数据项集合并使用可自定义布局呈现它们的对象。
布局:
UICollectionViewLayout类的对象定义集合视图中内容的视觉排列。Layout对象类似于另一个数据源,不同之处在于它提供可视信息而不是项数据。
通常在创建集合视图时指定布局对象,但也可以通过collectionViewLayout属性
动态更改集合视图的布局,设置此属性会立即直接更新布局,而不会以动画方式进行更改。如果要以动画形式显示更改,可以调用setCollectionViewLayout(_:animated:completion:)
方法。
Cells and Supplementary Views:
CollectionView会维护一个data source
已标记为要重复使用的视图对象的队列或列表。
当需要新的Cell时,不是在代码中显式创建新视图,而是始终将旧视图从复用队列中出列。根据需要使用的view类型的不同,有两种view的请求方法:
1 |
|
数据预取:
CollectionView提供了两种预取技术来提高响应速度:
-
Cell预取:Cell的渲染发生在早于该Cell显示所需的时间,从而带来更流畅的滚动体验。Cell Prefetch是默认开启的。
-
Data预取:当Cell的数据加载开销很大的时候,比如数据是通过网络请求得到的时,Data预取显得比较重要,将遵循
UICollectionViewDataSourcePrefetching
协议的对象分配给prefetchDataSource
属性,以接收何时预取单元格数据的通知。(这种方式之前从来没有使用过,可能是因为我们的数据一般都会先请求完成后再刷新UICollectionView
)
Reordering Items Interactively:
Collection views允许基于用户交互来移动项。可以通过调用beginInteractiveMovementForItem(at:)
方法来直接移动,在调用过程中使用updateInteractiveMovementTargetPosition(_:)
方法来记录触摸位置,最后再通过endInteractiveMovement()
来更新试图。
UICollectionView遵循的协议类型为UICollectionViewDelegate
,协议中所有方法均为optional
,主要包括:管理被选中的Cell、Cell高亮、跟踪添加或删除Cell、响应Layout 的变化、管理上下文菜单等等。
举个栗子🌰:
1 |
|
(2) UIVisualEffectView
UIVisualEffectView可以为View添加一个毛玻璃的效果!
1 |
|
6、SystemRefreshControl
UIRefreshControl
UIRechresControl对象是附加到任何UIScrollView对象(包括表视图和集合视图)的标准控件。将此控件添加到可滚动的视图中,为用户提供刷新其内容的标准方式。当用户向下拖动可滚动内容区域的顶部时,滚动视图显示刷新控件,开始设置进度指示器动画,并通知您的应用程序。可以使用该通知来更新您的内容并取消刷新控件。
1 |
|
一些属性:
1 |
|
7、GradientColor
这个项目主要是展示渐变的颜色,用到了 CAGradientLayer()
这个类。(第一次见到,反正知道他是一个CALayer就好了)
1 |
|
7、VideoBackground
这个项目主要介绍了怎么能在将视频作为背景播放,实现起来也没有什么特别难的地方,基本和播放本地视频那一节是一样的,只不过他是将VideoPlayerController的View放在了最后一层,这里学一下动画:
1 |
|
8、UIPickerView
UIPickerView相对来说是一个功能比较简单的控件,有点类似于密码箱上的密码盘。和UITableView
一样,需要遵循UIPickerViewDelegate
和UIPickerViewDataSource
两个协议。
其中,UIPickerViewDataSource
主要负责数据源,有且仅有两个必选的方法:
1 |
|
UIPickerViewDataSource
主要负责用户选择之后的回调以及UIPickerView的宽度、高度、View的自定义
1 |
|
9、UIButton图片在上,文字在下
1 |
|
10、UIEdgeInsets
UIEdgeInsets
先来看看这个UIEdgeInsets:
typedef struct UIEdgeInsets {
CGFloat top, left, bottom, right; // specify amount to inset (positive) for each of the edges. values can be negative to 'outset'
} UIEdgeInsets;
原来是结构体,它的四个参数:top, left, bottom, right, 分别表示距离上边界,左边界,下边界,右边界的位移,默认值均为0。
contentEdgeInsets
我们都知道,UIButton按钮可以只设置一个UILabel或者一个UIImageView,还可以同时具有UILabel和UIImageView;如果给按钮设置contentEdgeInsets属性,就是按钮的内容整体(包含UILabel和UIImageView)进行偏移。 按钮内容整体向右下分别移动10像素:
Button.contentEdgeInsets = UIEdgeInsetsMake(10, 10, -10, -10);
titleEdgeInsets & imageEdgeInsets
这两个属性的效果是相辅相成的。如果给一个按钮同事设置了title和image,他们默认的状态是图片在左,标题在右,而且image和title之间没有空隙;那就这就引出一个问题,title和image的UIEdgeInsets属性分别的相对于谁而言的?
真相只有一个: image的UIEdgeInsets属性的top,left,bottom都是相对于按钮的,right是相对于title; title的UIEdgeInsets属性的top,bottom,right都是相对于按钮的,left是相对于image;
知道真相的你不知道有没有眼泪流下来,怪不得之前怎么设置都不是想要的结果,原来相对于谁的位移压根没有搞清楚。现在既然搞清楚了,我们来试一下:
title在左,image在右:
1 |
|
image在上,title在下:
1 |
|
按照我现在的理解,只要能够保证UIEdgeInsets中的「上和下」、「左和右」是互为相反数就可以了,其他的不用考虑那么多
比方说, UIEdgeInsetsMake(10, 0, -10, 0)
就代表按钮中的内容向下整体平移10像素
先这么理解,有问题再来填坑。
11、NotificationCenter
基本用法:
1 |
|
observer:注册观察器的对象👀
selector:发送通知时调用的方法,该方法必须有且只能有一个参数(NSNotification的实例)。
name:通知的名称,这里感觉比较混乱,因为这个参数的类型是NSNotification.Name?
,但是实际使用的时候,这个结构体中的静态属性并不能包含所有通知的名称,比如说上面这个代码的name参数就是从UIResponder的extension中取到的,现在还不是很理解。
填坑了填坑了!
在SWIFT 4.2中,大量的
Notification.Name
实例成为其他类中的实例变量。例如,keyboardWillShowNotification
现在是UIResponder
的实例变量。
刚才又看到了Name结构体的定义:
1 |
|
也就是说,NSNotification.Name
是可以自己定义的,可是如果自己定义一种通知名称的话,如何指定什么时候发送这个通知呢?晕了。
关于键盘出现、消失的通知名可以参考这里。原来是Xcode的一个Bug啊!还好我机智,没看答案自己都能改过来,哈哈哈!
技术感觉上去了,加油!
12、键盘拉起,消失的一些注意事项
1 |
|
13、给Cell中的按钮添加点击事件🔘
1 |
|
14、ScrollView底部判断
1 |
|
15、悬浮窗
touchesBegan
、touchEnded
等方法是UIResponder
的类方法。该类还有becomeFirstResponder() -> Bool
、resignFirstResponder() -> Bool
等方法。
UIView拥有hitTest
和point
方法,用于确定第一响应对象,hitTest
和point
的关系是:hitTest
会递归的调用point
,直到确定最合适的响应对象。
1 |
|
16、可左滑编辑的UITableView
1 |
|
17、UIGestureRecognizer
1 |
|