Statusbar状态栏样式

From: http://www.jianshu.com/p/ae47fdbf28fd

很多时候我们需要修改页面的Statusbar的样式,这里的样式是固定的,系统仅提供了两种:

typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
    UIStatusBarStyleDefault                                     = 0, // Dark content, for use on light backgrounds
    UIStatusBarStyleLightContent     NS_ENUM_AVAILABLE_IOS(7_0) = 1, // Light content, for use on dark backgrounds

    UIStatusBarStyleBlackTranslucent NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 1,
    UIStatusBarStyleBlackOpaque      NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 2,
} __TVOS_PROHIBITED;

这里是对UIStatusBarStyle的枚举,虽然有四个,但是后两个是在iOS7.0之后废弃的,使用的时候会有警告,所以,实际上只有两种

UIStatusBarStyleLightContent

UIStatusBarStyleDefault

下面,我们就来看一看,怎么在项目中根据我们的需要进行选择;
与状态栏相关的方法主要有以下几个:

- (UIStatusBarStyle)preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault
- (BOOL)prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO
// Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden.
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarAnimationFade

第一个是状态栏的样式,第二个是否隐藏状态栏,第三个是动画方式;

1. 不含有导航

如果工程中没有使用导航,我们直接在ViewController中重写上面的方法就可以修改状态栏的样式了:

- (UIStatusBarStyle)preferredStatusBarStyle {

    return UIStatusBarStyleLightContent;
}

这样可以根据我们的需求在不同的页面进行修改,隐藏,显示状态栏;
但是,当我们使用了导航后,好像没效果了?

2. 含有导航

如果使用了导航,我们再去重写上面的方法,就失去作用了,查了写资料,发现:

UINavigationController不会将 preferredStatusBarStyle方法调用转给它的子视图,而是由它自己管理状态,而且它也应该那样做.因为UINavigationController 包含了它自己的状态栏;
因此就算 UINavigationController中的viewController 实现了 preferredStatusBarStyle方法 也不会调用

那么,我们怎么在使用导航的时候修改status bar的状态呢?

它是基于它的 UINavigationBar.barStyle属性.默认(UIBarStyleDefault)的是黑色文本的状态栏 而 UIBarStyleBlack是设置为白色文本的状态栏;

也就是说,如果viewController是在导航中的,想要改变状态栏文本颜色,那么需要通过代码:

self.navigationController.navigationBar.barStyle = UIBarStyleBlack;//设置为白色
self.navigationController.navigationBar.barStyle = UIBarStyleDefault;//设置为黑色

很多时候,我们虽然使用了导航,但是导航条都是我们自定义的,这个时候,我们隐藏了系统的导航:

self.navigationController.navigationBarHidden = YES;//隐藏系统导航

这时,我们就可以在ViewController里重写preferredStatusBarStyle方法,来设置状态栏的样式了,方式同1;

PS:如果我们设置了不同页面的状态栏样式,但是在进入到相应页面时,状态栏并没有按我们的预期发生变化,可尝试调用下面的方式试试:

- (void)setNeedsStatusBarAppearanceUpdate

3. 设置全局的状态栏

如果我们想更改所有的状态栏为同样状态,可以在设置中进行修改:
在info.plist中添加如下字段:

<key>View controller-based status bar appearance<key>
<value>NO<value>

类型为Boolean,设置为NO;

设置为NO后,就不能使用代码控制状态栏的样式了;如果想用代码控制,就在这里设置为YES,当然,这样在下面的设置就无效了 (注:UIApplication.shared.statusBarStyle = UIStatusBarStyle.default 还是有效的)

然后在项目的额General–>Deployment Info–>Status Bar Style中选择需要的样式:

这样,项目中所有的状态栏就都变为白色的了;

4. 总结

上面的第三种方法虽然使用简单,但是不够灵活,不能使用代码在程序中动态调整状态栏的设置,如果,APP中状态栏统一,可以使用此方法,简单,高效;如果状态栏的设置需要动态调整,就只能在方法1,2中选择了!!!

个人总结:
全局
info 中加入

View controller-based status bar appearance

NO

然后在项目的额General–>Deployment Info–>Status Bar Style中选择需要的样式:

分文件 View controller-based status bar appearance

YES
General中配置无效
重写

1
2
3
4
5
UIViewController
/// 根据info 是否起效
override var preferredStatusBarStyle: UIStatusBarStyle{
return UIStatusBarStyle.lightContent
}

http://www.jianshu.com/p/ee1c9c91a477

1
2
3
如果将View controller-based status bar appearance设置为NO,不在UIApplication管理的情况下,所有控制器view上状态栏的只受启动图导航栏颜色设置的影响(保持一样),其它任何设置都不起作用。
如果将View controller-based status bar appearance设置为YES。所有控制器View上状态栏首先受navigationBar的barStyle影响,为UIBarStyleBlack时状态栏字体颜色为白色,UIBarStyleDefault时状态栏前景部分颜色为黑色。
只有将View controller-based status bar appearance设置为YES的情况下,才能单独修改某个控制器View的状态栏前景部分颜色,更改方法见34部分。

—- kvc 来使用 ——

1
2
3
4
5
6
7
- (UIView *) statusBar{
if (_statusBar == nil){
_statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
}
return _statusBar;
}
// 设置他的隐藏和显示

iOS 如何优雅地 hook 系统的 delegate 方法

From: http://www.daizi.me/2017/11/01/iOS%20%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E5%9C%B0%20hook%20%E7%B3%BB%E7%BB%9F%E7%9A%84%20delegate%20%E6%96%B9%E6%B3%95%EF%BC%9F/

在 iOS 开发中我们经常需要去 hook 系统方法,来满足一些特定的应用场景。

比如使用 Swizzling 来实现一些 AOP 的日志功能,比较常见的例子是 hook UIViewControllerviewDidLoad ,动态为其插入日志。

这当然是一个很经典的例子,能让开发者迅速了解这个知识点。不过正如现在的娱乐圈,diss 天 diss 地,如果我们也想 hook 天,hook 地,顺便 hook 一下系统的 delegate 方法,该怎么做呢?

所以就进入今天的主题:如何优雅地 hook 系统的 delegate 方法?

hook 系统类的实例方法

首先,我们回想一下 hook UIViewControllerviewDidLoad 方法,我们需要使用 category,为什么需要 category 呢?因为在 category 里面才能在不入侵源码的情况下,拿到实例方法 viewDidLoad ,并实现替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "UIViewController+swizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (swizzling)
+ (void)load {
Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));
method_exchangeImplementations(fromMethod, toMethod);
}
- (void)swizzlingViewDidLoad {
NSString *str = [NSString stringWithFormat:@"%@", self.class];
NSLog(@"日志打点 : %@", self.class);
[self swizzlingViewDidLoad];
}
@end

这个例子里面,有一个注意点,通常我们创建 ViewController 都是继承于 UIViewController,因此如果想要使用这个日志打点功能,在自定义 ViewController 里面需要调用 [super viewDidLoad]。所以一定需要明白,这个例子是替换 UIViewControllerviewDidLoad,而不是全部子类的 viewDidLoad

1
2
3
4
5
6
7
8
9
10
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
@end

hook webView 的 delegate 方法

这个需求最初是项目中需要统计所有 webView 相关的数据,因此需要 hook webView 的 delegate 方法,今天也是以此为例,主要是 hook UIWebViewWKWebView类似)。

首先,我们需要明白,调用 delegate 的对象,是继承了 UIWebViewDelegate 协议的对象,因此如果要 hook delegate 方法,我们先要找到这个对象。

因此我们需要 hook [UIWebView setDelegate:delegate] 方法,拿到 delegate 对象,才能动态地替换该方法。这里 swizzling 上场:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@implementation UIWebView(delegate)
+(void)load{
Method originalMethod = class_getInstanceMethod([UIWebView class], @selector(setDelegate:));
Method swizzledMethod = class_getInstanceMethod([UIWebView class], @selector(hook_setDelegate:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (void)dz_setDelegate:(id<UIWebViewDelegate>)delegate{
[self dz_setDelegate:delegate];
}
@end

这里有个局限性,源码中需要调用 setDelegate: 方法,这样才会调用 dz_setDelegate:

接下来就是重点了,我们需要根据两种情况去动态地 hook delegate 方法,以 hook webViewDidFinishLoad: 为例:

  • delegate 对象实现了 webViewDidFinishLoad: 方法。则交换实现。
  • delegate 对象未实现了 webViewDidFinishLoad: 方法。则动态添加该 delegate 方法。

下面是 category 实现的完整代码,实现了以上两种情况下都能正确统计页面加载完成的数据:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

static void dz_exchangeMethod(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel, SEL orginReplaceSel){

Method originalMethod = class_getInstanceMethod(originalClass, originalSel);

Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);

if (!originalMethod) {

Method orginReplaceMethod = class_getInstanceMethod(replacedClass, orginReplaceSel);

BOOL didAddOriginMethod = class_addMethod(originalClass, originalSel, method_getImplementation(orginReplaceMethod), method_getTypeEncoding(orginReplaceMethod));

if (didAddOriginMethod) {

NSLog(@”did Add Origin Replace Method”);

}

return;

}

BOOL didAddMethod = class_addMethod(originalClass, replacedSel, method_getImplementation(replacedMethod), method_getTypeEncoding(replacedMethod));

if (didAddMethod) {

NSLog(@”class_addMethod_success –> (%@)”, NSStringFromSelector(replacedSel));

Method newMethod = class_getInstanceMethod(originalClass, replacedSel);

method_exchangeImplementations(originalMethod, newMethod);

}else{

NSLog(@”Already hook class –> (%@)”,NSStringFromClass(originalClass));

}

}

@implementation UIWebView(delegate)

+(void)load{

Method originalMethod = class_getInstanceMethod([UIWebView class], @selector(setDelegate:));

Method swizzledMethod = class_getInstanceMethod([UIWebView class], @selector(dz_setDelegate:));

method_exchangeImplementations(originalMethod, swizzledMethod);

}

  • (void)dz_setDelegate:(id)delegate{

[self dz_setDelegate:delegate];

[self exchangeUIWebViewDelegateMethod:delegate];

}

#pragma mark - hook webView delegate 方法

  • (void)exchangeUIWebViewDelegateMethod:(id)delegate{

dz_exchangeMethod([delegate class], @selector(webViewDidFinishLoad:), [self class], @selector(replace_webViewDidFinishLoad:),@selector(oriReplace_webViewDidFinishLoad:));

}

  • (void)oriReplace_webViewDidFinishLoad:(UIWebView *)webView{

NSLog(@”统计加载完成数据”);

}

  • (void)replace_webViewDidFinishLoad:(UIWebView *)webView

{

NSLog(@”统计加载完成数据”);

[self replace_webViewDidFinishLoad:webView];

}

@end

与 hook 实例方法不相同的地方是,交换的两个类以及方法都不是 [self class],在实现过程中:

  1. 判断 delegate 对象的 delegate 方法(originalMethod)是否为空,为空则用 class_addMethod 为 delegate 对象添加方法名为 (webViewDidFinishLoad:) ,方法实现为(oriReplace_webViewDidFinishLoad:)的动态方法。

  2. 若已实现,则说明该 delegate 对象实现了 webViewDidFinishLoad: 方法,此时不能简单地交换 originalMethodreplacedMethod,因为 replaceMethod 是属于 UIWebView 的实例方法,没有实现 delegate 协议,无法在 hook 之后调用原来的 delegate 方法:[self replace_webViewDidFinishLoad:webView];

因此,我们也需要将 replace_webViewDidFinishLoad: 方法动态添加到 delegate 对象中,并使用添加后的方法和源方法交换。

结语

以上,通过动态添加方法并替换的方式,可以在不入侵源码的情况下,优雅地 hook 系统的 delegate 方法。通过合理使用 runtime 期间几个方法的特性,使得 hook 系统未实现的 delegate 方法成为可能。

最后献上:github 源码地址

Extensions存储属性

From: https://grayluo.github.io/WeiFocusIo/cocoa-swift/2015/12/21/swiftpracticeextensionmd

在OC中我们使用Category提高的代码的规范,减少了冗余,在swift中虽然不再有Category了,但是有了类似功能的Extension,相比于Category而言,Extension没有名称,而且不能定义存储属性(当然我们可以使用runtime解决,稍后会讲到)。

Extensions可以扩展的内容包括:

  • 添加计算型属性和计算静态属性
  • 定义实例方法和类型方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使一个已有类型符合某个接口
  • 属性与方法:
extension SomeType {
    // 加到SomeType的新功能写到这里
}
  • 协议:
extension SomeType: SomeProtocol, AnotherProctocol {
    // 协议实现写到这里
}


//CustomCell   
extension CustomCell{
    var defaulHeight:Float{
        return 50;
    }
}
//test
let cell = CustomCell(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
let defaultHeight = cell.defaulHeight
print("default height:\(defaultHeight)")

虽然我们前面已经讲到了,默认的extension不支持存储属性,我们来试一下:

//CustomCell   
extension CustomCell{
    var company:String?
}

编译器直接就提示: Extensions may not contain stored properties

但是有时候我们也会有这种需要在Extension中添加存储属性的时候,比如我们使用别人的第三方库时,那我们有没有其它办法可以实现在Extension中添加存储属性呢,当默认的方法都实现不了的时候,在iOS开发中不论是OC还是Swift,我们都可以考虑一下Runtime。

//CustomCell 
extension CustomCell{
    private struct AssociatedKeys{
        static var name = "CustomCell_name"
        static var location = "CustomCell_location"
    }
    var name:String?{
        get{
            return objc_getAssociatedObject(self, &AssociatedKeys.name) as? String
        }
        set{
            if let newValue = newValue{
                objc_setAssociatedObject(self, &AssociatedKeys.name, newValue as String, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
    var location:String?{
        get{
            return objc_getAssociatedObject(self, &AssociatedKeys.location) as? String
        }
        set{
            if let newValue = newValue{
                objc_setAssociatedObject(self, &AssociatedKeys.location, newValue as String, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
}

//test
let cell = CustomCell(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
cell.name = "Grey.Luo"
cell.location = "成都市高新区"
print("cell.name:\(cell.name),cell.location:\(cell.location)")

以上就是一个最基本的使用Runtime给Extension添加存储属性的示例,其中objc_getAssociatedObject与objc_setAssociatedObject中都会用到一个关联key,可以把其想作是KVC的那种思想,为了不对整个类和命名空间(即模块)导致污染,使用这种私有嵌套结构体的方式是业内大牛们推荐的方式,OK,我们再看一下runtime的文档:

 /** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
@available(iOS 3.1, *)
public func objc_setAssociatedObject(object: AnyObject!, _ key: UnsafePointer<Void>, _ value: AnyObject!, _ policy: objc_AssociationPolicy)

/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */
@available(iOS 3.1, *)
public func objc_getAssociatedObject(object: AnyObject!, _ key: UnsafePointer<Void>) -> AnyObject!


//CustomCell
extension CustomCell{
    convenience init(defaultHeight:Float){
        self.init()
        print("convenience init...\(defaultHeight)")
    }

//test
let cell = CustomCell(defaultHeight: 50)


extension CustomCell{    
    func normalFun(param1:String,param2:Int){
        print("normalFun,\(param1),\(param2)")
    }
}

PS: 一旦某个扩展方法中要修改实例本身,则需要在方法前添加mutating,而且仅支持结构体与枚举的扩展,不支持类和协议,其实就是数据类型与类在系统中的存储机制不一样。 如:

extension Int{
    mutating func doubleIt(){
        self = self * 2
    }
}

以下方法则不行:

class CustomOb: NSObject {
    var customName:String?
    var customLocation:String?
    init(name:String, location:String) {
        customName = name
        customLocation = location
     }
}
extension CustomOb{
    mutating func modifyMyself(defaultHeight:Float){
        self = CustomOb(name: "Grey.Luo", location: "成都高新")
    }
} 

以上这样对CustomOb进行扩展,无法使用mutating进行实例修改,直接报错:

‘mutating’ isn’t valid on methods in classes or class-bound protocols

如果对Subscripts不太熟悉的同学,可以查看[Cocoa-Swift之Subscripts][1]

   [1]: 


//CustomOb
class CustomOb: NSObject {
    var customName:String?
    var customLocation:String?
    init(name:String, location:String) {
        customName = name
        customLocation = location
     }
}
extension CustomOb{
    subscript(index:Int) -> String{
        var rs:String? = nil
        if(index == 0){
            rs = customName
        }else if(index == 1){
            rs = customLocation
        }
        return rs!
    }
}
//test
let customOb = CustomOb(name: "Grey.Luo", location:"成都高新")
print("\(customOb[0]),\(customOb[1])")

我们前面在讲存储属性的时候其实已经涉及到了,前面嵌套了一个结构体用于Runtime的关联key。我们再来一个枚举示例。

class CustomOb: NSObject {
    var customName:String?
    var customLocation:String?
    var customGroup:String?

    init(name:String , location:String , group:String?) {
        customName = name
        customLocation = location
        customGroup = group
    }

    enum Sex{
        case Secret,Man,Woman
    }
    var sex:Sex{
        switch(customGroup?.lowercaseString){
            case "ManGroup"?:
                return .Man
            case "WomanGroup"?:
                return .Woman
            default:
                return .Secret
        }
    }
}

//test
let customOb = CustomOb(name: "Grey.Luo", location:"成都高新" ,group: nil)
print("customOb.Sex:\(customOb.sex)")


protocol CustomObProtocol{
    func protocolFun(age:Int,company:String)->Bool
}
extension CustomOb:CustomObProtocol{
    func protocolFun(age: Int, company: String) -> Bool {
        print("age:\(age),company:\(company)")
        return true
    }
}

参考:
本文主要用于一个知识的归纳总结,过程中可能会引用到其它地方的文字或代码,如有侵权请及时联系我,在此对写作过程中参考了的文章作者表示感谢!

CocoaPods 升级

//最新源 https://gems.ruby-china.org
From: http://www.jianshu.com/p/935f156b7499

简述

当我们自己电脑上的CocoaPods版本过低,但是别的项目使用的CocoaPods版本过高的时候,你如果通过pod update获取Pods中的内容,就会提示下面的问题:

[!] The 项目 repo requires CocoaPods 1.0.0 -(currently using 0.39.0)

这就是提醒你项目用的CocoaPods的版本是1.0.0,你现在自己的版本是0.39.0。需要升级CocoaPods,可以通过pod –version查看你当前的CocoaPods版本号。

升级

依次使用下面的命令执行更新:(因为一些资源被墙了,步骤2,3是为了国内访问,4是让你确认2,3的操作是否正常执行。当然如果你已经安装了vpn可以访问国外的网站,2,3,4步骤省略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1.$ sudo gem update --system// 先更新gem,国内需要切换源
2.$ gem sources --removehttps://rubygems.org/
3.$ gem sources -ahttps://ruby.taobao.org
4./$ gem sources -l
\*\*\* CURRENT SOURCES \*\*\*https://ruby.taobao.org/
5.$ sudo gem install cocoapods// 安装cocoapods
6.$ pod setup
```
注: 转

更新
如果你不加版本号应该会给你安装最新版

$ sudo gem install cocoapods
我想要更新到1.1.1,所以我执行以下命令

$ sudo gem install cocoapods -v 1.1.1
如果你想尝试预览版,那就用以下命令

$ sudo gem install cocoapods –pre

问题
现实总是不让你开心,各种各样的问题都会出现,今天我在更新pod的时候就遇到了下面的问题。

$ sudo gem install cocoapods -v 1.1.1
Password:
Fetching: cocoapods-core-1.1.1.gem (100%)
Successfully installed cocoapods-core-1.1.1
Fetching: cocoapods-1.1.1.gem (100%)
ERROR: While executing gem … (Errno::EPERM)
Operation not permitted - /usr/bin/pod
从提示可以看出是因为没有权限在/usr/bin/这个目录写入,经过搜索发现原因是OS X 10.11的新安全机制:System Integrity Protection也叫作:rootless,这个默认的安全机制用于保护下面三个目录,所以无法在/usr/bin/中安装pod

/System
/sbin
/usr (with the exception of /usr/local subdirectory)
这个问题网上给出了两种解决方案,一个是关闭这个安全机制,另一个是绕过这个机制安装在别的位置。

关闭该机制

$ sudo nvram boot-args=”rootless=0”; sudo reboot
执行该命令后会重启电脑,保证设置生效

然后再进行正常的安装

$ sudo gem install cocoapods
绕开该机制

$ sudo gem install -n /usr/local/bin cocoapods
这样呢就把pod安装在了/usr/local/bin目录下

对于这两种方法,第二种会好一些,因为自己的可执行程序确实应该放在/usr/local/bin目录下,而且苹果既然有这项设置那肯定有他的道理,没有必要非要关闭这个设置,多个防护多点安全。

```

如果一切顺利,执行完上面的操作,你的CocoaPods就更新完了。但是有时候会碰到一下问题:

ERROR:While executing gem … (Errno::EPERM)

Operation not permitted - /usr/bin/xcodeproj

如果遇到此问题可以将上面步骤5中的命令改为下面的命令

sudo gem install -n /usr/local/bin cocoapods。

参考:http://www.cnblogs.com/brycezhang/p/3675670.html

http://stackoverflow.com/questions/30812777/cannot-install-cocoa-pods-after-uninstalling-results-in-error/30851030#30851030  

GPUImage 手动导入

From: http://www.jianshu.com/p/f7076dfd8b62

GPUImage是Brad Larson在github托管的开源项目。
GPUImage是一个基于GPU图像和视频处理的开源iOS框架,提供各种各样的图像处理滤镜,并且支持照相机和摄像机的实时滤镜; 基于GPU的图像加速,因此可以加速对实时摄像头视频、电影以及image的滤镜和其它效果处理,并且能够自定义图像滤镜。另外, GPUImage支持ARC。
使用GPUImage处理图片比Core Image更简单,只需要将过滤器赋给图片对象即可,不用考虑context或者设备等其他问题。GPUImage提供了除高斯模糊外的其他几种不同效果的模糊,虽然Core Image也提供了几种模糊效果,但目前在iOS上能用的就只有高斯模糊,而GPUImage可用的有FastBlur, GaussianBlur, GaussianSelectiveBlur 和 BoxBlur。此外,作为开源框架的GPUImage还支持自定义的过滤器。
github链接

😄刚开始准备研究这个开源的框架时就遇到了一个问题,如何将该框架导入到项目中使用……..折腾了一上午,先是看作者对框架的描述(全英文的…),按照里边的步骤一步步做,最终也还是没搞出来….后来goolge了半天,终于按照一篇文章的步骤将该框架顺利导入到工程了~
链接

下载GPUImage

下载下来之后注意下整个文件的内容

屏幕快照 2016-04-12 下午3.32.27.png

整个framework都是我们需要用的东西!

将下载好的文件拷贝到自己的工程里边

在自己工程目录下(最好是在根目录下)新建一个文件夹,我的文件夹名字叫GPUImage(后面将会用到这个路径),然后将整个 framework 文件夹复制粘贴到该文件夹下,这一步做好之后应该是这个个样子的

屏幕快照 2016-04-12 下午3.34.11.png

将GPUImage.xcodeproj拖到工程里边

拖拽的是你刚刚拷贝过来的那个GPUImage.xcodeproj

屏幕快照 2016-04-12 下午3.45.38.png

在自己项目的target依赖设置里面添加GPUImage.a作为Target Dependency

屏幕快照 2016-04-12 下午3.48.50.png

屏幕快照 2016-04-12 下午3.49.51.png

添加下面这些系统framework

CoreMedia
CoreVideo
OpenGLES
AVFoundation
QuartzCore

添加头文件路径

Build Settings -> Header Search Paths 添加GPUImage的路径
因为我之前是在项目的根目录下创建的GPUImage这个文件夹,framework 在GPUImage这个文件夹下,所以添加的路径为 GPUImage/framework 。!!注意,路径需要选择recursive!!

屏幕快照 2016-04-12 下午3.57.50.png

target-build setting里面,other linker flags 里面添加 -fobjc-arc -ObjC 这两项

ok!在 ViewController里边导入 GPUImage.h

屏幕快照 2016-04-12 下午3.58.52.png

编译通过了没????

ok 接下来要做的就是如何使用 GPUImage 这个框架了!

上边的步骤如果有不对或者不妥的地方,还请大神赐教,我只是一个小菜鸟~

iOS开发之源码解析 - MBProgressHUD

From: http://dev.dafan.info/detail/365641?p=60

iOS开发之源码解析 - MBProgressHUD

MBProgressHUD 是一个为 APP 添加 HUD 窗口的第三方框架,使用起来极其简单方便,关于 MBProgressHUD 的使用方法,GitHub 上有详细的说明,这里就不多加介绍了,本文主要是从源码的角度分析 MBProgressHUD的具体实现。

  • 先来对 MBProgressHUD 有个大体的认识,这是刚从 GitHub 上拉下来的代码,如下图,MBProgressHUD 的主要文件只有两个:

MBProgressHUD 源码解析


下面我们就开始分析 MBProgressHUD 的具体实现。在此之前先了解下文章的目录,本文主要有三个部分:

  1. MBProgressHUD 核心 API
    • 这一部分讲的主要是 MBProgressHUD 的一些属性方法等,主要是为了对 MBProgressHUD 先有个大概的认识
  2. show 系列方法
    • 这一部分主要是展示 HUD 窗口时调用的方法及代码分析
  3. hide 系列方法
    • 这一部分主要是隐藏 HUD 窗口时调用的方法及代码分析

begin~~~

一、MBProgressHUD 核心 API

这一部分讲的主要是 MBProgressHUD 的一些属性方法等,主要是为了对 MBProgressHUD 先有个大概的认识

1.1 模式

首先来看看 [MBProgressHUD][7] 中定义的枚举,mode 一共有六种显示样式:

[7]: https://github.com/jdg/MBProgressHUD

/// 显示样式
typedef NS_ENUM(NSInteger, MBProgressHUDMode) {
/// 默认模式, 系统自带的指示器
MBProgressHUDModeIndeterminate,
/// 圆形饼图
MBProgressHUDModeDeterminate,
/// 水平进度条
MBProgressHUDModeDeterminateHorizontalBar,
/// 圆环
MBProgressHUDModeAnnularDeterminate,
/// 自定义视图
MBProgressHUDModeCustomView,
/// 只显示文字
MBProgressHUDModeText
};

简单的效果图如下(颜色尺寸等都可以优化,我这里只是简单地示例):

默认模式:hud.mode = MBProgressHUDModeIndeterminate

圆形饼图:hud.mode = MBProgressHUDModeDeterminate

水平进度条:hud.mode = MBProgressHUDModeDeterminateHorizontalBar

圆环:hud.mode = MBProgressHUDModeAnnularDeterminate

自定义视图:hud.mode = MBProgressHUDModeCustomView

只显示文字:hud.mode = MBProgressHUDModeText

1.2 动画效果

MBProgressHUD 在显示 HUD 窗口的时候,一般都伴随着动画效果,MBProgressHUD 中的动画效果也是一个枚举,如下:

typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {
    ///  默认效果,只有透明度变化
    MBProgressHUDAnimationFade,
    /// 透明度变化 + 形变 (放大时出现缩小消失)
    MBProgressHUDAnimationZoom,
    /// 透明度变化 + 形变 (缩小)
    MBProgressHUDAnimationZoomOut,
    /// 透明度变化 + 形变 (放大)
    MBProgressHUDAnimationZoomIn
};

这里先简单的罗列出来,下文中还会多次用到。

1.3 MBProgressHUD 组成

MBProgressHUD 主要由四部分组成:loading 动画视图标题文本框详情文本框HUD 背景框,如下图。

MBProgressHUD 组成

之前用 MBProgressHUD 设置标题文本详情文本是通过几个属性来实现的,功能少也较为繁琐,因此被遗弃了;现在设置标题文本详情文本等十分简便,直接通过 label 等控件就可以实现,而且在功能上也有很大的扩展,详情请看下面这个代码块:

/// bezelView 是指包括文本和指示器的视图,和自定义的 customView 类似
@property (strong, nonatomic, readonly) MBBackgroundView *bezelView;
/// backgroundView 背景视图
@property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;
/// customView 自定义视图
@property (strong, nonatomic, nullable) UIView *customView;
/// label 指的是标题文本
@property (strong, nonatomic, readonly) UILabel *label;
/// detailsLabel指的是详情文本
@property (strong, nonatomic, readonly) UILabel *detailsLabel;
/// hud 窗口还可以加入button,添加事件
@property (strong, nonatomic, readonly) UIButton *button;

另外这里再附加两张 MBProgressHUD 的整体布局图,以便更好地认识 MBProgressHUD

MBProgressHUD 的整体布局图 1

MBProgressHUD 的整体布局图 2

简单介绍

  • backgroundView:整个背景图层,可以通过 MBBackgroundView 的 style 属性设置
  • bezelView:提供元素 (indicator、label、detailLabel、button)的背景
  • indicator:指示器显示进度情况 这个视图由我们设定的mode属性决定
  • label:显示标题文本
  • detailLabel:显示详情文本
  • button:添加点击事件

1.4 MBProgressHUD 中的属性

MBProgressHUD 文件中主要包括四个类,它们分别是 MBProgressHUDMBRoundProgressViewMBBarProgressViewMBBackgroundView。这四个类各有各的用法,比如如果是进度条模式(MBProgressHUDModeDeterminateHorizontalBar),则使用的是 MBBarProgressView 类;如果是饼图模式(MBProgressHUDModeDeterminate)或环形模式(MBProgressHUDModeAnnularDeterminate),则使用的是 MBRoundProgressView类。下面是这四个类的相关属性。

1.41 MBProgressHUD 相关属性
/// show 方法触发到显示 HUD 窗口的间隔时间,默认是 0
@property (assign, nonatomic) NSTimeInterval graceTime;

/// HUD 窗口显示的最短时间,默认是 0
@property (assign, nonatomic) NSTimeInterval minShowTime;

/// HUD 窗口显示模式, 默认是系统自带的指示器
@property (assign, nonatomic) MBProgressHUDMode mode;

/// 进度条指示器以及文本的颜色
@property (strong, nonatomic, nullable) UIColor *contentColor UI_APPEARANCE_SELECTOR;

/// HUD 窗口显示和隐藏的动画类型MBProgressHUD
@property (assign, nonatomic) MBProgressHUDAnimation animationType UI_APPEARANCE_SELECTOR;

/// HUD 窗口位置设置,比如 hud.offset = CGPointMake(0.f, MBProgressMaxOffset),可以移到底部中心位置
@property (assign, nonatomic) CGPoint offset UI_APPEARANCE_SELECTOR;

/// HUD 元素到 HUD 边缘的距离,默认是 20.f
@property (assign, nonatomic) CGFloat margin UI_APPEARANCE_SELECTOR;

/// HUD 窗口背景框的最小尺寸
@property (assign, nonatomic) CGSize minSize UI_APPEARANCE_SELECTOR;

/// 是否强制 HUD 背景框宽高相等
@property (assign, nonatomic, getter = isSquare) BOOL square UI_APPEARANCE_SELECTOR;

/// 进度条 (0.0 到 1.0)
@property (nonatomic, assign) float progress;

/// bezelView 是指包括文本和指示器的视图,和自定义的 customView 类似
@property (strong, nonatomic, readonly) MBBackgroundView *bezelView;

/// backgroundView 背景视图
@property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;

/// customView 自定义视图
@property (strong, nonatomic, nullable) UIView *customView;

/// label 指的是标题文本
@property (strong, nonatomic, readonly) UILabel *label;

/// detailsLabel指的是详情文本
@property (strong, nonatomic, readonly) UILabel *detailsLabel;

/// hud 窗口还可以加入button,添加事件
@property (strong, nonatomic, readonly) UIButton *button;
1.42 MBRoundProgressView 相关属性
/// 进度条 (0.0 到 1.0)
@property (nonatomic, assign) float progress;

/// 进度条颜色
@property (nonatomic, strong) UIColor *progressColor;

/// 进度条指示器的颜色
@property (nonatomic, strong) UIColor *progressTintColor;

/// 进度条指示器的背景颜色,只适用在 iOS7 以上,默认为半透明的白色 (透明度 0.1)
@property (nonatomic, strong) UIColor *backgroundTintColor;

/// 显示模式,NO = 圆形;YES = 环形。默认是 NO
@property (nonatomic, assign, getter = isAnnular) BOOL annular;
1.43 MBBarProgressView 相关属性
/// 进度条 (0.0 到 1.0)
@property (nonatomic, assign) float progress;

/// 进度条边界线的颜色,默认是白色
@property (nonatomic, strong) UIColor *lineColor;

/// 进度条背景色,默认是透明
@property (nonatomic, strong) UIColor *progressRemainingColor;

/// 进度条颜色
@property (nonatomic, strong) UIColor *progressColor;
1.44 MBBackgroundView 相关属性
/// 背景图层样式,有两种,iOS7 或者以上版本默认风格是MBProgressHUDBackgroundStyleBlur,其他为MBProgressHUDBackgroundStyleSolidColor,由于 iOS7 不支持 UIVisualEffectView,所以在 iOS7 和更高版本中会有所不同
@property (nonatomic) MBProgressHUDBackgroundStyle style;

/// 背景颜色,由于 iOS7 不支持 UIVisualEffectView,所以在 iOS7 和更高版本中会有所不同
@property (nonatomic, strong) UIColor *color;

1.5 MBProgressHUD 中的一些方法

1.51 类方法
/// 创建一个 HUD 窗口,并把它显示在 view 上,还可以设置是否有动画
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;

/// 找到最上层的 HUD subview 并把它隐藏,成功为YES、其他情况为 NO
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;

/// 返回最上层的 HUD subview
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;

这三个类方法中,常用的是第一个函数+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;直接创建 HUD,并把它显示在 view 上,用起来极其方便

1.52 对象方法
/// 以view为基准创建初始化一个HUD对象,为HUD的初始化构造函数
- (instancetype)initWithView:(UIView *)view;

/// 显示HUD控件,此函数应该在主线程中调用
- (void)showAnimated:(BOOL)animated;

/// 隐藏HUD控件,animated控制是否显示动画。对应于- (void)showAnimated:(BOOL)animated;
- (void)hideAnimated:(BOOL)animated;

/// 在delay时间之后隐藏HUD,animated控制显示动画与否,delay控制延迟时间
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;

这几个对象方法中,常用的也有两个- (void)hideAnimated:(BOOL)animated;- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;

二、show 系列方法

这一部分主要是展示 HUD 窗口时调用的方法及代码分析

下面这个方法在我们创建 MBProgressHUD 对象时首先调用

+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {

    /// 创建并初始化 MBProgressHUD 对象,根据传进来的 view 来设定
    MBProgressHUD *hud = [[self alloc] initWithView:view];

    /// 移除 HUD 窗口
    hud.removeFromSuperViewOnHide = YES;

    /// 添加到 View 上,并显示
    [view addSubview:hud];
    [hud showAnimated:animated];
    return hud;
}

这个方法会调用两个主要方法:- (id)initWithView:(UIView *)view- (void)showAnimated:(BOOL)animated,具体的调用流程如下图:

show 相关的方法调用

当然在 MBProgressHUD 中,+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated 调用的方法远不止上图列的这些,图上列的只是几个主要方法。接下来我们就根据程序的执行过程来一步一步分析一下代码。

在方法 - (id)initWithView:(UIView *)view中,调用 - (instancetype)initWithFrame:(CGRect)frame,接着会调用- (void)commonInit

- (id)initWithView:(UIView *)view {
    NSAssert(view, @"View must not be nil.");
    return [self initWithFrame:view.bounds];
}

- (instancetype)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        [self commonInit];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self commonInit];
    }
    return self;
}

- (void)commonInit {

    /// 默认效果, 透明度变化
    _animationType = MBProgressHUDAnimationFade;

    /// 默认模式, 系统自带的指示器
    _mode = MBProgressHUDModeIndeterminate;

    /// HUD 元素到 HUD 边缘的距离,默认是 20.f
    _margin = 20.0f;
    _opacity = 1.f;
    _defaultMotionEffectsEnabled = YES;

    // 默认颜色,根据当前的 iOS 版本
    BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;

    /// 进度条指示器以及文本的颜色
    _contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f];

    /// opaque 类似 Alpha,表示当前 UIView 的不透明度,设置是否之后对于 UIView 的显示并没有什么影响,官方文档的意思是 opaque 默认为 YES,如果 alpha 小于 1,那么应该设置 opaque 设置为 NO,当 alpha 为 1,opaque设置为 NO
    self.opaque = NO;

    /// 背景色
    self.backgroundColor = [UIColor clearColor];

    // 透明度为 0 
    self.alpha = 0.0f;
    /// 自动调整子控件与父控件之间的宽高
    self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.layer.allowsGroupOpacity = NO;

    /// 设置所需的子view
    [self setupViews];
    /// 设置指示器样式
    [self updateIndicators];
    /// 注册通知
    [self registerForNotifications];
}

上面代码块中的代码已经加过注释,因此在这里不再累述某句代码有什么作用,这里直接说程序的执行流程。当程序执行到 - (void)commonInit 这个方法时,会相继执行- (void)setupViews- (void)updateIndicators- (void)registerForNotifications 这三个方法,当然在执行这三个方法期间,也会执行其它的方法,比如会执行- (void)updateForBackgroundStyle- (void)updateBezelMotionEffects等等,这和你设置的 mode 的模式,以及和 label,detailsLabel ,button 这一系列元素,以及和相应的属性都有一定的关系。

接着我们来分析一下 - (void)setupViews- (void)updateIndicators- (void)registerForNotifications 这三个方法。

- (void)setupViews {

    /// 进度条指示器以及文本的颜色
    UIColor *defaultColor = self.contentColor;


    /// 创建背景视图
    MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];

    /// 背景图层样式
    backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
    backgroundView.backgroundColor = [UIColor clearColor];

    /// 自动调整 view 的宽度和高度
    backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    backgroundView.alpha = 0.f;
    [self addSubview:backgroundView];
    _backgroundView = backgroundView;

    /// 创建背景视图(和上面那个大小不同)
    MBBackgroundView *bezelView = [MBBackgroundView new];

    /// 代码层面使用 Autolayout,需要对使用的 View 的translatesAutoresizingMaskIntoConstraints 属性设置为NO
    bezelView.translatesAutoresizingMaskIntoConstraints = NO;
    bezelView.layer.cornerRadius = 5.f;
    bezelView.alpha = 0.f;
    [self addSubview:bezelView];
    _bezelView = bezelView;

    /// 调用 updateBezelMotionEffects 方法,设置视差效果
    [self updateBezelMotionEffects];


    /// 创建 label 标签,显示主要文本
    UILabel *label = [UILabel new];

    /// 取消文字大小自适应
    label.adjustsFontSizeToFitWidth = NO;
    label.textAlignment = NSTextAlignmentCenter;
    label.textColor = defaultColor;
    label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize];

    /// opaque 类似 Alpha,表示当前 UIView 的不透明度,设置是否之后对于 UIView 的显示并没有什么影响,官方文档的意思是 opaque 默认为 YES,如果 alpha 小于 1,那么应该设置 opaque 设置为 NO,当 alpha 为 1,opaque设置为 NO
    label.opaque = NO;
    label.backgroundColor = [UIColor clearColor];
    _label = label;


    /// 创建 detailsLabel 标签,显示详细信息

    UILabel *detailsLabel = [UILabel new];
    /// 取消文字大小自适应
    detailsLabel.adjustsFontSizeToFitWidth = NO;
    detailsLabel.textAlignment = NSTextAlignmentCenter;
    detailsLabel.textColor = defaultColor;
    detailsLabel.numberOfLines = 0;
    detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];

    /// opaque 类似 Alpha,表示当前 UIView 的不透明度,设置是否之后对于 UIView 的显示并没有什么影响,官方文档的意思是 opaque 默认为 YES,如果 alpha 小于 1,那么应该设置 opaque 设置为 NO,当 alpha 为 1,opaque设置为 NO
    detailsLabel.opaque = NO;
    detailsLabel.backgroundColor = [UIColor clearColor];
    _detailsLabel = detailsLabel;


    /// 创建 button 按钮,并添加响应按钮
    UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom];
    button.titleLabel.textAlignment = NSTextAlignmentCenter;
    button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
    [button setTitleColor:defaultColor forState:UIControlStateNormal];
    _button = button;


    /// 将 label,detailLabel,button 添加到蒙版视图
    for (UIView *view in @[label, detailsLabel, button]) {

        /// 代码层面使用 Autolayout,需要对使用的 View 的translatesAutoresizingMaskIntoConstraints 属性设置为NO
        view.translatesAutoresizingMaskIntoConstraints = NO;

        /// 为视图设置水平方向上优先级为 998 的压缩阻力
        [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];

        /// 为视图设置垂直方向上优先级为 998 的压缩阻力
        [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
        [bezelView addSubview:view];
    }


    /// 创建顶部视图
    UIView *topSpacer = [UIView new];

    /// 代码层面使用 Autolayout,需要对使用的 View 的translatesAutoresizingMaskIntoConstraints 属性设置为NO
    topSpacer.translatesAutoresizingMaskIntoConstraints = NO;
    topSpacer.hidden = YES;
    [bezelView addSubview:topSpacer];
    _topSpacer = topSpacer;

    /// 创建底部视图
    UIView *bottomSpacer = [UIView new];

        /// 代码层面使用 Autolayout,需要对使用的 View 的translatesAutoresizingMaskIntoConstraints 属性设置为NO
    bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO;
    bottomSpacer.hidden = YES;
    [bezelView addSubview:bottomSpacer];
    _bottomSpacer = bottomSpacer;
}

- (void)updateIndicators {
    UIView *indicator = self.indicator;

    /// 判断当前指示器是否是 UIActivityIndicatorView
    BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];

    /// 判断当前指示器是否是 MBRoundProgressView
    BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];

    MBProgressHUDMode mode = self.mode;
    /// MBProgressHUDModeIndeterminate:系统自带的指示器
    if (mode == MBProgressHUDModeIndeterminate) {
        if (!isActivityIndicator) {
             // 如果当前指示器不属于 UIActivityIndicatorView 类型,则移除之前的indicator,重新创建
            [indicator removeFromSuperview];
            indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
            [(UIActivityIndicatorView *)indicator startAnimating];
            [self.bezelView addSubview:indicator];
        }
    }
    else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
        /// 如果当前指示器不属于 MBBarProgressView 类型,则移除之前的indicator,重新创建
        [indicator removeFromSuperview];
        indicator = [[MBBarProgressView alloc] init];
        [self.bezelView addSubview:indicator];
    }
    else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
        if (!isRoundIndicator) {
            /// 如果当前指示器不属于 MBRoundProgressView 类型,则移除之前的indicator,重新创建
            [indicator removeFromSuperview];
            indicator = [[MBRoundProgressView alloc] init];
            [self.bezelView addSubview:indicator];
        }
        if (mode == MBProgressHUDModeAnnularDeterminate) { /// 圆环指示器
            [(MBRoundProgressView *)indicator setAnnular:YES];
        }
    } 
    else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) { /// 自定义视图指示器
        [indicator removeFromSuperview];
        indicator = self.customView;
        [self.bezelView addSubview:indicator];
    }
    else if (mode == MBProgressHUDModeText) { /// 文本形式,去除指示器视图
        [indicator removeFromSuperview];
        indicator = nil;
    }
        /// 代码层面使用 Autolayout,需要对使用的 View 的translatesAutoresizingMaskIntoConstraints 属性设置为NO
    indicator.translatesAutoresizingMaskIntoConstraints = NO;
    self.indicator = indicator;

    if ([indicator respondsToSelector:@selector(setProgress:)]) {
        /// 设置进度条的数值
        [(id)indicator setValue:@(self.progress) forKey:@"progress"];
    }


     /// 为视图设置水平方向上优先级为 998 的压缩阻力
    [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];

    /// 为视图设置垂直方向上优先级为 998 的压缩阻力
    [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];

    /// 设置控件颜色
    [self updateViewsForColor:self.contentColor];
    /// 更新布局
    [self setNeedsUpdateConstraints];
}

- (void)registerForNotifications {
#if !TARGET_OS_TV
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    /// 通过通知 UIApplicationDidChangeStatusBarOrientationNotification 来处理屏幕转屏事件
    [nc addObserver:self selector:@selector(statusBarOrientationDidChange:)
               name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
#endif
}
  • 由上面代码我们可以看出,在方法- (void)setupViews中,创建了 backgroundView、bezelView、label、detailsLabel、button 这几个控件,并使用 for 循环把 label、detailsLabel、button 添加到bezelView 视图中,最后还创建了顶部视图和底部视图,不过默认是隐藏的。有一点值得说明,在创建 button 时并没有设置 button 的 size 等属性,那么这个按钮是不会显示的。在这里 MBProgressHUD 重写了一个 Unbutton 的子类 MBProgressHUDRoundedButton。这个子类里面有一个方法,- (CGSize)intrinsicContentSize,通过这个方法来设置 Unbutton 的 size。

    • (CGSize)intrinsicContentSize {
      /// 只有当有事件才显示(这里也告诉我们,如果这个 button 没有任何事件的话,它的大小就是 CGSizeZero,即不会显示)
      if (self.allControlEvents == 0) return CGSizeZero;
      CGSize size = [super intrinsicContentSize];
      // Add some side padding
      size.width += 20.f;
      return size;
      }
  • - (void)updateIndicators这个方法主要是用来设置 indicator 指示器的,根据 mode 的属性显示不同的形式,具体可以参看代码注释。这个方法最后调用的是setNeedsUpdateConstraints函数,这个函数是系统自带的方法,它会自动调用- (void)updateConstraints 方法,- (void)updateConstraints 主要作用是更新各个控件的布局,我们稍后再对这个方法进行详细分析。

  • - (void)registerForNotifications这个方法中的代码量很少,它的作用是通过通知 UIApplicationDidChangeStatusBarOrientationNotification 来处理屏幕转屏事件

- (void)registerForNotifications这一系列方法执行完毕之后,程序会重新返回到+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated这个方法中,接着调用另一个主要函数- (void)showAnimated:(BOOL)animated

- (void)showAnimated:(BOOL)animated {

    /// 显示放在主线程中
    MBMainThreadAssert();

    /// 取消定时器
    [self.minShowTimer invalidate];
    self.useAnimation = animated;
    self.finished = NO;

    /// 如果设置了宽限时间graceTime,则延迟显示(避免 HUD 一闪而过的差体验)
    if (self.graceTime > 0.0) {

        /// 创建定时器,把它加入 NSRunLoop 中 
        NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        self.graceTimer = timer;
    } 

    /// 没有设置 graceTime,则直接显示
    else {
        [self showUsingAnimation:self.useAnimation];
    }
}

/// 设置宽限时间 graceTime时调用的方法
- (void)handleGraceTimer:(NSTimer *)theTimer {
    // Show the HUD only if the task is still running
    if (!self.hasFinished) {
        [self showUsingAnimation:self.useAnimation];
    }
}

- (void)showUsingAnimation:(BOOL)animated {
    /// 移除所有动画
    [self.bezelView.layer removeAllAnimations];
    [self.backgroundView.layer removeAllAnimations];

    /// 取消 hideDelayTimer
    [self.hideDelayTimer invalidate];

    /// 开始时间
    self.showStarted = [NSDate date];
    self.alpha = 1.f;

    /// 以防我们隐藏 NSProgress 对象
    [self setNSProgressDisplayLinkEnabled:YES];

    if (animated) {
        [self animateIn:YES withType:self.animationType completion:NULL];
    } else {
        /// 方法弃用告警
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        self.bezelView.alpha = self.opacity;
#pragma clang diagnostic pop
        self.backgroundView.alpha = 1.f;
    }
}

由上面这段代码我们可以看出,在方法- (void)showAnimated:(BOOL)animated中,无论我们有没有设置graceTime这个属性,最后都会去执行一个方法 - (void)showUsingAnimation:(BOOL)animated- (void)showUsingAnimation:(BOOL)animated 这个方法在上面已经做过注释,不再细说,不过有两小点值得我们注意,第一点是 - (void)showUsingAnimation:(BOOL)animated 在执行过程中调用了一个方法 - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled,先来看下这个方法

- (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {

    /// 这里使用 CADisplayLink,是因为如果使用 KVO 机制会非常消耗主线程(因为 NSProgress 频率非常快)
    if (enabled && self.progressObject) {
        /// 创建 CADisplayLink 对象
        if (!self.progressObjectDisplayLink) {
            self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
        }
    } else {
        self.progressObjectDisplayLink = nil;
    }
}

这个方法是关于 CADisplayLink 的,CADisplayLink 是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。我们在应用中创建一个新的 CADisplayLink 对象,把它添加到一个runloop 中,并给它提供一个 targetselector 在屏幕刷新的时候调用。一旦 CADisplayLink 以特定的模式注册到 runloop 之后,每当屏幕需要刷新,runloop 就会向 CADisplayLink 指定的target 发送一次指定的 selector 消息, CADisplayLink 类对应的 selector 就会被调用一次。

- (void)showUsingAnimation:(BOOL)animated 这个方法中还有一点值得注意,就是只有具有动画效果的前提下,即 animated 为真时才会调用 - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion 这个方法,下面我们再一起来看下这个方法。

/// animated 为真时调用,消失或出现时的伸缩效果,以及透明度
- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
    /// 自动确定正确的缩放动画类型,关于 MBProgressHUDAnimation 的几种类型,上文已全部列出,这里不再详细介绍
    if (type == MBProgressHUDAnimationZoom) {
        type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
    }

    /// CGAffineTransformMakeScale 中的两个参数,分别代表x和y方向缩放倍数
    CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
    CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);

    /// 设定初始状态
    UIView *bezelView = self.bezelView;
    if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) {
        bezelView.transform = small; /// 缩放
    } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) {
        bezelView.transform = large; /// 扩大
    }

    /// 创建动画任务
    dispatch_block_t animations = ^{
        if (animatingIn) {
            bezelView.transform = CGAffineTransformIdentity;
        } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) {
            bezelView.transform = large;
        } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) {
            bezelView.transform = small;
        }

    /// 方法弃用告警
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        bezelView.alpha = animatingIn ? self.opacity : 0.f;
#pragma clang diagnostic pop
        self.backgroundView.alpha = animatingIn ? 1.f : 0.f;
    };

    /// 动画的两种形式,>= iOS7 的是一种形式,iOS7以下是另一种
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
        /// 只支持 >= iOS7
        [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
        return;
    }
#endif
    [UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
}

- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion 这个方法,无论是处于 show 状态还是处于 hide 状态,都会调用,下边我们再一起看下 hide 系列的一些方法。

三、hide 系列方法

这一部分主要是隐藏 HUD 窗口时调用的方法及代码分析

关于隐藏 HUD 窗口,MBProgressHUD 给我们提供的方法有以下几个:

/// 找到最上层的 HUD subview 并把它隐藏,成功为YES、其他情况为 NO
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;

//隐藏HUD控件,animated控制是否显示动画。对应于- (void)showAnimated:(BOOL)animated;
- (void)hideAnimated:(BOOL)animated;

//在delay时间之后隐藏HUD,animated控制显示动画与否,delay控制延迟时间
- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay;

最常用的是后面两个: - (void)hideAnimated:(BOOL)animated- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay,这两个方法的本质是相同的,不同的只是形式,也就是说这两个方法的实现流程基本上是一致的,只不过 - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay 多执行一两个方法而已。下面我们就来具体分析下 hide 系列的方法。

  • 首先还是来说说+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated这个函数,如果调用这个方法来隐藏 HUD 窗口,那么会先调用两个方法:

    • (BOOL)hideHUDForView:(UIView )view animated:(BOOL)animated {
      /// 获取当前 view 的最上面的 HUD
      MBProgressHUD
      hud = [self HUDForView:view];
      if (hud != nil) {

      /// 移除 HUD 窗口
      hud.removeFromSuperViewOnHide = YES;
      [hud hideAnimated:animated];
      return YES;
      

      }
      return NO;
      }

    • (MBProgressHUD )HUDForView:(UIView )view {
      /// NSEnumerator 是一个枚举器,依附于集合类(NSArray,NSSet,NSDictionary等),reverseObjectEnumerator 倒序遍历
      NSEnumerator subviewsEnum = [view.subviews reverseObjectEnumerator];
      for (UIView
      subview in subviewsEnum) {

      if ([subview isKindOfClass:self]) {
          return (MBProgressHUD *)subview;
      }
      

      }
      return nil;
      }

当执行完上面这两个方法之后,接下来执行的方法和调用- (void)hideAnimated:(BOOL)animated隐藏 HUD 窗口时执行的方法相同,所以下边会详细分析。

  • 接下来说说调用- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay 隐藏 HUD 窗口时的情况,上文已经说过,调用这个方法会比调用- (void)hideAnimated:(BOOL)animated 多执行一两个方法:

    • (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay {
      /// 创建定时器,并把它加入到 NSRunLoop 中
      NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO];
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
      self.hideDelayTimer = timer;
      }

    • (void)handleHideTimer:(NSTimer *)timer {
      [self hideAnimated:[timer.userInfo boolValue]];
      }

由上面代码可以清晰的看出,- (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay 这个方法中加了一个定时器,当执行完这个定时器的selector时,就会执行- (void)hideAnimated:(BOOL)animated方法。

  • 由此可见无论使用哪种方法隐藏 HUD 窗口,最终都会来到这个方法,- (void)hideAnimated:(BOOL)animated,接下来我们就来分析下这个方法的具体调用流程,先看个图:

hide 相关的方法调用

上图显示的是 hide 相关的方法调用,只罗列了几个主要方法。接下来我们就来分析下这几个主要方法。先来到- (void)hideAnimated:(BOOL)animated方法中:

- (void)hideAnimated:(BOOL)animated {
    MBMainThreadAssert();
    [self.graceTimer invalidate];
    self.useAnimation = animated;
    self.finished = YES;

    /// 如果设置了最小显示时间,则执行此步骤,否则直接隐藏
    if (self.minShowTime > 0.0 && self.showStarted) {
        NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];

        /// 如果 minShowTime 比较大,则暂时不触发 HUD 的隐藏,而是启动一个 NSTimer
        if (interv < self.minShowTime) {
            /// 创建定时器,并把它加入到 NSRunLoop 中
            NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
            [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
            self.minShowTimer = timer;
            return;
        } 
    }
    /// 直接隐藏 HUD
    [self hideUsingAnimation:self.useAnimation];
}

- (void)handleMinShowTimer:(NSTimer *)theTimer {
    [self hideUsingAnimation:self.useAnimation];
}

从上面代码块中可以看出,无论我们有没有设置最小显示时间 self.minShowTime,都会触发 - (void)hideUsingAnimation:(BOOL)animated 这个方法,因此程序最后都会来到 - (void)hideUsingAnimation:(BOOL)animated 这个方法中:

- (void)hideUsingAnimation:(BOOL)animated {
    if (animated && self.showStarted) {
        /// 将 showStarted 设为 nil
        self.showStarted = nil;
        [self animateIn:NO withType:self.animationType completion:^(BOOL finished) {
            [self done];
        }];
    } else {
        self.showStarted = nil;
        self.bezelView.alpha = 0.f;
        self.backgroundView.alpha = 1.f;
        [self done];
    }
}

这个方法和 show 系列的 - (void)showUsingAnimation:(BOOL)animated 方法一样,只要设定 animated 的属性为 YES,最终都会走到 - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion 这个方法中,同时会执行一个方法:- (void)done,接下来我们来看一下这两个方法:

/// animated 为真时调用,消失或出现时的伸缩效果,以及透明度
- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
    /// 自动确定正确的缩放动画类型,关于 MBProgressHUDAnimation 的几种类型,上文已全部列出,这里不再详细介绍
    if (type == MBProgressHUDAnimationZoom) {
        type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
    }

    /// CGAffineTransformMakeScale 中的两个参数,分别代表x和y方向缩放倍数
    CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
    CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);

    /// 设定初始状态
    UIView *bezelView = self.bezelView;
    if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) {
        bezelView.transform = small; /// 缩放
    } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) {
        bezelView.transform = large; /// 扩大
    }

    /// 创建动画任务
    dispatch_block_t animations = ^{
        if (animatingIn) {
            bezelView.transform = CGAffineTransformIdentity;
        } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) {
            bezelView.transform = large;
        } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) {
            bezelView.transform = small;
        }

    /// 方法弃用告警
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        bezelView.alpha = animatingIn ? self.opacity : 0.f;
#pragma clang diagnostic pop
        self.backgroundView.alpha = animatingIn ? 1.f : 0.f;
    };

    /// 动画的两种形式,>= iOS7 的是一种形式,iOS7以下是另一种
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
        /// 只支持 >= iOS7
        [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
        return;
    }
#endif
    [UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
}

- (void)done {
    /// 取消 hideDelayTimer
    [self.hideDelayTimer invalidate];
    /// 隐藏 NSProgress 对象
    [self setNSProgressDisplayLinkEnabled:NO];

    if (self.hasFinished) {
        self.alpha = 0.0f;
        if (self.removeFromSuperViewOnHide) {
            /// 从父视图中移除
            [self removeFromSuperview];
        }
    }
    MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
    if (completionBlock) {
        completionBlock();
    }
    id<MBProgressHUDDelegate> delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
        [delegate performSelector:@selector(hudWasHidden:) withObject:self];
    }
}

关于 - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion 这个方法,只要 animated 的属性为 YES,都会调用;而在- (void)done 这个方法中,如果 removeFromSuperViewOnHide 属性为 YES,则将自己从父视图上移除;如果有 completionBlock 回调函数,则执行回调;如果实现了代理并实现了代理方法,则执行代理方法。而且我们还观察到在 hide 时,也会调用 - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled 方法,只是在 hide 时 enabled 为 NO。

- (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {

    /// 这里使用 CADisplayLink,是因为如果使用 KVO 机制会非常消耗主线程(因为 NSProgress 频率非常快)
    if (enabled && self.progressObject) {
        /// 创建 CADisplayLink 对象
        if (!self.progressObjectDisplayLink) {
            self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
        }
    } else {
        self.progressObjectDisplayLink = nil;
    }
}

over


以上便是对 MBProgressHUD 源码的一些总结和认识,如果有不足之处,还希望各位道友能多指点哈!


原文链接:http://www.jianshu.com/p/59121cac78bd

CocoaPods 升级指定版本or降级

From: http://blog.csdn.net/hsf_study/article/details/69945473

=====================升级版本===================

CocoaPods 1.1.0+ is required to build SnapKit 3.0.0+.

在swift3以后很多github框架需要在cocoapods1.1.0以后版本环境下才能正常使用,比如SnapKit .
我的cocoapods 依然是原始的 0.39.0 版本,俨然跟不上时代的进步.
$ pod –version
0.39.0

故记录升级cocoapods1.1.0 的历程.望对你有用.

第一步 升级 ruby - 升级至2.2.2以上

执行 ruby -v 命令查看当前ruby环境,如果在2.2.2以上自行忽略第一步.

$ ruby -v

1、 RVM安装

$ curl -L get.rvm.io | bash -s stable

###2、 之后就是等待一段时间之后,就可以安装成功了,使用以下命令来验证

$ source ~/.bashrc

$ source ~/.bash_profile

3、列出已知ruby的版本

rvm list known

这里写图片描述

我们可以选择安装2.2.4版本 执行命令->

4.安装ruby 2.2.4()

rvm install 2.2.4

等待安装…
这里写图片描述

如果安装失败(

解决方案:
1.brew update
2.brew install gmp

查看 http://www.jianshu.com/p/95c1ebbc403b)

5、安装完之后,可以ruby -v 测试一下 在这一步,需要按回车键,需要输入电脑密码,当然如果你没装xcode,需要先去装xcode.

ruby -v

测试安装完成
这里写图片描述

第二步:升级cocoapods1.2.0

1.先切换gem源 中间可能会要求输入电脑密码

1.1移除旧源:
如果之前是 https://rubygems.org/
gem sources --remove https://rubygems.org/

如果之前是 https://ruby.taobao.org/
gem sources --remove https://ruby.taobao.org/

1.2可以先执行一次系统更新操作
$ sudo gem update --system

1.3切换gem源 (2019-3-18更新)
gem source -a https://gems.ruby-china.com

切换 gem源 成功
这里写图片描述

2.升级cocoapods

下面命令,只选择一个使用。


sudo gem install -n /usr/local/bin cocoapods --pre //最新版本

或着 sudo gem install -n /usr/local/bin cocoapods -v (版本号) //指定安装 cocoapods的版本

这里写图片描述

安装成功! 测试一下

pod --version

会显示已安装的最新版本
这里写图片描述

其实这已经完成标题的需求了…
不过最后还是需要设置pod仓库

3.设置pod仓库

pod setup

根据网速等待不等时间..等待 ” Setup completed “

这里写图片描述

这就可以使用swift3.0以后的框架了.

pod install –no-repo-update

IOS可变参数

From: http://swifter.tips/variadic/

可变参数函数指的是可以接受任意多个参数的函数,我们最熟悉的可能就是 NSString-stringWithFormat: 方法了。在 Objective-C 中,我们使用这个方法生成字符串的写法是这样的:

NSString *name = @"Tom";
NSDate *date = [NSDate date];
NSString *string = [NSString stringWithFormat:
                @"Hello %@. Date: %@", name, date];

这个方法中的参数是可以任意变化的,参数的第一项是需要格式化的字符串,后面的参数都是向第一个参数中填空。在这里我们不再详细描述 Objective-C 中可变参数函数的写法 (毕竟这是一本 Swift 的书),但是我相信绝大多数即使有着几年 Objective-C 经验的读者,也很难在不查阅资料的前提下正确写出一个接受可变参数的函数。

但是这一切在 Swift 中得到了前所未有的简化。现在,写一个可变参数的函数只需要在声明参数时在类型后面加上 ... 就可以了。比如下面就声明了一个接受可变参数的 Int 累加函数:

func sum(input: Int...) -> Int {
    //...
}

输入的 input 在函数体内部将被作为数组 [Int] 来使用,让我们来完成上面的方法吧。当然你可以用传统的 for...in 做累加,但是这里我们选择了一种看起来更 Swift 的方式:

func sum(input: Int...) -> Int {
    return input.reduce(0, combine: +)
}

print(sum(1,2,3,4,5))
// 输出:15

Swift 的可变参数十分灵活,在其他很多语言中,因为编译器和语言自身语法特性的限制,在使用可变参数时往往可变参数只能作为方法中的最后一个参数来使用,而不能先声明一个可变参数,然后再声明其他参数。这是很容易理解的,因为编译器将不知道输入的参数应该从哪里截断。这个限制在 Swift 中是不存在的,因为我们会对方法的参数进行命名,所以我们可以随意地放置可变参数的位置,而不必拘泥于最后一个参数:

func myFunc(numbers: Int..., string: String) {
    numbers.forEach {
        for i in 0..<$0 {
            print("\(i + 1): \(string)")
        }
    }
}

myFunc(1, 2, 3, string: "hello")
// 输出:
// 1: hello
// 1: hello
// 2: hello
// 1: hello
// 2: hello
// 3: hello

限制自然是有的,比如在同一个方法中只能有一个参数是可变的,比如可变参数都必须是同一种类型的等。对于后一个限制,当我们想要同时传入多个类型的参数时就需要做一些变通。比如最开始提到的 -stringWithFormat: 方法。可变参数列表的第一个元素是等待格式化的字符串,在 Swift 中这会对应一个 String 类型,而剩下的参数应该可以是对应格式化标准的任意类型。一种解决方法是使用 Any 作为参数类型,然后对接收到的数组的首个元素进行特殊处理。不过因为 Swift 提供了使用下划线 _ 来作为参数的外部标签,来使调用时不再需要加上参数名字。我们可以利用这个特性,在声明方法时就指定第一个参数为一个字符串,然后跟一个匿名的参数列表,这样在写起来的时候就 “好像” 是所有参数都是在同一个参数列表中进行的处理,会好看很多。比如 Swift 的 NSString 格式化的声明就是这样处理的:

extension NSString {
    convenience init(format: NSString, _ args: CVarArgType...)
    //...
}

调用的时候就和在 Objective-C 时几乎一样了,非常方便:

let name = "Tom"
let date = NSDate()
let string = NSString(format: "Hello %@. Date: %@", name, date)

IOS 设备信息

From: http://www.jianshu.com/p/c3499d8e9a72

最近工作上需要获取设备的一些信息,整理了一下,方便大家。

1.获取电池电量(一般用百分数表示,大家自行处理就好)

-(CGFloat)getBatteryQuantity
{
    return [[UIDevice currentDevice] batteryLevel];
}

2.获取电池状态(UIDeviceBatteryState为枚举类型)

-(UIDeviceBatteryState)getBatteryStauts
{
    return [UIDevice currentDevice].batteryState;
}

3.获取总内存大小

-(long long)getTotalMemorySize
{
    return [NSProcessInfo processInfo].physicalMemory;
}

4.获取当前可用内存

-(long long)getAvailableMemorySize
{
    vm_statistics_data_t vmStats;
    mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT;
    kern_return_t kernReturn = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmStats, &infoCount);
    if (kernReturn != KERN_SUCCESS)
    {
        return NSNotFound;
    }
    return ((vm_page_size * vmStats.free_count + vm_page_size * vmStats.inactive_count));
}

5.获取已使用内存

- (double)getUsedMemory
{
  task_basic_info_data_t taskInfo;
  mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
  kern_return_t kernReturn = task_info(mach_task_self(), 
                                       TASK_BASIC_INFO, 
                                       (task_info_t)&taskInfo, 
                                       &infoCount);

  if (kernReturn != KERN_SUCCESS
      ) {
    return NSNotFound;
  }

  return taskInfo.resident_size;
}

6.获取总磁盘容量

include <sys/mount.h>
-(long long)getTotalDiskSize
{
    struct statfs buf;
    unsigned long long freeSpace = -1;
    if (statfs("/var", &buf) >= 0)
    {
        freeSpace = (unsigned long long)(buf.f_bsize * buf.f_blocks);
    }
    return freeSpace;
}

7.获取可用磁盘容量

-(long long)getAvailableDiskSize
{
    struct statfs buf;
    unsigned long long freeSpace = -1;
    if (statfs("/var", &buf) >= 0)
    {
        freeSpace = (unsigned long long)(buf.f_bsize * buf.f_bavail);
    }
    return freeSpace;
}

8.容量转换

-(NSString *)fileSizeToString:(unsigned long long)fileSize
{
    NSInteger KB = 1024;
    NSInteger MB = KB*KB;
    NSInteger GB = MB*KB;

    if (fileSize < 10)  {
        return @"0 B";
    }else if (fileSize < KB)    {
        return @"< 1 KB";
    }else if (fileSize < MB)    {
        return [NSString stringWithFormat:@"%.1f KB",((CGFloat)fileSize)/KB];
    }else if (fileSize < GB)    {
        return [NSString stringWithFormat:@"%.1f MB",((CGFloat)fileSize)/MB];
    }else   {
         return [NSString stringWithFormat:@"%.1f GB",((CGFloat)fileSize)/GB];
    }
}

8.型号

#import <sys/sysctl.h>

+ (NSString *)getCurrentDeviceModel:(UIViewController *)controller
{
    int mib[2];
    size_t len;
    char *machine;

    mib[0] = CTL_HW;
    mib[1] = HW_MACHINE;
    sysctl(mib, 2, NULL, &len, NULL, 0);
    machine = malloc(len);
    sysctl(mib, 2, machine, &len, NULL, 0);

    NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
    free(machine);

    if ([platform isEqualToString:@"iPhone3,1"]) return @"iPhone 4 (A1332)";
    if ([platform isEqualToString:@"iPhone3,2"]) return @"iPhone 4 (A1332)";
    if ([platform isEqualToString:@"iPhone3,3"]) return @"iPhone 4 (A1349)";
    if ([platform isEqualToString:@"iPhone4,1"]) return @"iPhone 4s (A1387/A1431)";
    if ([platform isEqualToString:@"iPhone5,1"]) return @"iPhone 5 (A1428)";
    if ([platform isEqualToString:@"iPhone5,2"]) return @"iPhone 5 (A1429/A1442)";
    if ([platform isEqualToString:@"iPhone5,3"]) return @"iPhone 5c (A1456/A1532)";
    if ([platform isEqualToString:@"iPhone5,4"]) return @"iPhone 5c (A1507/A1516/A1526/A1529)";
    if ([platform isEqualToString:@"iPhone6,1"]) return @"iPhone 5s (A1453/A1533)";
    if ([platform isEqualToString:@"iPhone6,2"]) return @"iPhone 5s (A1457/A1518/A1528/A1530)";
    if ([platform isEqualToString:@"iPhone7,1"]) return @"iPhone 6 Plus (A1522/A1524)";
    if ([platform isEqualToString:@"iPhone7,2"]) return @"iPhone 6 (A1549/A1586)";
    if ([platform isEqualToString:@"iPhone8,1"]) return @"iPhone 6s";
    if ([platform isEqualToString:@"iPhone8,2"]) return @"iPhone 6s Plus";
    if ([platform isEqualToString:@"iPod1,1"])   return @"iPod Touch 1G (A1213)";
    if ([platform isEqualToString:@"iPod2,1"])   return @"iPod Touch 2G (A1288)";
    if ([platform isEqualToString:@"iPod3,1"])   return @"iPod Touch 3G (A1318)";
    if ([platform isEqualToString:@"iPod4,1"])   return @"iPod Touch 4G (A1367)";
    if ([platform isEqualToString:@"iPod5,1"])   return @"iPod Touch 5G (A1421/A1509)";

    if ([platform isEqualToString:@"iPad1,1"])   return @"iPad 1G (A1219/A1337)";
    if ([platform isEqualToString:@"iPad2,1"])   return @"iPad 2 (A1395)";
    if ([platform isEqualToString:@"iPad2,2"])   return @"iPad 2 (A1396)";
    if ([platform isEqualToString:@"iPad2,3"])   return @"iPad 2 (A1397)";
    if ([platform isEqualToString:@"iPad2,4"])   return @"iPad 2 (A1395+New Chip)";
    if ([platform isEqualToString:@"iPad2,5"])   return @"iPad Mini 1G (A1432)";
    if ([platform isEqualToString:@"iPad2,6"])   return @"iPad Mini 1G (A1454)";
    if ([platform isEqualToString:@"iPad2,7"])   return @"iPad Mini 1G (A1455)";

    if ([platform isEqualToString:@"iPad3,1"])   return @"iPad 3 (A1416)";
    if ([platform isEqualToString:@"iPad3,2"])   return @"iPad 3 (A1403)";
    if ([platform isEqualToString:@"iPad3,3"])   return @"iPad 3 (A1430)";
    if ([platform isEqualToString:@"iPad3,4"])   return @"iPad 4 (A1458)";
    if ([platform isEqualToString:@"iPad3,5"])   return @"iPad 4 (A1459)";
    if ([platform isEqualToString:@"iPad3,6"])   return @"iPad 4 (A1460)";

    if ([platform isEqualToString:@"iPad4,1"])   return @"iPad Air (A1474)";
    if ([platform isEqualToString:@"iPad4,2"])   return @"iPad Air (A1475)";
    if ([platform isEqualToString:@"iPad4,3"])   return @"iPad Air (A1476)";
    if ([platform isEqualToString:@"iPad4,4"])   return @"iPad Mini 2G (A1489)";
    if ([platform isEqualToString:@"iPad4,5"])   return @"iPad Mini 2G (A1490)";
    if ([platform isEqualToString:@"iPad4,6"])   return @"iPad Mini 2G (A1491)";

    if ([platform isEqualToString:@"i386"])      return @"iPhone Simulator";
    if ([platform isEqualToString:@"x86_64"])    return @"iPhone Simulator";
    return platform;
}

可以根据自己的需求增改。有人说也可以按照手机屏幕来判断,但5和5s/5c等手机屏幕相同尺寸则无法判断。。。我只做了iPhone的机型,所以iPad和iPod Touch的信息并没有更新到最新,也请朋友们补充。

9.IP地址

#import <ifaddrs.h>#import <arpa/inet.h>

- (NSString *)deviceIPAdress {
    NSString *address = @"an error occurred when obtaining ip address";
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    int success = 0;

    success = getifaddrs(&interfaces);

    if (success == 0) { // 0 表示获取成功

        temp_addr = interfaces;
        while (temp_addr != NULL) {
            if( temp_addr->ifa_addr->sa_family == AF_INET) {
                // Check if interface is en0 which is the wifi connection on the iPhone
                if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
                    // Get NSString from C String
                    address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
                }
            }

            temp_addr = temp_addr->ifa_next;
        }
    }

    freeifaddrs(interfaces);
    return address;
}

10.当前手机连接的WIFI名称(SSID)

需要#import <SystemConfiguration/CaptiveNetwork.h>

- (NSString *)getWifiName
{
    NSString *wifiName = nil;

    CFArrayRef wifiInterfaces = CNCopySupportedInterfaces();
    if (!wifiInterfaces) {
        return nil;
    }

    NSArray *interfaces = (__bridge NSArray *)wifiInterfaces;

    for (NSString *interfaceName in interfaces) {
        CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName));

        if (dictRef) {
            NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef;

            wifiName = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];

            CFRelease(dictRef);
        }
    }

    CFRelease(wifiInterfaces);
    return wifiName;
}

11.当前手机系統版本

[[[UIDevice currentDevice] systemVersion] floatValue] ;

版权声明:本文为 Crazy Steven 原创出品,欢迎转载,转载时请注明出处!

xcode 项目更名

From: http://www.jianshu.com/p/2887d6fb5769

前言:

在iOS开发中,有时候想改一下项目的名字,这会遇到很多麻烦。

  • 直接改项目名的话,Xcode不会帮你改所有的名字
  • 项目中的很多文件、文件夹或者是项目设置的项,都是不能随便改的,有时候改着改着,就会编译不了。

所以各位重命名项目时,记得先备份好一份噢。本文我会介绍一种“完美”的修改方法。

注意:重命名项目时,记得先备份好一份
注意:重命名项目时,记得先备份好一份
注意:重命名项目时,记得先备份好一份

重要的事情说三遍

本文会把一个项目名叫 OldDemo123 改成 NewDemo

正文:

修改前的项目结构:

修改前的项目结构

1、打开项目,对项目名进行 Rename

1.1、选中项目名并按下回车,进入可编辑状态:

选中项目名字,进行编辑

1.2、输入新的项目名字,然后按回车,弹出改名前和改名后的文件对名,这时点击 Rename

点击 Rename

2、修改文件夹名字和显示包内容

2.1、打开应用所在文件夹,修改文件夹名字

注意:

  • 文件夹NewDemoTestsNewDemoUITests里面也要修改
  • 这里的NewDemoTests,原先为OldDemo123Tests
    我们改名字时需要注意,只需要把旧名字(OldDemo123)替换成新名字(NewDemo)即可,不要把其它字符(Tests)删除!

修成后的文件夹名字

2.2、选中 NewDemo.xcodeproj 右键打开 –> 显示包内容 –> 双击打开 project.pbxproj

显示包内容,双击打开 project.pbxproj

2.3、打开 project.pbxproj 文件之后,用搜索快捷键 command + f 全局搜索旧的项目名 OldDemo123 ,并用新的项目名 NewDemo 进行替换。替换完成后进行保存 command + s,然后关闭。

注意:要把所有的 OldDemo123 更换成 NewDemo

搜索 OldDemo123 ,并替换成 NewDemo

3、打开 NewDemo.xcodeproj 文件

注意:打开的是 NewDemo.xcodeproj 文件,而不是 NewDemo.xcworkspace文件。

3.1、此时会弹出提示框,点击 OK 就行。

弹出提示框

3.2、显示此时项目结构和修改更新Podfile文件

修改好项目结构

如果你的项目里面没有使用CocoaPods的话,项目应该可以运行成功了。

使用CocoaPods的话,项目虽然表面看起来已经修改成功了,但是运行之后发现提示错误:

使用CocoaPods的话,会提示的错误

此时打开项目文件夹,找到 Podfile 文件,双击打开,修改 target 后的项目名为最新的项目名 NewDemo

target 'NewDemo' do
pod 'AFNetworking', '~> 3.0'
end

然后在终端,用 cd 到项目目录下,运行 $ pod install,进行更新。

3.3、打开 NewDemo.xcworkspace 文件
此时文件显示错误:因为文件路径的原因

错误显示

选中显示红色的 OldDemo123 文件,点击右侧文件夹小图标,更改路径。

修改文件路径

路径更改成功之后,项目基本就可以运行成功了。

4、修改 Scheme

选中 OldDemo123 –> 下拉中选中 Manage Schemes –> 弹出一个显示框。

修改Scheme名

选中要修改的 OldDemo123 那一行,并按下回车,进行修改新的名称 NewDemo,然后点击 Close

修改新的 Scheme 名

5、项目内全局修改

其实到上面,项目已经基本修改完成了,但是对于一些处女座、强迫症患者来说,还有一些问题,如下:

生成类时的顶部介绍

全局搜索旧的项目名,并进行修改。

修改类的顶部介绍

最后:

到此,项目名已经完全修改完成了,小伙伴们可以尝试修改了。

下面是修改后的项目结构:

修改后的项目结构

//补充如果有oc的桥街头,手动更改下,项目文件地址可能需要修改下

注意:重命名项目时,记得先备份好一份