Add files via upload
182
IOS/Task01:基础插件与功能/1. Cocopods的安装与使用.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Cocopods的安装与使用
|
||||
|
||||
## Cocopods介绍
|
||||
|
||||
CocoaPods是管理iOS项目所依赖的第三方开源库的工具,其项目源码在Github上管理。若项目开发不使用Cocopods,我们引入第三方开源库要做的步骤可能有:
|
||||
|
||||
- 把开源库的源代码复制到项目中
|
||||
- 添加一些依赖框架和动态库(可能还需要引入其他的第三方开源库)
|
||||
- 设置-ObjC,-fno,-objc,-arc 等参数
|
||||
- 管理他们的更新
|
||||
|
||||
CocoaPods 是管理第三方插件的合集,其出现帮助节省了配置和更新第三方开源库的时间。 CocoaPods 将所有依赖的库都放在一个名为 Pods 的项目下,然后让主项目依赖 Pods 项目。然后, 我们编码工作都从主项目转移到 Pods 项目。Pods 项目最终会编译为一个 libPod-项目名.a 静态库, 主项目依赖于这个静态库。对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本在每次项目编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中。
|
||||
|
||||
CocoaPods 通过一个名为 Pods.xcconfig 的文件来在编译时设置所有的依赖和参数。
|
||||
|
||||
CocoaPods 是用 Ruby 写的,并由若干个 Ruby 包 (gems) 构成的。在解析整合过程中,最重 要的几个 gems 分别是:CocoaPods/CocoaPods, CocoaPods/Core, 和 CocoaPods/Xcodeproj。
|
||||
|
||||
|
||||
|
||||
## Cocopods安装
|
||||
|
||||
在使用 Cocopods 之前,我们需要在`Terminal`进行安装:
|
||||
|
||||
1. 安装需要用到 Ruby,虽然 Mac 自带了 Ruby,但版本需要更新:
|
||||
|
||||
```bash
|
||||
sudo gem update −−system
|
||||
```
|
||||
|
||||
2. 输入密码后安装 Cocopods:
|
||||
|
||||
```bash
|
||||
sudo gem install cocoapods
|
||||
```
|
||||
|
||||
3. 如果安装过程过慢导致失败,可以更换国内下载源,安装完成后,在 `Terminal` 中:
|
||||
|
||||
- cd 到目标工程文件路径下
|
||||
- pod init 会看到 Podfile 文件
|
||||
- vim Podfile 即可向其中插入想引用的第三方库
|
||||
|
||||
|
||||
|
||||
## 示例:安装第三方库
|
||||
|
||||
本部分我们将安装
|
||||
|
||||
|
||||
|
||||
新建Xcode项目myFirstApp后关闭Xcode(过程可参考Task00中的第一部分教程),打开终端(command+ "空格" )
|
||||
|
||||
1. cd到目标目录下:
|
||||
|
||||
```bash
|
||||
cd Downloads/code/myFirstApp/
|
||||
```
|
||||
|
||||
2. 进行Cocopods初始化
|
||||
|
||||
```bash
|
||||
pod init
|
||||
```
|
||||
|
||||
3. 打开Podfile文件
|
||||
|
||||
```bash
|
||||
open Podfile
|
||||
```
|
||||
|
||||

|
||||
|
||||
4. 插入如下命令后关闭Podfile
|
||||
|
||||
```bash
|
||||
pod 'Charts'
|
||||
pod 'TinyConstraints'
|
||||
```
|
||||
|
||||

|
||||
|
||||
5. 返回`终端`(Terminal),进行Pod更新,等待片刻即可
|
||||
|
||||
```bash
|
||||
pod update
|
||||
```
|
||||
|
||||
6. 在目录下双击运行 `myFirstApp.xcworkspace`
|
||||
|
||||

|
||||
|
||||
7. 在`myFirstApp`-`myFirstApp`-`ViewController.swift`导入加载的两个第三方库
|
||||
|
||||
```swift
|
||||
import Charts
|
||||
import TinyConstraints
|
||||
```
|
||||
|
||||

|
||||
|
||||
8. 即可完成加载,完整代码如下(暂时无需弄懂代码含义,复制即可):
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
import Charts
|
||||
import TinyConstraints
|
||||
|
||||
class ViewController: UIViewController,ChartViewDelegate {
|
||||
lazy var lineChartView:LineChartView={
|
||||
|
||||
let chartView = LineChartView()
|
||||
chartView.backgroundColor = .systemGray6
|
||||
chartView.rightAxis.enabled = false
|
||||
let yAxis = chartView.leftAxis
|
||||
yAxis.labelFont = .boldSystemFont(ofSize: 12)
|
||||
yAxis.setLabelCount(6, force: false)
|
||||
yAxis.labelTextColor = .black
|
||||
yAxis.labelPosition = .outsideChart
|
||||
|
||||
chartView.xAxis.labelPosition = .bottom
|
||||
chartView.xAxis.labelFont = .boldSystemFont(ofSize: 12)
|
||||
chartView.xAxis.setLabelCount(6, force: false)
|
||||
chartView.xAxis.labelTextColor = .black
|
||||
chartView.xAxis.axisLineColor = .systemGray
|
||||
chartView.animate(xAxisDuration: 4.5)
|
||||
|
||||
return chartView
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.addSubview(lineChartView)
|
||||
lineChartView.centerInSuperview()
|
||||
lineChartView.width(to:view)
|
||||
lineChartView.heightToWidth(of:view)
|
||||
setData()
|
||||
}
|
||||
|
||||
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
|
||||
print(entry)
|
||||
}
|
||||
|
||||
func setData(){
|
||||
let set1 = LineChartDataSet(entries: yValues, label: "Subscribers")
|
||||
let data = LineChartData(dataSet:set1)
|
||||
lineChartView.data = data
|
||||
|
||||
}
|
||||
|
||||
let yValues:[ChartDataEntry] = [
|
||||
ChartDataEntry(x:0.0, y:10.0),
|
||||
ChartDataEntry(x:1.0, y:9.0),
|
||||
ChartDataEntry(x:2.0, y:8.0),
|
||||
ChartDataEntry(x:3.0, y:7.0),
|
||||
ChartDataEntry(x:4.0, y:6.0),
|
||||
ChartDataEntry(x:5.0, y:5.0),
|
||||
ChartDataEntry(x:6.0, y:4.0),
|
||||
ChartDataEntry(x:7.0, y:3.0),
|
||||
ChartDataEntry(x:8.0, y:2.0),
|
||||
ChartDataEntry(x:9.0, y:1.0)
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
9. 点击运行,预览状态如下:
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/4.png" alt="4" style="zoom:50%;" />
|
||||
|
||||
## 如何寻找自己心仪的第三方库?
|
||||
|
||||
如何找到`Charts`与`TinyConstraints`等优秀的第三方库?如何知晓不同的第三方库的安装命令?你可以访问Github的开源项目:
|
||||
|
||||
[awesome-ios](https://github.com/vsouza/awesome-ios)
|
||||
|
||||
这里面罗列了iOS开发流程中可能用到的第三方库,以`Charts`为例,我们点击相应label进行跳转
|
||||
|
||||

|
||||
|
||||
就会得到安装该库的命令:
|
||||
|
||||

|
||||
|
||||
至此,我们已经学会了基本的第三方库引入,下面我们要学习基本控件的应用,以更好地运用到我们的app上
|
||||
217
IOS/Task01:基础插件与功能/2. ScrollView的应用.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# ScrollView的应用
|
||||
|
||||
## 什么是ScrollView?
|
||||
|
||||
`ScrollView`(即滚动视图)可被应用到多种场景,例如:纵向滚动视图可以用来上下滑动以查看超出屏幕尺寸的内容,横向滚动视图可以用来实现翻页、轮播图等功能。事实上, 对某个图片的缩放、移动等功能也是由ScrollView 来实现的。在本章中,你将学会如何开启计时器,如何纵向、横向滚动查看页面,如何滑动查看图片以及实现轮播图等。
|
||||
|
||||
## Storyboard中添加Scroll View
|
||||
|
||||
在storyboard控件库中搜索`Scroll View`,并拖拽至面板中,放大到与屏幕同尺寸。
|
||||
|
||||

|
||||
|
||||
右侧属性栏中Indicators部分罗列了两种滚动条:`纵向滚动`与`横向滚动`,如果我们不想在滑动过程中显示这两种滚动条,取消勾选即可。
|
||||
|
||||
|
||||
|
||||
## ScrollView 的代理及基本属性
|
||||
|
||||
在 `Main.storyboard` 中添加了 ScrollView 之后,我们需要手动在 `UIViewController` 中代理`UIScrollViewDelegate`,实现步骤如下:
|
||||
|
||||
1. 打开`Main.Storyboard`,鼠标右键将`ScrollView`控件与`ViewController`面板相连,点击`delegate`
|
||||
|
||||

|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/sc delegate.png" alt="sc delegate" style="zoom:50%;" />
|
||||
|
||||
2. 打开 `ViewController.swift`,在 `class UIViewController` 后添加 `UISCrollViewDelegate`
|
||||
|
||||
代理仅仅允许滚动视图可以进行滚动,但并没有明确何时才进行滚动。因此我们需要监听 ScrollView(捕捉用户动作),从而使当用户在上面做出滑动动作时,ScrollView 开始进行滚动。函数如下:
|
||||
|
||||
```swift
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView){}
|
||||
```
|
||||
|
||||
上述内容完毕后,我们即可在 `viewDidLoad()` 中对 ScrollView 进行各项参数初始化设置:
|
||||
|
||||
```swift
|
||||
self.scrollView.contentSize = self.imageView.frame.size //(去掉自动布局)
|
||||
//在图片内进行滚动,滚动范围为图片的尺寸
|
||||
|
||||
self.scrollView.contentSize = CGSize(width: lastImage.bounds.size.width, height:1000)
|
||||
//在页面中滚动,滚动范围为代码所设置的页面尺寸
|
||||
|
||||
//在页面中滚动,手动设置移动坐标
|
||||
var point:CGPoint = scrollView.contentOffset
|
||||
point.x += 150
|
||||
point.y += 150
|
||||
self.scrollView.contentOffset = point
|
||||
|
||||
self.scrollView.setContentOffset(contentOffset:CGPoint, animated:Bool)
|
||||
//加动画模式
|
||||
|
||||
self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsHorizontalScrollIndicator = false
|
||||
//显示/隐藏水平、垂直滚动器
|
||||
|
||||
self.scrollView.contentInset = UIEdgeInsets(top:100, left: CGFloat,bottom: CGFloat, right:CGFloat)
|
||||
//图片内边距
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 坐标参数
|
||||
|
||||
通常,我们以屏幕左上角作为坐标系的原点,垂直向下为y轴正半轴,水平向右为x轴正半轴。坐标需要专用的类型来定义,即 `CGPoint`:
|
||||
|
||||
```swift
|
||||
CGPoint:坐标参数
|
||||
CGSize:设置大小,如 ScrollView,TableView 大小等
|
||||
CGFloat:CG 浮点数,用于计算坐标
|
||||
CGRect:设置尺寸,框架范围
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 实现竖直、水平滚动
|
||||
|
||||
1. 竖直滚动:竖直滚动既可以通过 storyboard 实现,也可以通过代码实现,在 `viewDidLoad()` 中添加代码部分如下:
|
||||
|
||||
```swift
|
||||
self.scrollView.contentSize = CGSize(width: lastImage.bounds.size.wid th, height:1000)
|
||||
```
|
||||
|
||||
其中,`width`、`height` 可以通过手动计算得出,也可以通过图片宽和高的对应乘积让编译器自己设置数值。值得注意的是,此处仅接受 `CGFloat` 格式。
|
||||
|
||||
2. 水平滚动:在纵向滚动处,我们可以通过 storyboard 来设置 `Image` 或者 `Button` 尺寸,但水平滚动则不同,我们无法通过一块屏幕来设置超出屏幕尺寸的多个横向图,因此需 要手动设置代码。在 `viewDidLoad()` 中:
|
||||
|
||||
```swift
|
||||
var imageView1 = UIImageView()
|
||||
imageView1.frame = CGRect(x: 0, y: 0, width: 414, height: 180)
|
||||
imageView1.image = UIImage(named:"one.jepg")
|
||||
scrollView.addSubview(imageView1)
|
||||
self.scrollView.contentSize = CGSize(width: 2050, height: 0) self.scrollView.showsHorizontalScrollIndicator = false滚动播放逻辑
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 滚动播放逻辑
|
||||
|
||||
我们经常会在一些 app 上看到置顶的一些循环播放的广告图,这些广告被称之为轮播图。事实上,轮播图的本质也是一种滚动视图,接下来的两个部分你将学着自己构建出一个轮播图。
|
||||
|
||||
想要实现图片的横向滚动播放,需要我们在预定区域内横向同时放置 5 张图片,通过 `ScrollView` 控件实现 5 张图片的滚动查看。本章中,我们已经学习了通过一个 `Button` 来实现图片滚动的方法,此部分我们将把图片滚动和计时器(NSTimer)绑定到一起从而达到自动播放的效果。由于计时器应在程序初始时加入到内存中,因此我们将代码写入 `viewDidLoad()` 内。初始化计时器代码如下:
|
||||
|
||||
```swift
|
||||
let timer:Timer? = Timer.scheduledTimer(timeInterval: 3.0, target: sel f, selector:#selector(myTimer), userInfo: nil, repeats: true)
|
||||
timer?.fire()
|
||||
```
|
||||
|
||||
请注意,在 Timer 函数中我们擅自使用了#selector(myTimer)方法,此类方法需要
|
||||
|
||||
Objective-C 类型函数才能执行。事实上,随着学习内容的深入你会发现:在 Swift 中不仅仅是计时器才会用到`#selector` 方法,其他情况下如音频播放等基础功能也同样需要自定义 `selector` 的方法。事实上,所有的监听事件都需要`@obcj` 的方法来实现,不过随着版本的不断更新,Apple 逐渐在试着将这些 `objc` 的方法淡出 Swift 舞台,但这并不代表着你现在可以完全放弃使用 OC 的方法,本笔记中也会延续这一习惯。回到计时器问题上,此刻我们需要定义一个 OC 类型且并列于 `viewDidLoad()` 的函数:
|
||||
|
||||
```swift
|
||||
@objc func myTimer(){}
|
||||
```
|
||||
|
||||
至此,初始化计时器已经完成。我们先不急着将坐标偏移量与计时器函数绑定到一起,先试着让 `ScrollView` 页面可实现手动在几个图片之间滚动,以及将 `pageControl` 的 `currentPage` 与当前 `ScrollView` 页面同步。
|
||||
|
||||
如何才能实现两者同步滚动?由于 `scrollView` 与 `pageControl` 是两个独立的控件,我们没办法将两者进行绑定,二者的同步似乎成了一个难题。然而,换个思路看这个问题, 从结果导向和用户体验角度来说,我们仅仅希望看到当 `scrollView` 滚动时,`pageControl` 也随之滚动,至于两者是否是真的同步我们并不关心,也不用去确认他们两个是否真的被 绑定到了一起,只要让它看起来是一体化就达到了我们的目的。对于这个问题,一种比较常用的解决思路是计算当前图片在 `scrollView` 中偏移的 x 值,并用 x 值和当前页面图片 (以左上角为原点,横向为 x 正半轴,纵向为 y 轴正半轴)初始的 x 坐标作商,结果取整来定位 `scrollView` 目前的页面。由于 `pageControl `是索引值,因此初始为 0,在实际操作中我们需要根据不同情况来进行作商取整后的一些列加减数值实现同步。
|
||||
|
||||
在其他默认参数通过 storyboard 中设置完成后,我们可以通过监听滚动视图的值实现 两者同步,在 `scrollViewDidScroll()` 函数:
|
||||
|
||||
```swift
|
||||
var page:Int
|
||||
page = Int(self.scrollView.contentOffset.x / 414)
|
||||
self.pageControl.currentPage = page
|
||||
```
|
||||
|
||||
上述代码中,我们通过对滑动动作的监听,实现了当前页面与 pageControl 的同步 效果,接下来我们将这两者一并写入计时器方法中:
|
||||
|
||||
```swift
|
||||
@objc func myTimer(){
|
||||
if page==0{
|
||||
self.scrollView.contentOffset.x = 0
|
||||
page += 1
|
||||
self.scrollView.setContentOffset(CGPoint(x:self.scrollView.contentOffset.x, y:0), animated: true) }
|
||||
elseif page<5 && page>0 {
|
||||
self.scrollView.contentOffset.x += point
|
||||
page += 1
|
||||
self.scrollView.setContentOffset(CGPoint(x:self.scrollView.contentOffset.x, y:0), animated: true) }
|
||||
elseif page==5{
|
||||
page=0
|
||||
self.scrollView.setContentOffset(CGPoint(x: self.scrollView.contentOffset.x, y:0), animated: true)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
至此,我们已经成功搭建了自己的第一个轮播图。事实上,计时器的使用对内存来说是一种负担,尤其是多个计时器同时启用时,它们会占据大量的内存空间,因此我们必须学会如何在程序结束的时候销毁计时器,后面我们会逐步了解。
|
||||
|
||||
|
||||
|
||||
## 轮播图参考代码
|
||||
|
||||
```swift
|
||||
//定义部分
|
||||
var page:Int = 0
|
||||
let point:CGFloat = 414
|
||||
|
||||
//页面同步
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
page = Int(self.scrollView.contentOffset.x / 414)
|
||||
self.pageControl.currentPage = page
|
||||
self.scrollView.setContentOffset(CGPoint(x:self.scrollView.contentOffset.x, y:0), animated: true)
|
||||
}
|
||||
|
||||
//手动添加五张图片
|
||||
var imageView1 = UIImageView()
|
||||
var imageView2 = UIImageView()
|
||||
var imageView3 = UIImageView()
|
||||
var imageView4 = UIImageView()
|
||||
var imageView5 = UIImageView()
|
||||
|
||||
//在 viewDidLoad 里添加 image
|
||||
imageView1.frame = CGRect(x: 20, y: 80, width: 374, height: 180)
|
||||
imageView1.image = UIImage(named:"one.jepg")
|
||||
scrollView.addSubview(imageView1)
|
||||
|
||||
imageView2.frame = CGRect(x: 434, y: 80, width: 374, height: 180)
|
||||
imageView2.image = UIImage(named:"two.jepg")
|
||||
scrollView.addSubview(imageView2)
|
||||
|
||||
imageView3.frame = CGRect(x: 848, y: 80, width: 374, height: 180)
|
||||
imageView3.image = UIImage(named:"three.jepg")
|
||||
scrollView.addSubview(imageView3)
|
||||
|
||||
imageView4.frame = CGRect(x: 1262, y: 80, width: 374, height: 180)
|
||||
imageView4.image = UIImage(named:"four.jepg")
|
||||
scrollView.addSubview(imageView4)
|
||||
|
||||
imageView5.frame = CGRect(x: 1676, y: 80, width: 374, height: 180)
|
||||
imageView5.image = UIImage(named:"five.jepg")
|
||||
scrollView.addSubview(imageView5)
|
||||
|
||||
//scrollView 总尺寸
|
||||
self.scrollView.contentSize = CGSize(width: 2050, height: 0) self.scrollView.showsHorizontalScrollIndicator = false
|
||||
|
||||
//定义、开始计时器,在 override 内
|
||||
let timer:Timer? = Timer.scheduledTimer(timeInterval: 3.0, target:self, selector: #selector(myTimer), userInfo: nil, repeats: true)
|
||||
timer?.fire()
|
||||
|
||||
//定时器启用方法函数 @objc func myTimer(){
|
||||
if page==0 {
|
||||
self.scrollView.contentOffset.x = 0
|
||||
page += 1
|
||||
self.scrollView.setContentOffset(CGPoint(x:self.scrollView.contentOffset.x, y:0), animated: true) }
|
||||
elseif page<5 && page>0 {
|
||||
self.scrollView.contentOffset.x += point
|
||||
page += 1
|
||||
self.scrollView.setContentOffset(CGPoint(x:self.scrollView.contentOffset.x, y:0), animated: true) }
|
||||
elseif page==5 {
|
||||
page=0
|
||||
self.scrollView.setContentOffset(CGPoint(x: self.scrollView.contentOffset.x, y:0), animated: true)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
199
IOS/Task01:基础插件与功能/3. TableView的应用.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# TableView的应用
|
||||
|
||||
## 什么是 TableView?
|
||||
|
||||
`TableView` 直译为表格视图,如今,我们手机上的各种 app 都存在着表格视图:当我们打开知乎时,首先看到的就是一个 `TableView`,首页上的每一个回答都是 `TableView` 中的一个 `cell`,只不它是一种自定义的表格视图;当我们在淘宝搜索栏中搜索某一关键词后,屏幕上出现的也是`TableView`,每一个商品都是`TableView`上的一个`cell`;当我们打开微信想找某个对话框时,对话框也是 `TableView` 上的 `cell`。通过本章以及下一章的学习,你将学习如何做出联系人列表,以及如何构建基本的表格视图界面。当然,仅有`TableView` 是不够的,在下一章结束后,你将掌握更多的技能。
|
||||
|
||||
## Storyboard中添加Table View
|
||||
|
||||
在storyboard控件库中搜索`Table View`,并拖拽至面板中,放大到与屏幕同尺寸。
|
||||
|
||||

|
||||
|
||||
页面的右侧罗列了Table View的一些基本属性,第一个部分为单元格模式,分为两种:`Dynamic Prototypes`与`Static Cells`
|
||||
|
||||
`动态单元格`的原型在我们日常应用中十分常见,例如:iPhone上的`信息`应用就是一种动态表格,其特性为支持动态添加和删除。
|
||||
|
||||
`静态单元格`的数据内容相对固定,很少会用到动态添加和删除,但支持对表格的分组,通常可以用作个人界面的信息显示。Storyboard中提供了静态单元格的简易搭建(提示:在实际的开发中很少这样使用)本处仅作为演示使用。(下图为静态单元格)
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/8.png" alt="8" style="zoom:50%;" />
|
||||
|
||||
|
||||
|
||||
## 数据源代理
|
||||
|
||||
`UITableView` 继承自 `UIScrollView`,在进行 `ViewController` 编辑时,有如下两种方法对其进行代理:
|
||||
|
||||
- 第一种
|
||||
|
||||
1. 鼠标`右键`点击 `Table View`面板的空白处,拖拽至 `View Controller` 处与之连接,点击`dataSource`与`delegate`
|
||||
|
||||

|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/datasource.png" alt="datasource" style="zoom:50%;" />
|
||||
|
||||
2. 在`ViewController.swift`中添加如下代码:
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
class ViewController: UIViewController, UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这就完成了TableView的初始化,接下来我们仅需要在函数中添加相应代码即可运行。
|
||||
|
||||
- 第二种
|
||||
|
||||
1. 拖拽 `UITableView` 至代码中
|
||||
|
||||
2. 在 `ViewController` 中添加如下代码(或利用智能补全):
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
class ViewController: UIViewController, UITableViewDataSource{
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.tableView.dataSource = (self as UITableViewDataSource)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||

|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/11.png" alt="11" style="zoom: 50%;" />
|
||||
|
||||
## TableView 的基本属性
|
||||
|
||||
完成代理后,代码部分需要设置 `UITableView` 的各项参数,与 `ScrollView `不同的是,`TableView` 存在两个必须继承的方法:
|
||||
|
||||
1. ```swift
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return 1
|
||||
}
|
||||
```
|
||||
|
||||
2. ```swift
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
code
|
||||
}
|
||||
```
|
||||
|
||||
上述内容中,前者为每一个 section 有多少个 rows,后者为对每个 rows 进行属性设置,
|
||||
|
||||
下一章中我会对 rows 中的属性进行详细说明,本章仅探讨 `TableView` 的一些性质。除了这两个必须继承的方法外,`TableView` 还有一个十分重要的内容:设置 section 数量。若想显示不同分组,需要我们手动添加一个方法,否则只显示第一组内的单元格(注:分组显示需要在 storyboard 中设置Grouped):
|
||||
|
||||
```swift
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 1
|
||||
}
|
||||
```
|
||||
|
||||
若不设置此方法,return 默认值为 1,`TableView` 中除了上述的三个方法外,还有如下的几个属性:
|
||||
|
||||
```swift
|
||||
rowHeight//统一设置所有行高度
|
||||
separatorColor//分割线颜色
|
||||
separatorStyle//分割线样式
|
||||
tableHeaderView//可在分组单元格上方放置
|
||||
title tableFooterView//可以用来显示加载更多页面
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 国家分组 demo
|
||||
|
||||
接下来以一个国家分组 demo 练习表格视图的分组:
|
||||
|
||||
```swift
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
//一共分几组,若没有此则默认为 1 section,不会显示出了第一个以外的 section
|
||||
return 3
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
//组标题,即页眉
|
||||
if section == 0{
|
||||
return "亚洲"
|
||||
}else if section == 1{
|
||||
return "非洲"
|
||||
}else {
|
||||
return "欧洲"
|
||||
}
|
||||
}
|
||||
|
||||
/* func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
//组注释,即页尾
|
||||
if section == 0{
|
||||
return "Asia"
|
||||
}else if section == 1{
|
||||
return "Africa"
|
||||
}else {
|
||||
return "Euro"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if section == 0{
|
||||
//亚洲
|
||||
return 3
|
||||
}else if section == 1{
|
||||
//非洲
|
||||
return 2
|
||||
}else {
|
||||
//欧洲
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = UITableViewCell.init(style: UITableViewCell.CellStyle.default, reuseIdentifier: nil)
|
||||
if indexPath.section == 0 { //亚洲
|
||||
if indexPath.row == 0 {
|
||||
cell.textLabel?.text = "中国"
|
||||
}else if indexPath.row == 1{
|
||||
cell.textLabel?.text = "日本"
|
||||
}else if indexPath.row == 2{
|
||||
cell.textLabel?.text = "韩国"
|
||||
}
|
||||
|
||||
}else if indexPath.section == 1 {
|
||||
//非洲
|
||||
if indexPath.row == 0{
|
||||
cell.textLabel?.text = "南非"
|
||||
}else {
|
||||
cell.textLabel?.text = "坦桑尼亚"
|
||||
}
|
||||
|
||||
}else {
|
||||
cell.textLabel?.text = "英国"
|
||||
}
|
||||
return cell
|
||||
}
|
||||
```
|
||||
|
||||
值得注意的是,如果我们在工程文件中大量套用if else函数会导致性能大大下降,该部分仅为演示时使用,在下一章中我们会集中探讨如何通过数组来定义 `cell`。
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/12.png" alt="12" style="zoom:50%;" />
|
||||
111
IOS/Task01:基础插件与功能/4. Cell的应用.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Cell
|
||||
|
||||
## Cell 的基本属性
|
||||
|
||||
在前一章的学习中我们初步学习了 `cell`,但事实上,在国家分组 demo 中我们仅仅是构建了一个动态单元格(Dynamic Prototypes),`TableView`中的`Cell`共有两种类型,分别是动态单元格(Dynamic Cell)和静态单元格(Static Cell),前者往往用于联系人列表、商品信息表、新闻软件的信息栏等图片和文字需要经常变化的地方,而后者往往被用于系统设置、个人页面的设置等不轻易改变单元格本身属性内容的位置。
|
||||
|
||||
通常来说,定义一个 `cell` 通常有以下两种方式:
|
||||
|
||||
```swift
|
||||
let cell = UITableViewCell.init(style: UITableViewCell.CellStyle.default, re useIdentifier: nil)
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "RegistrationCell",
|
||||
for: indexPath)
|
||||
```
|
||||
|
||||
在简单的页面设置中,我们通常可以用第一类定义cell,值得注意的是,例子中给出的 `CelStyle.default`是`cell`的一种style,该格式不会显示除主标题以外的任何内容,对于本章的Emoji案例来说,这显然是不合适的,因此我们在具体设置时将其改为`CellStyle.subtitle`。以下罗列了一些cell的属性:
|
||||
|
||||
```swift
|
||||
imageView//主视图
|
||||
textlabel//大标题
|
||||
detailTextLabel//小标题
|
||||
accessoryType//更多信息
|
||||
accessoryView//更多信息
|
||||
backgroundColor//单元格背景颜色
|
||||
backgroundView//单元格背景图片,制定一个 UIImageView 就可以
|
||||
selectedBackgroundView//当某行被选中时候的背景
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Emoji 的 TableView 显示
|
||||
|
||||
此前我们通过 if-else 函数定义了一些单元格。在实际工程文件中,我们往往需要通过数组来确定单元格数量,在后面的章节你会了解到,开发工程师们会在互联网中引用 JSON 文件来直接调用服务器中的数组,这就赋予了 app 生命力,让其不断更新数据。本部分我们通过自定义一个 Emoji 数组来模拟这一过程,需要注意的是,我们将在此使用**动态单元格**(Dynamic Prototypes)。
|
||||
|
||||
接下来我们将通过一个构建demo案例:Emoji的`tableView`显示来学习数组与cell定义之间的联系,在以后的学习过程中,我们通常要用到这种方法。
|
||||
|
||||
1. 并列于 `UIViewController` 定义一个类:
|
||||
|
||||
```swift
|
||||
class Emoji {
|
||||
var symbol: String
|
||||
var name: String
|
||||
var description: String
|
||||
var usage: String
|
||||
init(symbol: String, name: String, description: String, usage: String) {
|
||||
self.symbol = symbol
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.usage = usage
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. 代理、继承 `UITableViewDataSource`:
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController,UITableViewDataSource {}
|
||||
```
|
||||
|
||||
3. 定义 `UITableViewCell` 属性:
|
||||
|
||||
```swift
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section:
|
||||
Int) -> Int {
|
||||
if section == 0 {
|
||||
return emojis.count
|
||||
}else{
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let Emoji:String = "Emoji"
|
||||
let cell:UITableViewCell = UITableViewCell.init(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: Emoji)
|
||||
let emoji = emojis[indexPath.row]
|
||||
cell.textLabel?.text = "\(emoji.symbol) - \(emoji.name)"
|
||||
cell.detailTextLabel?.text = emoji.description
|
||||
return cell
|
||||
}
|
||||
```
|
||||
|
||||
4. 定义子类:
|
||||
|
||||
```swift
|
||||
var emojis: [Emoji] =
|
||||
[Emoji(symbol: "😀", name: "Grinning Face", description: "A typical smiley face.", usage: "happiness"),
|
||||
|
||||
Emoji(symbol: "😕", name: "Confused Face",description: "A confused, puzzled face.", usage: "unsure what to think; displeasure"),
|
||||
|
||||
Emoji(symbol: "😍", name: "Heart Eyes", description: "A smiley face with hearts for eyes.", usage: "love of something; attractive"),
|
||||
|
||||
Emoji(symbol: "👮", name: "Police Officer", description: "A police officer wearing a blue cap with a gold badge.", usage: "person of authority"),
|
||||
|
||||
Emoji(symbol: "🐢", name: "Turtle", description:"A cute turtle.", usage: "Something slow"),
|
||||
|
||||
Emoji(symbol: "🐘", name: "Elephant", description: "A gray elephant.", usage: "good memory"),
|
||||
|
||||
Emoji(symbol: "🍝", name: "Spaghetti",description: "A plate of spaghetti.", usage: "spaghetti"),
|
||||
|
||||
Emoji(symbol: "🎲", name: "Die", description: "A single die.", usage:"taking a risk, chance; game"),
|
||||
|
||||
Emoji(symbol:"⛺", name: "Tent", description: "A small tent.", usage: "camping"),
|
||||
|
||||
Emoji(symbol: "📚", name: "Stack of Books", description: "Three colored books stacked on each other.", usage: "homework, studying")]
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
至此,你已经认识了一些基本控件。在接下来的两章中,我将通过酒店管理系统与贪吃蛇来帮助你熟悉更多的基本控件并强化你的基本功,学习完毕后你将具备最基本的一些开发能力,并且能够搭建自己的一些简单 app,继续加油!
|
||||
|
||||
61
IOS/Task01:基础插件与功能/5. 常见的Controller概览.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 常见的Controller概览
|
||||
|
||||
|
||||
|
||||
## 主视图控制器 View Controller
|
||||
|
||||
在前面的任务中,我们打开Storyboard即可看到主视图控制器 View Controller,这是一种最基本、最常见的视图控制器。例如:当我们需要实现Table View的功能时,我们可以在控件库中添加 Table View插件于 View Controller中;也可以添加Scroll View于 View Controller中以实现页面滚动。View Controller更像是一张空白的A4纸,我们可以在上面书写任何想要的内容。
|
||||
|
||||
在本章的学习过程中,请确保您的视图控制器中的一个处于`Is Initial View Controller`状态,这样您的程序在运行时才不会崩溃。
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/ViewController.png" alt="ViewController" style="zoom: 33%;" />
|
||||
|
||||
|
||||
|
||||
## Table View Controller
|
||||
|
||||
前文我们已经通过Table View控件实现了表格单元。事实上,控件库中提供了一种简易的实现方式:Table View Controller,直接添加相应控制器即可,该Controller与Table View完全一致,你可以直接在右侧信息栏中设置相关属性。
|
||||
|
||||
如果把View Controller比作空白的A4纸,那么在View Controller上添加Table View控件相当于在A4纸上画满一条一条的格线——这当然是一种办法,但我们常常还有另外的一种简易方法:直接使用带格线的笔记纸。Table View Controller正是这种带格线的笔记纸。
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/Table View Controller.png" alt="Table View Controller" style="zoom: 50%;" />
|
||||
|
||||
|
||||
|
||||
## Page View Controller
|
||||
|
||||
某乎的首页界面就是一个Page View Controller,Page View并不是在底部的控制栏中进行切换,你可以看到,即使在相同的Tab Bar Item中,我们也可以对页面进行左右切换,这正是Page View Controller的用处。
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/page1.PNG" alt="page1" style="zoom: 20%;" /> <img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/page2.PNG" alt="page2" style="zoom:20%;" />
|
||||
|
||||
在Xcode中,我们可以使用Page控件进行实现,当然也可以使用Page View Controller来“一劳永逸”地解决Page切换问题。
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/Page View Controller.png" alt="Page View Controller" style="zoom:50%;" />
|
||||
|
||||
## Navigation Controller
|
||||
|
||||
你可能并不清楚Navigation Controller究竟指的是什么,但是你一定用过且见过Navigation Controller。例如,在手机的设置中任何一个界面都是一个Navigation,它常常与带Cell的Table View一起出现
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/navigation bar.PNG" alt="navigation bar" style="zoom: 25%;" />
|
||||
|
||||
上图中,`主屏幕`与`设置` 回退button都是Navigation Controller上的一部分。
|
||||
|
||||

|
||||
|
||||
## Tab Bar Controller
|
||||
|
||||
上面的Page View Controller提供了一种在页面内进行切换的方式。然而,以微信为例,如果我们要实现不同页面的切换,我们需要点击屏幕下端的item进行切换。Tab Bar Controller正是实现该功能的控制器。
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/tab bar item.jpg" alt="tab bar item" style="zoom: 33%;" />
|
||||
|
||||
在Xcode中添加Tab Bar Controller,会直接出现三个视图控制器(当然我们可以手动添加更多),分别是Tab Bar Controller主控制器与两个Item实现我们可以在右侧的属性栏中手动选择每个Item对应的图标、文字,当然也可以设置更多的属性,你可以自行探索。
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/Tab Bar Controller.png" alt="Tab Bar Controller" style="zoom:50%;" />
|
||||
|
||||
## Collection Controller
|
||||
|
||||
除了上述视图控制器外,还有一种常见的视图控制器:Collection Controller,这是一种展览、罗列内容相关的Controller。例如,一些电子杂志app会将近几期发行本以矩阵形式罗列;一些音乐app也会以这种方式罗列自己的唱片集合。iPhone中的`图书` 功能就是Collection Controller的一种
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/Collection.PNG" alt="Collection" style="zoom: 15%;" /> <img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/Collection View Controller.png" alt="Collection View Controller" style="zoom: 40%;" />
|
||||
|
||||
熟悉这些基本的视图控制器可以帮助我们在实际开发过程中设计出更符合逻辑的app,后面,我们会在这些控制器上进行操作,以实现各种不同的内容。
|
||||
166
IOS/Task01:基础插件与功能/6. 动画、音频与视频.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# 动画、音频与视频
|
||||
|
||||
## 动画
|
||||
|
||||
流畅的动画是 iOS 的灵魂所在。对于开发者而言,合理地使用动画,让用户体验得到提升是攻城狮们的必修课程。在前面的课程中我们 已经熟悉了对 Swift 的基础控件,接下来我们将把学习内容放到设计结构上。
|
||||
|
||||
对于动画的应用一般在 View 控件上,动画的种类大体上分为仿射变换、旋转变换以及平移三 种,对于动画过程中的速度时间我们都可以进行设置,这三种变换是所有动画的基础,例如:我们可以通过平移来实现 app 的加载和退场,可以使用仿射变换实现音乐播放时封面的动画,从而让用户有真实感。此外,我们还可以通过旋转来使整个程序增添一份趣味。下面的代码罗列了一些基本的动画使用方法,你需要熟练掌握并且试着学会如何应用。在 playgrounds 中:
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
import PlaygroundSupport
|
||||
|
||||
//初始化 View
|
||||
let liveViewFrame = CGRect(x: 0, y: 0, width: 500, height: 500) let liveView = UIView(frame : liveViewFrame) liveView .
|
||||
backgroundColor = . white
|
||||
|
||||
PlaygroundPage . current . liveView = liveView
|
||||
let smallFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
|
||||
let square = UIView(frame: smallFrame)
|
||||
square.backgroundColor = .purple
|
||||
liveView.addSubview(square)
|
||||
|
||||
//添加动画
|
||||
UIView.animate(withDuration: 2.0) {
|
||||
square . backgroundColor = . orange
|
||||
let scaleTransform = CGAffineTransform(scaleX : 2.0 , y: 2.0) //添加仿射变换
|
||||
let rotateTransform = CGAffineTransform(rotationAngle: .pi)//添加旋转变换
|
||||
let translateTransform = CGAffineTransform(translationX:200, y: 200) //添加移动变换
|
||||
let comboTransform = scaleTransform.concatenating(rotateTransform ).concatenating (translateTransform ) square .transform = comboTransform //将三种变换合成
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 短效果音播放
|
||||
|
||||
一个有趣的 app 离不开各种令人印象深刻的音效。在 Swift 中,我们通常使用 AudioToolbox 与 AVFoundation 来播放音频和视频。假设导入一个 30s 内的 wav 格式音频,并将其命名为 myVoice, 接下来,我们需要将该音频的所在路径设置为虚拟的 url 地址,并将音频播放的地址设置为该地址:
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
import AudioToolbox
|
||||
|
||||
class ViewController : UIViewController{
|
||||
override func viewDidLoad () {
|
||||
super.viewDidLoad ()
|
||||
var _soundId : SystemSoundID = 0
|
||||
let path = Bundle.main.path(forResource: ”myVioce”,ofType: ”wav”)
|
||||
let soundUrl = URL( fileURLWithPath : path ! )
|
||||
AudioServicesCreateSystemSoundID ( soundUrl as CFURL,&_soundId )
|
||||
AudioServicesAddSystemSoundCompletion(_soundId , nil , nil ,{(soundID , clientData ) −> Void in
|
||||
print(”音频播放后重复之前的播放”)
|
||||
AudioServicesPlaySystemSound ( soundID )
|
||||
}, nil)
|
||||
AudioServicesPlaySystemSound ( _soundId )
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 长音乐音乐播放
|
||||
|
||||
在一款 app 中,仅仅有 30s 的效果音可能还不够,有时候我们需要导入长音乐。Swift 中内置了一款音乐播放器,对于基础开发者来说足够应用。该播放器的设置过程和短效果音的思路相同,我们也需要设置 url 路径来导入我们的本地音乐。请注意,Swift 内置的音频播放器中并没有暂停和继续播放的功能,我们需要手动设置一个 Button 来实现该效果:
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
class ViewController: UIViewController,AVAudioPlayerDelegate {
|
||||
var audioPlayer:AVAudioPlayer = AVAudioPlayer()
|
||||
//初始化音乐播放器为 AVAudioPlayer 格式
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
//令路径为项目文件下的音乐文件
|
||||
let path = Bundle.main.path(forResource: "兰亭序", ofType: "mp3") //将路径转化为 URL 路径
|
||||
let soundUrl = URL(fileURLWithPath: path!)
|
||||
do{
|
||||
try audioPlayer = AVAudioPlayer(contentsOf: soundUrl) audioPlayer.volume = 1.0
|
||||
//音频音量为 1.0
|
||||
audioPlayer.numberOfLoops = -1 //-1 为无限循环播放
|
||||
audioPlayer.delegate = self audioPlayer.play()
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
//定义一个暂停/恢复播放按钮
|
||||
let stopMusic = UIButton(frame: CGRect(x: 20, y: 80, width: 280, height: 44))
|
||||
stopMusic.backgroundColor = UIColor.blue
|
||||
stopMusic.setTitle("暂停/恢复音乐", for:UIControl.State.init(rawValue:0))
|
||||
|
||||
//为该按钮添加点击事件
|
||||
stopMusic.addTarget(self, action: #selector(ViewController.pauseOrResumeMusic), for: .touchUpInside)
|
||||
self.view.addSubview(stopMusic)
|
||||
}
|
||||
|
||||
//填写 objc 方法
|
||||
@objc func pauseOrResumeMusic(){
|
||||
if self.audioPlayer.isPlaying{
|
||||
//isPlaying 判断音乐是否在播放,若播放则暂停,反之亦然
|
||||
self.audioPlayer.pause()
|
||||
}else{
|
||||
self.audioPlayer.play()
|
||||
}
|
||||
}
|
||||
|
||||
func audioPlayerBeginInterruption(_ player: AVAudioPlayer) {
|
||||
print("被打断")
|
||||
}
|
||||
|
||||
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||
print("播放完成")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
附件中携带了一份我自己制作的兰亭序伴奏demo.mp3,你可以用这个音频文件进行功能实现尝试。
|
||||
|
||||
|
||||
|
||||
## 视频播放
|
||||
|
||||
实现了音频播放,该如何实现视频播放呢?Swift 中内置了视频播放的功能,总体上来说,本地视频播放的思路和音频一样,都需要我们设置视频的 url 地址,将本地视频导入其中。在此之前我们需要导入 AVFoundation 头文件,在 `viewDidLoad()`中:
|
||||
|
||||
```swift
|
||||
let moviePath = Bundle.main.path(forResource: "movie", ofType: "mp4")
|
||||
let movieURL = URL(fileURLWithPath: moviePath!)
|
||||
|
||||
let avPlayer = AVPlayer(url: movieURL as URL)
|
||||
let avPlayerLayer = AVPlayerLayer(player:avPlayer)
|
||||
avPlayerLayer.frame = self.view.bounds
|
||||
avPlayerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill self.view.layer.addSublayer(avPlayerLayer)
|
||||
avPlayer.play()
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 借助播放器
|
||||
|
||||
在上面的代码中,我们实现了视频的播放,不过你可能发现在该 项目中我们无法进行暂停与快进,更不用提画中画和声音等功能,要想实现这些内容,我们需要借助 Swift 内置的播放器:
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import AVKit
|
||||
|
||||
class ViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
//设置视频路径,令 URL 为预设地址
|
||||
let moviePath = Bundle.main.path(forResource: "movie", ofType: "mp4")
|
||||
let movieURL = URL(fileURLWithPath: moviePath!)
|
||||
//设置播放器
|
||||
let avPlayer = AVPlayer(url:movieURL as URL)
|
||||
let playerVC = AVPlayerViewController()
|
||||
playerVC.player = avPlayer
|
||||
playerVC.videoGravity = AVLayerVideoGravity.resizeAspect
|
||||
/*.resizeAspectFill 等比缩小,填满整个范围
|
||||
.resizeAspect 等比播放,中间播放 */
|
||||
playerVC.allowsPictureInPicturePlayback = true
|
||||
playerVC.showsPlaybackControls = true playerVC.view.frame = self.view.bounds
|
||||
playerVC.player!.play()
|
||||
self.view.addSubview(playerVC.view)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
92
IOS/Task01:基础插件与功能/7. 基本权限与url访问.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 基本权限与url访问
|
||||
|
||||
## 分享图片
|
||||
|
||||
我们的 app 肯定会用到各种权限,权限的申请也是开发者需要学习的一门课程,本章中提供了一些基本的权限请求方式,在后面的基础功能部分你将学习更多的内容。
|
||||
|
||||
此部分提供了分享照片的基本权限,我们可以通过一个 Button 来分享照片:
|
||||
|
||||
```swift
|
||||
@IBOutlet var imageView: UIView!
|
||||
@IBAction func shareButton(_ sender: Any) {
|
||||
guard let image = imageView else { return }
|
||||
let activityController = UIActivityViewController(activityItems:[image],applicationActivities: nil)
|
||||
activityController.popoverPresentationController?.sourceView = sender as? UIView present(activityController, animated: true, completion: nil)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 网页链接
|
||||
|
||||
如果想要 `Button` 连接到浏览器,则需要对网页做出请求,在此前需要导入 `Safari Services` 头文件:
|
||||
|
||||
```swift
|
||||
import SafariServices
|
||||
|
||||
@IBAction func Safari(_ sender: Any) {
|
||||
if let url = URL(string: "http://www.bilibili.com") {
|
||||
let safariViewController = SFSafariViewController(url:url)
|
||||
present(safariViewController, animated: true,completion: nil)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 提示与弹窗
|
||||
|
||||
如果想要获得照片权限,你需要一个弹窗,不仅如此,在选择头像的功能中,你也需要弹窗提示:从照片中选择、拍摄照片和取消三个部分:
|
||||
|
||||
```swift
|
||||
let alertController = UIAlertController(title: "Choose Image Source", message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||
let cameraAction = UIAlertAction(title: "Camera", style: .default, handler: {action in print("User selected Camera action") })
|
||||
|
||||
let photoLibraryAction = UIAlertAction(title: "Photo Library", style: .default, handler: { action in print("User selected Photo Library action") })
|
||||
|
||||
alertController.addAction(cancelAction)
|
||||
alertController.addAction(cameraAction) alertController.addAction(photoLibraryAction)
|
||||
|
||||
alertController.popoverPresentationController?.sourceView = sender as? UIView present(alertController, animated: true, completion: nil)
|
||||
|
||||
let alertController = UIAlertController(title: "Choose Image Source", message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||
let cameraAction = UIAlertAction(title: "Camera", style: .default, handler: { action in print("User selected Camera action") })
|
||||
let photoLibraryAction = UIAlertAction(title: "Photo Library", style: .default, handler: { action in print("User selected Photo Library action") })
|
||||
|
||||
alertController.addAction(cancelAction)
|
||||
alertController.addAction(cameraAction) alertController.addAction(photoLibraryAction) alertController.popoverPresentationController?.sourceView = sender as? UIView present(alertController, animated: true, completion: nil)
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 请求相机权限
|
||||
|
||||
iOS对相机以及其他各种权限的设置较为复杂,目前通用有两种手段获取相机权限:第一种通过 `ImagePickerController` 来实现,智能识别部分用到的就是这个,但是设置起来十分繁琐。不过好在还有第二种,通过 `Info` 中设置相关参数,直接访问即可,在 `Info.plist` 文件中添加:
|
||||
|
||||
```swift
|
||||
Privacy - Photo Library Additions Usage Description
|
||||
```
|
||||
|
||||
赋值 `value`:
|
||||
|
||||
```swift
|
||||
To share photos from camera or photo library
|
||||
```
|
||||
|
||||
相机请求:
|
||||
|
||||
```swift
|
||||
@IBAction func photosButtonTapped(_ sender: UIButton) {...}
|
||||
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
|
||||
if let selectedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
|
||||
imageView.image = selectedImage
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
61
IOS/Task01:基础插件与功能/8. 代码启动与页面image.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 代码启动与登录界面
|
||||
|
||||
## 代码启动
|
||||
|
||||
在大型工程文件中,我们常常要避免使用storyboard,因为大量的storyboard会造成卡顿,内存占用过多。下面我们将介绍如何使用代码启动我们的app。
|
||||
|
||||
**Step 1:打开工程文件,在左侧导航栏中删除storyboard**
|
||||
|
||||

|
||||
|
||||
**Step 2:**在`Info.plist`中删除`Main storyboard file base name`一行
|
||||
|
||||

|
||||
|
||||
**Step 3:** 在左侧导航栏----`Info.plist`----`Application Scene Manifest`----`Scene Configuration`----`Application Session role`----`Item -(Default Configuration)`----`Storyboard Name`右侧Main中,删除该行
|
||||
|
||||

|
||||
|
||||
**Step 4:在左侧SceneDelegate.swift中,添加如下代码:**
|
||||
|
||||
```swift
|
||||
guard let windowScene = (scene as? UIWindowScene) else { return }
|
||||
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
|
||||
window?.windowScene = windowScene
|
||||
window?.rootViewController = ViewController()
|
||||
window?.makeKeyAndVisible()
|
||||
```
|
||||
|
||||

|
||||
|
||||
即可完成!
|
||||
|
||||
## 设置app的Icon
|
||||
|
||||
你可以将喜欢的图片用作自己app的Icon,这是一个在线免费转换尺寸的网址:
|
||||
|
||||
https://icon.wuruihong.com
|
||||
|
||||

|
||||
|
||||
Xcode的Assets.xcassets中罗列了该应用可能与用到的所有尺寸的Icon,你可以将上述网站下载得到的文件夹拖拽至Appicon处,系统会自动进行填充。
|
||||
|
||||
## 设置启动页面
|
||||
|
||||
打开各种app后,映入眼帘的往往不是空白屏幕,而是伴有启动页面的等待加载部分,如何实现用自己的图片进行加载?下面我们就来一起设置!
|
||||
|
||||
iOS 中设置启动页有两种方式 Launch Image 和 LaunchScreen,本部分我们以Launch Image为例
|
||||
|
||||
**Step 1:**在工程 targets--Build Settings 搜索 Asset Catalog Launch Image Set Name 然后设置创建的启动页名字LaunchImage。
|
||||
|
||||

|
||||
|
||||
**Step 2:**再在Info.plist中删除 `Launch screen interface file base name`并添加 `Launch image`并设置 `LaunchImage`
|
||||
|
||||

|
||||
|
||||
**Step 3:**资源文件中添加LaunchImage放入不同尺寸的图片。
|
||||
|
||||
**Step 4:**点击运行
|
||||
|
||||
<img src="/Users/mac/Desktop/iOSdev/Task01:基础插件与功能/img/15.png" alt="15" style="zoom:33%;" />
|
||||
210
IOS/Task01:基础插件与功能/LaunchImage.launchimage/Contents.json
Normal file
@@ -0,0 +1,210 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-2688h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "12.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "3x",
|
||||
"subtype" : "2688h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-Landscape-2688h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "12.0",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "3x",
|
||||
"subtype" : "2688h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-1792h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "12.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x",
|
||||
"subtype" : "1792h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-Landscape-1792h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "12.0",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "2x",
|
||||
"subtype" : "1792h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-2436h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "11.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "3x",
|
||||
"subtype" : "2436h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-Landscape-2436h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "11.0",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "3x",
|
||||
"subtype" : "2436h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-736h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "8.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "3x",
|
||||
"subtype" : "736h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-Landscape-736h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "8.0",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "3x",
|
||||
"subtype" : "736h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-667h.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "8.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x",
|
||||
"subtype" : "667h"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default@2x~iphone.png",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x",
|
||||
"subtype" : "retina4"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-Portrait~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-Landscape~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-Portrait@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-Landscape@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"minimum-system-version" : "7.0",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default~iphone.png",
|
||||
"idiom" : "iphone",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "iphone",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"filename" : "Default-568h@2x~iphone.png",
|
||||
"idiom" : "iphone",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x",
|
||||
"subtype" : "retina4"
|
||||
},
|
||||
{
|
||||
"extent" : "to-status-bar",
|
||||
"filename" : "Default-No-StatusBar~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "ipad",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"extent" : "to-status-bar",
|
||||
"filename" : "Default-Landscape-No-StatusBar~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "ipad",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"extent" : "to-status-bar",
|
||||
"filename" : "Default-No-StatusBar@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "ipad",
|
||||
"orientation" : "portrait",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "to-status-bar",
|
||||
"filename" : "Default-Landscape-No-StatusBar@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"extent" : "full-screen",
|
||||
"idiom" : "ipad",
|
||||
"orientation" : "landscape",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
IOS/Task01:基础插件与功能/LaunchImage.launchimage/Default-1792h.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
IOS/Task01:基础插件与功能/LaunchImage.launchimage/Default-2436h.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
IOS/Task01:基础插件与功能/LaunchImage.launchimage/Default-2688h.png
Normal file
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 61 KiB |
BIN
IOS/Task01:基础插件与功能/LaunchImage.launchimage/Default-667h.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
IOS/Task01:基础插件与功能/LaunchImage.launchimage/Default-736h.png
Normal file
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 54 KiB |
BIN
IOS/Task01:基础插件与功能/LaunchImage.launchimage/Default@2x~iphone.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
IOS/Task01:基础插件与功能/LaunchImage.launchimage/Default~iphone.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
IOS/Task01:基础插件与功能/img/1.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
IOS/Task01:基础插件与功能/img/10.png
Normal file
|
After Width: | Height: | Size: 822 KiB |
BIN
IOS/Task01:基础插件与功能/img/11.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
IOS/Task01:基础插件与功能/img/12.png
Normal file
|
After Width: | Height: | Size: 454 KiB |
BIN
IOS/Task01:基础插件与功能/img/13.png
Normal file
|
After Width: | Height: | Size: 308 KiB |
BIN
IOS/Task01:基础插件与功能/img/14.png
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
IOS/Task01:基础插件与功能/img/15.png
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
IOS/Task01:基础插件与功能/img/2.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
IOS/Task01:基础插件与功能/img/21.png
Normal file
|
After Width: | Height: | Size: 371 KiB |
BIN
IOS/Task01:基础插件与功能/img/22.png
Normal file
|
After Width: | Height: | Size: 353 KiB |
BIN
IOS/Task01:基础插件与功能/img/23.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
IOS/Task01:基础插件与功能/img/24.png
Normal file
|
After Width: | Height: | Size: 523 KiB |
BIN
IOS/Task01:基础插件与功能/img/3.png
Normal file
|
After Width: | Height: | Size: 1008 KiB |
BIN
IOS/Task01:基础插件与功能/img/4.png
Normal file
|
After Width: | Height: | Size: 631 KiB |
BIN
IOS/Task01:基础插件与功能/img/5.png
Normal file
|
After Width: | Height: | Size: 235 KiB |
BIN
IOS/Task01:基础插件与功能/img/6.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
IOS/Task01:基础插件与功能/img/7.png
Normal file
|
After Width: | Height: | Size: 638 KiB |
BIN
IOS/Task01:基础插件与功能/img/8.png
Normal file
|
After Width: | Height: | Size: 311 KiB |
BIN
IOS/Task01:基础插件与功能/img/9.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
IOS/Task01:基础插件与功能/img/Collection View Controller.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
IOS/Task01:基础插件与功能/img/Collection.PNG
Normal file
|
After Width: | Height: | Size: 1016 KiB |
BIN
IOS/Task01:基础插件与功能/img/Launch 3.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
IOS/Task01:基础插件与功能/img/Navigation Controller.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
IOS/Task01:基础插件与功能/img/Page View Controller.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
IOS/Task01:基础插件与功能/img/Tab Bar Controller.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
IOS/Task01:基础插件与功能/img/Table View Controller.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
IOS/Task01:基础插件与功能/img/ViewController.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
IOS/Task01:基础插件与功能/img/add Navigation Controller.png
Normal file
|
After Width: | Height: | Size: 949 KiB |
BIN
IOS/Task01:基础插件与功能/img/coding Setup3.png
Normal file
|
After Width: | Height: | Size: 522 KiB |
BIN
IOS/Task01:基础插件与功能/img/coding setup.png
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
IOS/Task01:基础插件与功能/img/coding setup2.png
Normal file
|
After Width: | Height: | Size: 417 KiB |
BIN
IOS/Task01:基础插件与功能/img/datasource.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
IOS/Task01:基础插件与功能/img/delegate ViewController.png
Normal file
|
After Width: | Height: | Size: 503 KiB |
BIN
IOS/Task01:基础插件与功能/img/delegate.png
Normal file
|
After Width: | Height: | Size: 434 KiB |
BIN
IOS/Task01:基础插件与功能/img/figure.png
Normal file
|
After Width: | Height: | Size: 477 KiB |
BIN
IOS/Task01:基础插件与功能/img/launch 2.png
Normal file
|
After Width: | Height: | Size: 674 KiB |
BIN
IOS/Task01:基础插件与功能/img/launch screen.png
Normal file
|
After Width: | Height: | Size: 482 KiB |
BIN
IOS/Task01:基础插件与功能/img/navigation bar.PNG
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
IOS/Task01:基础插件与功能/img/page1.PNG
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
IOS/Task01:基础插件与功能/img/page2.PNG
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
IOS/Task01:基础插件与功能/img/sc delegate.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
IOS/Task01:基础插件与功能/img/sc1.png
Normal file
|
After Width: | Height: | Size: 666 KiB |
BIN
IOS/Task01:基础插件与功能/img/scroll View.png
Normal file
|
After Width: | Height: | Size: 540 KiB |
BIN
IOS/Task01:基础插件与功能/img/setup.png
Normal file
|
After Width: | Height: | Size: 380 KiB |
BIN
IOS/Task01:基础插件与功能/img/tab bar item.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |