swift抛出异常_Swift 斯坦福课 笔记(五)

c564f081eb535c40cd9c3df3e872039b.png

前几节课过于基础,所以没做笔记,“五”指第5节课

「written by Talaxy on 2020/2/18」


抛出异常

func save() throws

do {
    try context.save()
} catch let error {
    // ...
    throw error
}

如果你能够确定语句不会报错,可以:

try! context.save()

如果不确定,可以:

let x = try? context.save()

其中x是Int?类型。如果try语句报错,则返回nil。


Any & AnyObject

Any & AnyObject 通常是为了兼容OC中的API所使用。

因为swift是强类型语言,所以方法也可以推断为一个Any类型。

Any类型无法使用任何属性方法,所以平常中我们不要去使用Any。但我们会需要将一个Any类型转为一个具体的类型,可以使用 as? 语句:

let unknown: Any = ...
if let foo = unknown as? MyType {
    ...
    ...
}

大部分情况MyType会是目标类型的父类或者协议。但这种情况下,foo也会被转为MyType。所以foo中的MyType子类方法也会因此不可用。


其他一些有趣的类

NSObject:

NSObject在OC中是所有类的父类,但在swift中没有要求所有类必须是NSObject的子类。

NSNumber:

NSNumber是个class,所以是引用类型。这个类型用来存储数字类型:

Int, Double, Float, Bool, ...

可以如此构造及使用:

let n = NSNumber(35.5) or
let m: NSNumber = 35.5
let intified: Int = n.intValue
// also doubleValue, boolValue, ...

Date(and Calendar, DateFormatter, DateComponents):

在UI中会使用到Date,并且需要注意不同时区会用不同的时间系统。

Data:

Data是一包二进制数,在传输中经常用到。通过Data可以获知数据的格式,比如 jpg, png, json, ...


Views

我们会在MVC中使用view(V代表Views)。

一个view(UIView类型)代表一个矩形区域,用来绘制或者处理触控事件。

view是分层的(hierarchy)

一个view只能有一个父级view:

var superview: UIView?

但可以有许多子级view:

var subviews: [UIView]

并且subviews是根据view的添加顺序排序

在Xcode中,我们会手动编辑一个view,但其实我们可以用代码去添加一个view:

// 添加一个subview
func addSubview(_ view: UIView)
// 告诉superview把自己去除
func removeFromSuperview()

在顶层的view是Controller的属性:

var view: UIView

也是每个iOS应用的根view


创建一个UIView

如果是在代码中创建一个view,则使用:

init(frame: CGRect)

如果是在storyboard创建一个vieww,则使用:

init(coder: NSCoder)

如果你需要为一个view创建一个构造方法,你必须实现以上两个构造方法:

func setup() { ... }
override init(frame: CGRect) {
    super.init(frame: CGRect)
    setup()
}
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setup()
}

尽量避免去创建一个构造方法

创建一个构造方法的替代方法是awakeFromNib()。awakeFromNib是个函数,只有当storyboard有view时才会调用,并且每一个view都会调用一次,调用顺序不确定。
NIB是界面构造器文件的一个非常古老的名称。


坐标系统数据结构

CG: Core Graphics

CGFloat:

用来取代有关坐标系统的Double/Float数据(因此,绘制方法中一般不能用Double/Float传参)。可以通过Double/Float构造:

let cgf = CGFloat(12.34)

CGPoint:

含有两个CGFloat类型(x, y)的结构:

var point = CGPoint(x: 37.0, y: 55.2)
point.y -= 30

CGSize:

含有两个CGFloat类型(宽, 长)的结构:

var size = CGSize(100.0, height: 50.0)
size.width += 42.5
size.height += 75

CGRect:

含有一个CGPoint和一个CGSize的结构:

struct CGRect {
    var origin: CGPoint
    var size: CGSize
}
let rect = CGRect(origin: aCGPoint, size: aCGSize)
// 还有一些其他的构造方法

CGRect还有许多的便捷的属性和方法:

// 矩形中x的最小值
var minX: CGFloat
// 中点y值
var midY: CGFloat
// 自身是否和这个CGRect有相交
intersects(CGRect) -> Bool
// 取交集
intersect(CGRect)
// 这个CGPoint是否在自身中
contains(CGPoint) -> Bool
...

还有许多的方法在文档,需要去记忆


界面坐标系统

一个view中:
Origin是view的左上角,Units是点,而不是像素(像素是材质的)。
大部分情况下每个点有两个像素,可以通过属性:

var contentScaleFactor: CGFloat

来查询

view的边界(boundary):

var bounds: CGRect

一些UIView属性可以知道view在superview的位置:

var center: CGPoint
var frame: CGRect

(注意,这些数据都是对于superview的)

bounds和frame的(width,height)值不一定是相同的:
view是可以旋转的(或者缩放/转移),而frame是包含view的最小的标准矩形。所谓标准,是其边平行于x, y轴。


创造界面

大部分情况下,你的view在storyboard创建
Xcode的控件板中有一个通用的UIView供使用。但在这创建之后,你需要通过Identify Inspector去阐明其具体类型(通常是自己创建的一个类型)

如果是通过代码去创建了一个view,可以:

let newView = UIView(frame: meViewFrame) or
// 这个 newView.frame == CGRect.zero,CGRect.zero即四个属性皆为0
let newView = UIView()

举个例子:

// 假设view是根界面
let labelRect = CGRect(x: 20, y: 20, width:100, height: 50)
// UILabel是UIView的子类
let label = UILabel(frame:labelRect)
label.text = "Hello"
view.addSubView(label)

自定义的界面

当我们想自定义绘制方法或者特殊地去处理触控事件,就需要去自定义一个界面。

对于绘图,只需要继承UIView,然后覆盖draw(_:)方法:

override func draw(_ rect: CGRect)

你可以在rect的外部绘制,但一般不会得到系统优化。只有view的bounds才是绘图边界。

注意,千万不要去手动调用draw(_:) 如果你想让系统重绘,只需要如此来告诉系统:

setNeedsDisplay() or
setNeedsDisplay(_ rect: CGRect)

如何去实现draw方法?有两种方法:

  • 可以获得一个上下文并且告诉它怎么绘制...
  • 使用UIBezierPath类来创建一个绘图路径

Core Graphics构想(第一种方法)

  1. 你需要上下文来绘制。所谓上下文,就是绘制前的准备,一般是各种属性。全局函数UIGraphicsGetCurrentContext()提供一个上下文,你可以使用它
  2. 创建路径(线, 弧, ...)
  3. 设置绘图属性,比如colors, fonts, textures(材质), linewidths, linecaps(线端), ...
  4. 笔触(stroke)是绘制图形线边轮廓,填充(fill)是绘制图形内部填充

UIBezierPath(Bezier:贝塞尔)(第二种方法)

和第一种一样,但是通过一个UIBezierPath实例来获得所有的绘制要求。UIBezierPath自动在当前上下文绘制


定义一个路径

创建一个UIBezierPath:

let path = UIBezierPath()

移动,给路径加边/弧:

path.move(to: CGPoint(80, 50))
path.addLine(to: CGPoint(140, 150))
path.addLine(to: CGPoint(10, 150))

合上路径:

path.close()

设置绘图属性:

UIColor.green.setFill()
UIColor.red.setStroke()
path.linewidth = 3.0

填充,线条:

path.fill()
path.stroke()

可以用UIBezierPath画出一些普遍的图形:

// 圆角矩形
let roundRect = UIBezierPath(roundedRect: CGRect, cornerRadius: CGFloat)
// 椭圆形
let oval = UIBezierPath(ovalIn: CGRect)

添加图形边界:

// 调用方法后,将不再绘制超出填充范围外的图形
// Clip意为修剪
addClip()

访问目标点是不是在闭合路径中:

// 在调用该方法前必须调用close方法
func contains(_ point: CGPoint) -> Bool

更多的方法需要查阅文档


UIColor

我们用UIColor来设置颜色。UIColor设置了一些类属性来获得基础颜色:

let green = UIColor.green

或者我们自定一个颜色(RGB, HSB, 图案)
UIColor构造方法详见文档

UIView的背景颜色属性:

var backgroundColor: UIColor

颜色拥有透明度(Alpha):

let semitransparentYellow = UIColor.yellow.withAlphaComponent(0.5)

Alpha的定义域是[0.0, 1.0],从完全透明到完全不透明

如果你想在view中绘制透明图案,需要设置:

var isOpaque = false

因为默认情况下view是不透明的。同时,也可以设置透明度:

var alpha = CGFloat(0.5)

图层

在UIView底下有个CALayer绘图机制

CA: Core Animation

view的绘制其实是由Core Animation完成的

你可以访问你的view的图层:

var layer: CALayer

一般情况下我们不回去访问layer属性,但是CALayer能做些很特别的事:

// 通过设置拐角半径,使得背景是个圆角矩形
var cornerRadius: CGFloat
// 设置view的边界
var borderWidth: CGFloat
// 设置边界颜色,注意不是UIColor
// 因为CA机制在CG之上,但在UIKit工具包层之下
// 所以CA不能使用UIColor类型
var borderColor: CGColor?

界面透明度

如果各个view互相重叠/有透明度,那么会发生什么呢?

如果互相重叠,则按照superview的subviews列表迭代顺序从下往上显示(subviews是可以设置的,因此可以手动控制图层顺序)

你可以隐藏一个view而非删除通过设置:

var isHidden: Bool

一个隐藏的view将不会被绘制,以及接受触控事件


绘制文字

通常我们会用UILabel去显示文字。但有时候,我们会需要在view的draw方法显示文字:

// 通常我们还会为text设置属性
let text = NSAttributedString(string: "hello")
// 或者draw(in: CGRect)
text.draw(at: aCGPoint)

我们可以访问size属性来获得text所占空间:

let textSize: CGSize = text.size

通过NSRange来访问/设置NSAttributedString部分范围字符属性:

let pizzaJoint = "café pesto"
// 创建一个可变的NSAttributedString
var attrString = NSMutableAttributedString(string: pizzaJoint)
// 获得目标范围
let firstWordRange = pizzaJoint.startIndex..<pizzaJoint.indexOf(" ")!
// 用NSRange封装该范围
let nsrange = NSRange(firstWordRange, in pizzaJoint)
// 添加为nsrange范围下属性(线条颜色:橘色)
attrString.addAttribute(.strokeColor, value: UIColor.orange, range: nsrange)

字体

通常会在UIButton, UILabel等UI组件中设置字体

简单的方式去获得一个字体:

static func preferredFont(forTextStyle: UIFont.TextStyle) -> UIFont

其中,UIFont.TextStyle的静态属性有:

  • .headline
  • .body
  • .footnote

或者更高级的方式:

let font = UIFont(name: "Helvetiva", size: 36.0)

也可以使用UIFontDescripter类型

有的时候,人们会调节iOS系统中的字体大小。但在默认情况下,简单字体大小不会随系统设置而变化。可以通过这个方式来使得字体随系统设置变化:

let metrics = UIFontMetrics(forTextStyle: .body)
let fontToUse = metrics.scaledFont(for: font)

还有一些"系统字体",这些经常出现在按钮等控件上:

static func systemFont(ofSize: CGFloat) -> UIFont
static func boldSystemFont(ofSize: CGFloat) -> UIFont

但是不要将这种字体用在用户内容里。对于普通的用户内容,请使用上面提到的preferredFont


绘制图片

有个和UILabel等价的控件:UIImageView
但是,和字体一样,我们可能需要在draw方法中去绘制图片

我们可以先创建一个UIImage对象:

// 注意这里的构造方法返回一个可选类型
let image = UIImage(named: "foo")

你需要将 foo.jpg 文件放在工程中的Assets.xcassets文件夹中,并且Xcode可能会要求你对不同分辨率的屏幕适配不同的图片

或者可以通过文件系统去创建一个UIImage:

// 通过提供照片的文件路径
let image = UIImage(contentOfFile: pathString)
// 通过提供一个存了照片的Data值
let image = UIImage(data: aData)

还可以使用全局函数UIGraphicsBeginImageContext(CGSzie)来绘制自定义图片 (详细见文档)

一旦你获得了UIImage,你就可以直接在draw方法绘制:

let image: UIImage = ...
// 固定在某点
image.draw(at point: aCGPoint)
// 缩放到一个矩形里
image.draw(in rect: aCGRect)
// 平铺图片(反复重复图像来填充矩形)
image.drawAsPattern(in rect: aCGRect)

在边界(bounds)改变时重绘

在bounds变化的时候,系统默认不会去重绘view

有时候边界改变后会使得布局糟糕。幸运的是,有个属性可以控制布局:

var contentMode: UIViewContentMode

这个也可以在Xcode中设置

UIViewContentMode
如果不想缩放,仅仅需要将view放在...:

  • .left
  • .right
  • .top
  • .bottom
  • .topRight
  • .topLeft
  • .bottomRight
  • .bottomLeft
  • .center

如果想要缩放:

  • .scaleToFill
  • .scaleAspectFill
  • .scaleAspectFit

如果想要重新绘制:

  • .redraw

当bounds变化时,我们也需要考虑subviews的布局。
通常情况下,你会用Xcode的自动布局(Autolayout constraints)来处理subviews,但你也可以手动的处理布局,通过覆盖layoutSubviews方法:

override func layoutSubviews() {
    super.layoutSubviews()
    ...
    ...
}

当你的view的bounds值变化时,就会自动调用这个方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值