17 Mar 2015
使用 NSUserDefaults
分享数据
首先我们要在创建的工程中打开应用和 WatchKit Extension 的 APP Groups
并且应用和 WatchKit Extension 要添加在同一个 group 下面。
然后我们创建一个数据对象,功能很简单 从 NSUserDefaults
中读取 String 数组 ,然后在对数组的添加删除操作后 更新 NSUserDefaults
对象
1 -(void)loadItems
2 _defaults =[[NSUserDefaults alloc] initWithSuiteName:@"group.com.test.DataSharing"];
3 _items = []_defaults objectForKey:@"Items"];
4 if (!_items)
5 _items =[NSMutableArray array];
6 }
7 }
8
9 -(void)appendWith:(NSString *)string
10 [_items addObject:string];
11 [self save];
12 }
13
14 -(void)removeItemAt:(NSInteger)index
15 [_items removeObjectAtIndex:index];
16 [self save];
17 }
18
19 -(void)save
20 [_defaults setObject:_items forKey:@"Items"];
21 [self.delege dataModelDidChanged];
22 }
在 WatchKit Extension 中 我们就可以使用同一个数据对象来更新 Watch app 的UI
1 -(void)loadTable
2 [self.tableView setNumberOfRows:[_dataModel.items count] withRowType:@"ItemCell"];
3 [_dataModel.items enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop)
4 WKRowItem *rowItem = [self.tableView rowControllerAtIndex:idx];
5 [rowItem.label setText:obj];
6 }];
7 }
Watch App 唤醒应用
因为我们在 Watch App 中也可以输入文本。所以在手表上更新数据后 我们还以通知 应用来使用新数据刷新UI。在 WKInterfaceController
有提供API 给我们
1 +(BOOL)openParentApplication:(NSDictionary *)userInfo
2 reply:(void(^)(NSDictionary *replyInfo, NSError *error)) reply;
1 [self presentTextInputControllerWithSuggestions:@[@"suggestion",@"Test",@"Input"]
2 allowedInputMode:WKTextInputModeAllowAnimatedEmoji
3 completion:^(NSArray *results)
4 __strong typeof(weakSelf) strongSelf = weakSelf;
5 if (strongSelf)
6 [_dataModel appendWith:results.firstObject];
7 [strongSelf loadTable];
8 [WKInterfaceController openParentApplication:@{@"update":@"update"}
9 reply:^(NSDictionary *replyInfo, NSError *error)}];
10 }
11 }];

Demo
15 Mar 2015
Apple Watch 中的文本输入
在 Apple Watch 上文本输入的交互主要是通過 Emoji 和 听写 两种方式。在现有的Watch Kit中苹果提供了一个现成的 API 来实现这个功能
1 -(void)presentTextInputControllerWithSuggestions:(NSArray *)suggestions
2 allowedInputMode:(WKTextInputMode)inputMode
3 completion:(void (^)(NSArray *results))completion
我们可以通过 suggestions
来指定一些默认的输入文本内容。
WKTextInputMode
定义了三种输入模式
WKTextInputModePlain
只能输入建议的默认文本和听写的内用
WKTextInputModeAllowEmoji
允许建议的文本 听写 和 没有动画的 Emoji
WKTextInputModeAllowAnimatedEmoji
建议的文本 听写 没有动画的 Emoji 和 带动画的 Emoji。
这个方法是异步的,调用后会在下一个 runloop
开始执行。当用户输入完成或者通过 - (void)dismissTextInputController
取消输入后, 会通过completion
回调回来,通过results
数组我们就可以得到用户的输入内容,有可能是 NSString
或者 Emoji 。带动画的Emoji 可能是以 NSData
的形式返回给我们,我们需要自己在 Watch extension 中把他绘制成相应的 UIImage
对象来使用。
API的相关文档
1 -(IBAction)textInputButtonPressed {
2 [self presentTextInputControllerWithSuggestions:@[@"suggestion",@"Test",@"Input"]
3 allowedInputMode:WKTextInputModeAllowAnimatedEmoji
4 completion:^(NSArray *results) {
5 NSLog(@"results %@",results);
6 }];
7 }


目前在模拟器上 听写和Emoji 还无法正常的使用,等到真机在测试吧。
24 Jan 2015
函数 ()->()
Swift 中的闭包和 Objective-C 中的 block 类似,闭包是功能性自包含模块,可以在代码中被传递和使用。 在 Swift 中 函数只是闭包的一种特殊形式。
1.全局函数是一个有名字但不会捕获任何值的闭包
2.内嵌函数是一个有名字可以捕获到所在的函数域内值的闭包
3.闭包表达式是一个没有名字的可以捕获上下文中的变量或者常量的闭包
定义函数
我们在 Swift 中使用 func 关键字来定义函数,函数可以没有参数和返回值,也可以有一个或者多个参数和返回值。在Swift中多个参数的返回值叫做元组(tuples)
func foo(firstname:String ,lastname:String)->(fullname:String)
return("(firstname) (lastname)")
}
函数调用
let fullname = foo("zhao”,”sunny”)
函数类型
每一个函数都有它的类型,函数的类型由函数本身的参数类型和返回值类型来决定的。
func sum(x:Int,y:Int) -> (result:Int)return x + Y
上面这个函数的函数类型就是
函数类型在构建函数的时候当成参数类型或者返回值类型来使用。
返回函数类型
func foo()->((String,String) -> String)
func bar(firstname:String,lastname:String) ->(fullname:String)
return("(firstname) (lastname)")
}
return bar
}
let namecombination = foo()
namecombination("wang","er")
可变参数函数
可变参数函数是指函数可以接收不固定个参数。在参数类型后面添加 … 来标记这个参数为可变参数。我们可以在函数中像访问数组一样访问可变参数。
func foo(names:String...) ->()
for name in names
println("(name)")
}
}
func foo("zhao","zhang","wang")
In-out 参数函数
传入函数的参数值只能在函数域内改变。参数传递是值传递,也就是说我们在函数内部修改了一个参数的值,在函数结束后,函数外部访问到的参数值还是传入函数之前的值,它并没有随着函数内的修改而改变。在Objective-C 我们通过指针来保存函数内修改过的值以供函数结束后外部访问。常见的例子就是 NSError 的使用。
在Swift 中我们也可以做类似的操作。我们需要** inout** 关键字来标记要修改的参数。
func swapTwoInts(inout a: Int, inout b: Int)
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
闭包 { ()->() in }
定义一个闭包
闭包是一个使用花括号**** 包围起来,并且使用函数类型()->()来定义的代码模块。->符号分割了输入参数和返回值类型。在关键字 in 来区分闭包的头和闭包函数体。
(params) -> returnType in
statements
}
一个数组的map闭包的例子
let names = ["zhao","wang","Li"]
names.map(
(name:String) -> String in
"(name) has been map !"
})
已知参数类型的闭包
map的闭包是作为函数参数传入的,所以 Swift 是可以做类型推断的,这样的话我们就不需要在闭包中在描述闭包的函数类型,也就是我们可以省略 (String) -> (String) 部分,来简写闭包表达式
let names = ["zhao","wang","Li"]
names.map(
name in
"(name) has been map !"
})
如果闭包有返回值我们也可以简写省略掉return。
例如
let names = ["zhao","wang","Li"]
let orderNames = sort(names,s1,s2 in s1 > s2)
闭包参数名简写
Swift 为内联函数提供了参数名称简写功能,您可以直接通过 $0,$1,$2等名字来引用的闭包的参数的值。
let names = ["zhao","wang","Li"]
names.map("($0) has been map !")
sort(names,$0 > $1)
如果闭包是函数的最后一个参数,我们可以省略掉圆括号
names.map"($0) has been map !"
sort(names)$0 > $1
对于排序函数 sort() 来说作为参数的闭包表达式只进行了两个值的比较,比较是通过操作符 >来进行的。在Swift中可以进行操作符重载,我们会发现在String 中定义了>的函数实现,也就是 > 本身就是一个函数 ,它接收两个String 类型的参数 并返回一个Bool类型的值。这和 Sort 函数的第二个参数的类型是一致的。所以,我们还可以进一步简写 Sort()
闭包跟函数是引用类型
无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:
23 Jan 2015
iOS应用中实现图片滤镜的一个简单方法
最近做的一个项目要用到图片滤镜,我对于OpenGL
和图形图片处理完全是外行。当然,在iOS上做图片处理绕不开的两个框架GPUImage
和苹果自家的Core Image
。根据以前的项目经验 GPUImage
因为内存还有其他一些问题,一直高居崩溃排行榜的榜首。但是Core Image
在没有技术积累的情况下我又没有把握在一周之内写出一个框架可以叫产品和UI同学自由调整滤镜效果。偶然中发现了 GPUImage
中的一个滤镜 GPUImageLookupFilter
。简直是神器,对于码农的工作量来说 瞬间降到0,分分钟搞定,然后将效果调试的任务丢给产品和UI就可以了。
滤镜的原理
首先我们了解一下关于图片滤镜的基本知识,也就是应该怎么实现一个图片滤镜。
整个滤镜的计算过程一般可以拆解成几个步骤。
1. 在原图上做一对一的像素彩色转换。
对于RGB
的颜色通道调整都是独立的,不会相互影响。
由于这个方法可以表述成[0, 255] -> [0, 255]
的映射数组, 那么使用255个不同灰度的全灰图片很简单的就把instagram的图片滤镜功能了解清楚了.记下所有计算结果.
如果你只是想看到一个大概的结果, 这个解码过程可以这样用一张图简化
2. 暗脚风格的Mask。通常只需要设计师生成一张带暗角的透明PNG 然后和图片合成在一起。
合成的方式有很多种 [ Insight into Photoshop 7.0 Blending Modes ] 这片文章中有详细的介绍。所有的合成方式GPUImage
基本上都实现了。
3. 还有就是材质和相框,使用方法和给图片添加暗脚是一样的。
GPUImageLookupFilter
的使用
GPUImageLookupFilter
通过一个颜色映射表来实现颜色转换。这种查表法的原理就是在一张表中为每种颜色纪录一个对应的映射目标的颜色。当用查表法对一张照片做颜色映射时,只需要遍历照片的每个像素点,然后在表中找到该像素颜色对应的目标颜色,最后将该像素设置为目标颜色即可。查表法实现的前提是颜色的映射与周围的颜色无关,即一种颜色无论周围的颜色为何、无论其位于照片的哪个位置,其目标颜色都应该是相同的。
RGB
的颜色数量表示为255*255*2555 = 16777216
如果要记录每种颜色的映射结果,那么需要一千六百多万条记录,这显然无法应用到实际的工程中。为了简化起见,一般每相近的 4 种颜色采用一条记录存储,这样颜色表只需要 64*64*64 = 262144
条记录。
这是一张基准颜色表。设计师或产品经理在PS
中调整好的效果可能是这样的一组Action

将这些调整应用到基准颜色表上就得到了一张映射表。
上表将 262144 种颜色分为 8 个块,每块64*64
格,每一格的颜色都不同。进行颜色映射时,首先使用数字图像处理软件对该基准颜色表应用要模拟的滤镜来生成映射表(如下图),然后对要处理的照片的每个像素,从基准颜色表中找到该像素颜色的位置,然后在映射表的相应位置就可以得到目的颜色。
暗角的添加
暗角我没有使用PNG素材而是使用了GPUImage
中的另外一个滤镜 GPUImageVignetteFilter
Demo Code
1 -(UIImage *)lookkupImageFilterWithFilterName:(NSString *)filterName originImage:(UIImage *)originImage{
2
3 GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:originImage];
4 GPUImagePicture *lookupImageSource = [[GPUImagePicture alloc] initWithImage:[UIImage imageNamed:filterName]];
5 GPUImageLookupFilter *lookupFilter = [[GPUImageLookupFilter alloc] init];
6
7 [stillImageSource addTarget:lookupFilter];
8 [lookupImageSource addTarget:lookupFilter];
9 if (self.hasVignetteEffect) {
10 [lookupFilter useNextFrameForImageCapture];
11 GPUImageVignetteFilter *vignettefilter = [[GPUImageVignetteFilter alloc] init];
12 vignettefilter.vignetteEnd = kAlohaImageVignetteEnd;
13 vignettefilter.vignetteStart = kAlohaImageVignetteStart;
14
15 [lookupFilter addTarget:vignettefilter];
16 [stillImageSource processImage];
17 [lookupImageSource processImage];
18 [vignettefilter useNextFrameForImageCapture];
19 UIImage *filteredimage = [vignettefilter imageFromCurrentFramebuffer];
20 return filteredimage;
21 } else {
22 [stillImageSource processImage];
23 [lookupImageSource processImage];
24 [lookupFilter useNextFrameForImageCapture];
25 UIImage *filteredimage = [lookupFilter imageFromCurrentFramebuffer];
26 return filteredimage;
27 }
28 }
参考
Add Instagram-like effects to your iOS app.
Insight into Photoshop 7.0 Blending Modes
Instagram 是用什么语言编写的?为什么它的图片滤镜效果那么出众?
GPUImage
30 Mar 2014
下面是实现一个带燥点的旧胶片效果的处理流程

- 首先用 CISepiaTone 生成单色旧胶片的图像
- 然后生成一张白色燥点的遮罩图片
- 然后生成一张黑色划痕燥点的遮罩图片,来体现胶片磨损的效果
- 最后把三张图片合成在一起
首先来单色照片
CIFilter *sepiaToneFilter = [CIFilter filterWithName:@"CISepiaTone"];
[sepiaToneFilter setValue:myImage forKey:kCIInputImageKey];

生成白色燥点遮罩然后跟单色的照片合成在一起
CIFilter *randomFilter = [CIFilter filterWithName:@"CIRandomGenerator"];
CIFilter *colorFilter = [CIFilter filterWithName:@"CIColorMatrix"];
[colorFilter setValue:randomFilter.outputImage forKey:kCIInputImageKey];
CIVector *vector = [CIVector vectorWithX:0 Y:1 Z:0 W:0];
[colorFilter setValue:vector forKey:@"inputRVector"];
[colorFilter setValue:vector forKey:@"inputGVector"];
[colorFilter setValue:vector forKey:@"inputBVector"];
[colorFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:0.1] forKey:@"inputAVector"];
CIFilter *compositingFilter = [CIFilter filterWithName:@"CIAdditionCompositing"];
[compositingFilter setValue:colorFilter.outputImage forKey:kCIInputImageKey];
[compositingFilter setValue:sepiaToneFilter.outputImage forKey:kCIInputBackgroundImageKey];

生成黑色划痕遮罩 然后在合成在一起
CIImage *maskImage = [randomFilter.outputImage imageByApplyingTransform:CGAffineTransformMakeScale(10, 45)];
CIFilter *colorFilter2 = [CIFilter filterWithName:@"CIColorMatrix"];
[colorFilter2 setValue:maskImage forKey:kCIInputImageKey];
CIVector *vector2 = [CIVector vectorWithX:0 Y:0 Z:0 W:0];
[colorFilter2 setValue:[CIVector vectorWithX:6 Y:0 Z:0 W:0] forKey:@"inputRVector"];
[colorFilter2 setValue:vector2 forKey:@"inputGVector"];
[colorFilter2 setValue:vector2 forKey:@"inputBVector"];
[colorFilter2 setValue:vector2 forKey:@"inputAVector"];
[colorFilter2 setValue:[CIVector vectorWithX:0 Y:1 Z:1 W:1] forKey:@"inputBiasVector"];
CIFilter *blackFilter = [CIFilter filterWithName:@"CIMinimumComponent"];
[blackFilter setValue:colorFilter2.outputImage forKey:kCIInputImageKey];
CIFilter *compositingFilter2 = [CIFilter filterWithName:@"CIMultiplyCompositing"];
[compositingFilter2 setValue:blackFilter.outputImage forKey:kCIInputImageKey];
[compositingFilter2 setValue:compositingFilter.outputImage forKey:kCIInputBackgroundImageKey];
