类型转换 Objective-C 和 Swift

在 iOS 开发使用 Swift 时, 经常会遇到需要调用Objective-C 类方法的情况,苹果已经将几乎所有的Objective-C的API都已经无缝的转接到了 Swift,同时一些特别的类型也做了桥接,桥接意味着可以随意进行转换。

1
2
3
4
5
6
7
8
Let aString = (aString as NSString).lenght
(anArray as NSArray).componentsJoinedByString(NSString)
/**
* Array 桥接到了 NSArray。对应的是AnyObject的数组。*
* Dictionary 桥接到了 NSDictionary 对应的是 [NSObject:AnyObject]
* Int, Float Double, Bool 都桥接到了 NSNumber (反过来不适用)
* 如果需要从 NSNumber 对象中获取某个类型需要用 doubleValue,intValue 等。
*/

利用这种括弧 + as的方法可以直接调用Objective-C的方法, 同时实现了自动的类型转换,比如上面的例子,lenghtNSString的方法, 我们可直接在一行代码中将String 转换成NSString并调用其方法。

String ArrayDictionary 都是结构体,而不是类,但他们仍然可以对应 AnyObject,因为它们桥接到的NS类版本都是类。
Dictionary 被桥接到了NSDictionary -> [NSObject:AnyObject]。虽然Key值对应的并不是桥接的NSString, 但NSString继承自NSObject .

typealias

我们可以使用 typealias 关键字为某个类型声明别名。比如想让某个类型有特别的意思。例如

1
typealias AudioSample = Int

之后在代码中使用Int类型的话就可以直接使用别名AudioSample了。

operation

++ 或 –放在前后的区别。

1
2
3
4
5
6
let prefix = ++start
// prefix = 9
// start = 9
let postfix = start++
// postfix = 9
// start = 10

Property List

Property List表面上可以看作是 AnyObject, 它们是一组只有创建者才知道如何解释的数据,其包括的数据类型和CoreData中支持的数据类型类似,同时可以用于泛型的数据结构
我们可以利用下列常用方法进行某些类型的存储:

1
2
3
4
5
6
7
8
9
10
11
/**
It can store/retrieve entire Property Lists by name (keys) …
*/
setObject(AnyObject, forKey: String)// the AnyObject must be a Property List
objectForKey(String) -> AnyObject?
arrayForKey(String) -> Array<AnyObject>? // returns nil if value is not set or not an array
/**
It can also store/retrieve little pieces of data …
*/
setDouble(Double, forKey: String)
doubleForKey(String) -> Double // not an optional, returns 0 if no such key

Property List其中的一个使用场景就是NSUserDefaults,通常用来存储一些用户设置之类的小数据,因为性能问题,切记不要用来存储图片类的大数据。下面是使用 NSUserDefault 常用方法。

1
2
3
4
5
6
使用类方法
let defaults = NSUserDefaults.standardUserDefaults()
读或写
let plist: AnyObject = defaults.objectForKey(String)
defaults.setObject(AnyObject, forKey: String)
// AnyObject 必须是 PropertyList

你在任何地方做的改变都会自动保存,而下面的方法会强制保存,比如在调试的时候直接在Xcode终止模拟器时,App是不会触发自动保存的,可以适时插入synchronize,进行强制保存。

1
2
if !defaults.synchronize() {
//处理错误,能做的不多。

CGRect

CGRect 在Swift 中是结构体,包括两个属性。

1
2
3
4
5
struct CGRect {
var origin: CGPoint
var size: CGSize
}
let rect = CGRect(origin: aCGPoint, size: aCGRize) // there are other inits as well

CGRect 提供了很多便利的方法,帮助我们快速设置范围

1
//TODO:

UIView

我们在开发过程经常需要自定义某个视图,比如需要绘制某些形状到屏幕上,或者希望通过与UIButton、Slider 不同的方式,让某个视图能够响应用户的某种触摸事件。这时候,就需要我们设计自己的UIView子类。

我们通过重写 UIView 的 drawRect()方法,来实现在屏幕上的自定义绘制。

1
override func drawRect(regionThatNeedsToBeDrawn: CGRect)

drawRect() 方法中, 我们既可以使用更接近底层的 C-Like APICore Graphics, 也可以使用面向对象的UIBezierPath类.

理所当然,这个方法会在视图初始化的时候即被调用,但当用户通过某个触摸事件更改了视图的某个属性,需要通知视图重新绘制时,不能直接调用子类的 drawRect方法,而是通过调用下面的方法来通知iOS某个视图需要重新绘制,系统会在合适的时间,调用drawRect

1
2
setNeedsDisplay()
setNeedsDisplayInRect(regionThatNeedsToBeRedrawn: CGRect)

在 drawRect 方法中进行绘制代码编写的时候,可参考下列顺序.

1
2
3
4
1. 你需要拿到绘制的上下文`context`, 利用 `UIGraphicsGetCurrentContext()方法可以获得.
2. 创建即将绘制的路径 Path, 通过线条和弧线或者类似的东西
3. 设置绘制时的`attributes`, 比如:颜色,字体,textures, linwidths, linecaps. 等.
4. 最后描边并且填充之前创建的路径 Path

上面就是绘制的基本步骤, 不仅仅是绘制图片,文字也是如此,在知道字体的情况下,iOS 知道如何获得一个完美的路径来绘制漂亮的字母,并将其填充。

示例:

1
2
let path = UIBezierPath()
//初始化之后就可以进行

如果需要绘制透明的颜色,需要将视图允许的属性设置成true,因为透明的系统资源
你可以利用 UIBezierPath 来画一些比较复杂的图形

1
2
3
4
//圆角矩形
let rondRect = UIBezierPath(roundedRect: aCGRect, cornerRadius: aCGFloat)
//椭圆
let oval = UIBezierPath(ovalInRect: aCGRect)

Clipping your drawing to a UIBezierPath’s path
你也可以剪切任意的 Path, 它意味着如果我设置好了,我有一个 Path, 我要剪切,如果我调用 addClip(), 那么在这之后的所有绘图操作只会影响到 Path 里面的部分.举个例子,比如你要在屏幕上画一张纸牌,纸牌有圆角的效果,所以你可以把纸牌画在一个大的矩形里面,然后剪切到一个圆角矩阵里,这样四个角就修圆了.

1
addClip()

Hit Detection
碰撞测试.判断某个坐标点是不是在 path 中.

1
2
3
func containsPoint(CGPoint) -> Bool
// returns whether the point is inside the path
The path must be closed. The winding rule can be set with userEvenOddFillRule property

#####Drawing Text
我们通常使用UIKit的UILabel将字符呈现在屏幕上,但你同样可以通过drawRect进行字符的绘制。
我们需要用到 NSAttributedString 来描述字符的字体,颜色,大小,等等属性。

1
2
3
4
let text = NSAttributedString(“hello”)
text.drawAtPoint(aCGPoint)
let textSize: CGSize = text.size
// how much space the string will take up 可以通过`text.size`拿到需要绘制字符的Size

同时,NSAttributedString 还拥有一个可变类型,当需要动态的更改字符属性的时候使用。

1
let mutableText = NSMutableAttributedString(“some string”)

注意 NSAttributedString 不是 String 或者 NSString,我们需要通过它的string or mutableString来获取它的字符串。

创建NSAttributedString之后,我们就可以为字符串添加属性了

1
2
3
4
5
func setAttributes(attributes: Dictionary, range: NSRange)
func addAttributes(attributes: Dictionary, range: NSRange)
/**
Warning! This is a pre-Swift API. NSRange is not a Range.And indexing into the string is using old-style indexing (not String.Index)
*/

这里可以展开讨论一下String.Index
字符的属性祖耀封装到字典中进行设置,我们常用到的属性包括:

1
2
3
4
5
6
NSForegroundColorAttributeName : UIColor
NSStrokeWidthAttributeName : CGFloat
NSFontAttributeName : UIFont
/**
更多的信息可以查看文档,在NSAttributedString(NSStringDrawing) 下。
*/

对于字符属性最重要的就是字体,自 iOS7 之后,苹果推出了动态调整全局字体大小的方案,如果需要让自己的App支持系统级的调整字体大小,我们就需要使用系统推荐的字体,我们可以通过下面的方法获取适合排版的字体样式。

1
2
3
4
5
6
7
8
9
10
/**
Get preferred font for a given text style (e.g. body, etc.) using this UIFont type method …
*/
class func preferredFontForTextStyle(UIFontTextStyle) -> UIFont
/**
Some of the styles (see UIFontDescriptor documentation for more) …
*/
UIFontTextStyle.Headline
UIFontTextStyle.Body
UIFontTextStyle.Footnote
1
2
3
4
5
6
7
8
9
/**
These appear usually on things like buttons
*/
class func systemFontOfSize(pointSize: CGFloat) -> UIFont
class func boldSystemFontOfSize(pointSize: CGFloat) -> UIFont
/**
Dont use these for your users content. Use preferred fonts for that.
你可以通过查看文档的UIFontUIFontDescriptor 了解更多信息,但用到的不会太多。
*/
Drawing Image

和字符一样,通常我们需要在屏幕上呈现图片的时候会使用UIImageView,但是,你可能希望绘制某张图片到一个固定的范围,下面我们来看看如何在drawRect中创建UIImage对象。

1
2
3
4
5
let image: UIImage? = UIImage(named: “foo”) // 这里的UIImage 是 optional
/**
添加 foo.jpg 到你项目中的 Images.xcassets
*/

你也可以将某个系统路径或者二进制数据作为UIImage的来源,图片的格式可以是:jpg,png,tiff等。

1
2
3
4
5
/**
But we haven’t talked about getting at files in the file system … anyway …)
*/
let image: UIImage? = UIImage(contentsOfFile: aString)
let image: UIImage? = UIImage(data: anNSData)

你还可以使用更底层的Core Graphics 创建图片,可从文档UIGraphicsBeginImageContext(CGSize) 中了解更详细的信息。

在创建UIImage之后,我们需要利用下面的方法进行屏幕中的定位和绘制。

1
2
3
4
5
6
let image: UIImage =
image.drawAtPoint(aCGPoint)//设置图片的位置(左上角)
image.drawInRect(aCGRect)//设置绘制的范围
image.drawAsPatternInRect(aCGRect)
// tiles the image into aCGRect

在绘制图片的时候,我们要考虑屏幕翻转带来的bounds变化,默认情况下,bounds改变后,View并不会重新绘制,其实UIView的contentMode属性可以帮助我们控制这种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var contentMode: UIViewContentMode
/**
contentMode 属性的关联值很多,我们可以将他们分成三个类别
*/
//1. 不调整比例,只设置位置
.Left
.Right
.Top
.Botto
.TopRight
.BottomRight
.BottomLeft
.Center
//2. 设置显示比例。 ScaleToFill是默认设置。
.ScaleToFill
.ScaleAspectFill
.ScaleAspectFit
//3. 重新调用 drawRect 方法
.Redraw

这些值我们不仅仅可以通过代码实现,在 Xcode 中的``也可以进行直接设置。

下面是重写drawRect绘制的Demo:FaceView
Creat a custom UIView subclass to draw a face with a specified amount of “smallness”

未完待续。