iOS

UIScrollView 停止滚动监测

今天需要做一个控件的动画,在scrollView滚动的时候隐藏底部一个控件,在scrollView停止滚动的时候显示底部那个控件。

该需求的“滚动隐藏”容易满足,只需要我们实现scrollView的代理:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView

但“停止滚动显示”这个需求,在我尝试了所有的scrollView代理后,仍然无法满足需求。苦思之下,想到了scrollView的监听滚动代理:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView

如果我们在该代理里添加一个延时定时器,在每次scrollView回调上面代理的时候,首先取消上次添加的定时任务,然后在添加一个新的定时任务,这样只要在上面的这个代理不在调用的时候,最后一个定时器,所携带的方法,就会被调用。然后就有了如下实践:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {]
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(scrollViewDidEndScrollingAnimation) object:nil];
    [self performSelector:@selector(scrollViewDidEndScrollingAnimation) withObject:nil afterDelay:0.1];
}

这样我们只要在:

- (void)scrollViewDidEndScrollingAnimation {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(scrollViewDidEndScrollingAnimation:) object:nil];
    // do something after end scrolling
}

实现停止滚动的代码就可以了。

自信

最近在网上看到一个关于自信的说法,看了后自我感觉还是写的比较好的。在这里与大家共勉

自律

  • 如果你连控制自己的言行、情绪、思想都做不到,你该怎么去相信自己呢?一切自信的前提,都是源于自律。

成就

  • 对自己的信心不是凭空就能建立的(无中生有那叫自大),你需要充足的事实基础去证明自己。获取成就的过程,会增加你的阅历、经验、能力,而获得成就的本身则是对你自己巨大的肯定与鼓舞。

健康

  • 身体健康强壮的人会更加的阳光自信,他们拥有更好的状态、精力,避免身体不适对他们的干扰与影响。同时运动是最好的抗抑郁方式。

学识

  • 增加自己的学识可以减少迷惑与忧虑,避免陷入自我怀疑;对技能的训练与学习会提高你的实力,增加对生活的掌控感。

认识自己

  • 你是谁

    • 你想要什么
    • 你想成为一个什么样的人
    • 你的原则是什么
    • 你擅长什么
  • 你不会相信一个你所不了解的人。
  • 认识自己是一个漫长、痛苦的过程,但它值得你这样去做。

iOS 通过URL打开APP

平常我们都看到通过APP调起另外一个APP,譬如像QQ分享等,那我们能否通过网页URL打开APP呢?当然能!下面我们就来看看如何通过网页URL打开APP。

网页链接调起APP
  • 首先我们在我们APP的Info.plist里新建一个URL Schemes.

当我们配置完上图所示的应用之后,我们就可以在浏览器里通过输入myApp://调起我们的APP了。

现在APP虽然调起了,但我们怎么知道是谁调起了我们的APP,而且你看我们在上图中还写了一个多余的参数。如果我们可以知道调起我们APP的链接就好了,这样我们就可以根据链接里的参数跳转到特定的APP页面了。需要实现这个功能,我们只需要在AppDelegate实现一个方法就行了(对于iOS10有不同的方法)。

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    //for iOS10
    NSLog(@"%@   ==   %@", url, options);
    return YES;
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    //for iOS9 or older version
    NSLog(@"%@   ==   %@", url, sourceApplication);
    return YES;
}

通过上面的方法,我们就可以知道是什么浏览器,通过什么链接来调起我们的APP了。

APP调起APP

我们在另外一个APP添加如下方法:

- (void)jump:(id)sender {
    NSString *urlStr = @"myApp://teamleader.cn";
    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:urlStr]]) {
        //可以调起APP
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
        NSLog(@"调起成功");
    } else {
        //无法调起APP,打开AppStore
        //itms-apps://itunes.apple.com/us/app/apple-store/idMY_APP_ID
        urlStr = @"itms-apps://itunes.apple.com/cn/app/apple-store/id391945719";
        //        urlStr = @"https://itunes.apple.com/us/app/apple-store/id391945719";
        //以上两种方式都是可以的
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
        NSLog(@"调起失败");
    }
}

这样我们就可以让其他的APP调起我们的APP了。并且在调起APP的时候传递了参数

Demo : iOS 通过URL打开APP

喜欢就给个star鼓励下吧。

iOS 汉字转拼音

iOS 汉字转拼音有好几种方式,以前都使用第三方开源库(PinYin4Objc)。最近项目中有需求也要进行汉字转拼音,于是就在网上搜索了下,看有没有更好的方式进行这个工作,如我所愿,系统也提供了一个库进行该转换。

CFStringTransform

iOS在CoreFoundation中提供了CFStringTransform函数,但在Foundation中却没有相对应的方法。它的定义如下:

Boolean CFStringTransform(CFMutableStringRef string, CFRange *range, CFStringRef transform, Boolean reverse);

其中string参数是要转换的string,比如要转换的中文,同时它是mutable的,因此也直接作为最终转换后的字符串。range是要转换的范围,同时输出转换后改变的范围,如果为NULL,视为全部转换。transform可以指定要进行什么样的转换,这里可以指定多种语言的拼写转换。reverse指定该转换是否必须是可逆向转换的。如果转换成功就返回true,否则返回false。

如果要进行汉字到拼音的转换,我们只需要将transform设定为kCFStringTransformMandarinLatin或者kCFStringTransformToLatin(kCFStringTransformToLatin也可适用于非汉字字符串):

CFMutableStringRef string = CFStringCreateMutableCopy(NULL, 0, CFSTR("中国"));
CFStringTransform(string, NULL, kCFStringTransformMandarinLatin, NO);
NSLog(@"%@", string);

这段代码将输出:

2016-9-22 14:41:14.644 Test[2436:907] zhōng guó

可以看出,CFStringTransform正确的输出了“中国”的拼音,而且还带上了音标。有时候我们不需要音标怎么办?还好CFStringTransform同时提供了将音标字母转换为普通字母的方法kCFStringTransformStripDiacritics。我们在上面的代码基础上再加上这个:

CFStringTransform(string, NULL, kCFStringTransformStripDiacritics, NO);
NSLog(@"%@", string);

那么最终将输出:

2016-9-22 14:47:00.380 Test[2470:907] zhong guo

iOS 数据持久化

所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案:

  1. Plist文件(属性列表)
  • Preference(偏好设置)
  • NSKeyedArchiver(归档)
  • SQLite 3
  • CoreData
1. Plist文件

Plist文件是将某些特定的类,通过XML文件的方式保存在目录中。

可以被序列化的类型只有如下几种:

NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;

其他类型需要转化为以上几种类型才能通过plist进行持久化。

使用方法:

  1. 可视化添加数据
    通过在XCode中进行添加数据
  2. 代码添加读取数据
//获得文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"fileName.plist"];

//存储数据
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];

//读取数据
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);

注意事项

  • 只有以上列出的类型才能使用plist文件存储。
  • 存储时使用writeToFile: atomically:方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。
  • 读取时使用arrayWithContentsOfFile:方法。
2. Preference

Preference该方法是通过系统提供的统一方法存储APP的一些配置信息,文件保存在Library/Preferences目录下。

使用方法:

//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);

注意事项

  • 偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。
  • 如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
  • 偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
3. NSKeyedArchiver

归档在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过它实现序列化。由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数类来说,归档相对而言还是比较容易实现的。

  1. 遵循NSCoding协议
    NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。
//1.遵循NSCoding协议 
@interface Person : NSObject   

//2.设置属性
@property (nonatomic, strong) UIImage *avatar;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end

@implementation Person
//解档
- (id)initWithCoder:(NSCoder *)aDecoder {
    if ([super init]) {
        self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.avatar forKey:@"avatar"];
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}
@end

注意事项

  • 如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;
  • 可以使用runtime来实现归档,可以使代码更加简洁、清晰,同时在自定义类的子类实现归档时,可以获取到父类的属性进行归档。

使用方法:

需要把对象归档是调用NSKeyedArchiver的工厂方法 archiveRootObject: toFile: 方法。

NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];

需要从文件中解档对象就调用NSKeyedUnarchiver的一个工厂方法 unarchiveObjectWithFile: 即可。

NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
    self.avatarView.image = person.avatar;
    self.nameField.text = person.name;
    self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}

注意事项

  • 必须遵循并实现NSCoding协议
  • 保存文件的扩展名可以任意指定
  • 继承时必须先调用父类的归档解档方法

iOS 读取文件四种方式

iOS 读取文件总结起来有以下四种方式:

iOS 读取文件内容步骤 :

  1. 获取文件位置
  2. 获取文件内容
//获取文件位置
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [paths lastObject];
filePath = [filePath stringByAppendingPathComponent:@"readFile.txt"];
NSLog(@"文件位置 : %@", filePath);

//1. 通过 NSString 直接读取数据
NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"%@", fileContent);

//2. 通过 NSData 读取数据
NSData *data = [NSData dataWithContentsOfFile:filePath];
fileContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", fileContent);

//3. 通过 NSFileManager 读取数据
NSFileManager *fm = [NSFileManager defaultManager];
data = [fm contentsAtPath:filePath];
fileContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", fileContent);

//4. 通过 NSFileHandle 读取数据
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:filePath];
data = [fh readDataToEndOfFile];
fileContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", fileContent);

总结 : 第一种方式直接读取数据最简单,其他方式都是需要转换为NSData,然后再通过NSString来获取文件内容

iOS 中延时执行的四种方法及其比较

在iOS开发中,有时会用到延时执行,下面就开发中会用到的四种延时执行的方法进行简单对比,以期后续可以更好的选择合适的延时方式。

需要延时执行的方法:

- (void)delayMethod {
    NSLog(@"execute : %@", [NSDate date]);
}
阻塞式延时

阻塞式延时会阻塞当前线程,为避免卡住用户界面,建议放在子线程执行

  • NSThread
NSLog(@"start : %@", [NSDate date]);
[NSThread sleepForTimeInterval:3.f];
//[NSThread exit];

[self delayMethod];
非阻塞式延时
  • performSelector
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:3];
NSLog(@"start : %@", [NSDate date]);

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayMethod) object:nil];
  • NSTimer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];
NSLog(@"start : %@", [NSDate date]);

[timer invalidate];
  • dispatch_after
NSLog(@"start : %@", [NSDate date]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self delayMethod];
});

Demo : iOS 中延时执行的四种方法及其比较

面向对象三大基本特性,五大基本原则

透切理解面向对象三大基本特性是理解面向对象五大基本原则基础.

三大特性是:封装,继承,多态

==所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。==封装是面向对象的特征之一,是对象和类概念的主要特性。 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

==所谓继承是指可以让某个类型的对象获得另一个类型的对象的属性的方它支持按级分类的概念。==继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;

==所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。==多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

五大基本原则

单一职责原则SRP(Single Responsibility Principle)
是指一个类的功能要单一,不能包罗万象。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。

开放封闭原则OCP(Open-Close Principle)
一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。比如:一个网络模块,原来只服务端功能,而现在要加入客户端功能,
那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码,这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。

替换原则(the Liskov Substitution Principle LSP)
子类应当可以替换父类并出现在父类能够出现的任何地方。比如:公司搞年度晚会,所有员工可以参加抽奖,那么不管是老员工还是新员工,
也不管是总部员工还是外派员工,都应当可以参加抽奖,否则这公司就不和谐了。

依赖原则(the Dependency Inversion Principle DIP) 具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,
这个时候,B不应当直接使用A中的具体类: 而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到
了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖。一个常见的问题就是编译A模块时需要直接包含到B模块的cpp文件,而编译B时同样要直接包含到A的cpp文件。

接口分离原则(the Interface Segregation Principle ISP)
模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来

转载自:面向对象三大基本特性,五大基本原则

KVO使用及注意点

KVO(Key-Value-Observer)是iOS中一个重要的概念,与协议(Protocol)、通知中心(NSNotification)、代码块(Block)等一起构起iOS的==通知体系==。
KVO主要提供对对象的属性变化的监听,包括对象属性的初始化、对象的修改,通过KVO我们可以获得对象属性修改的即时通知,以此来做一些程序的必要动作。

使用篇

KVO可以对当前对象的属性进行监听,同时也可以对其他对象的属性进行监听。
使用方法

//添加监听
[被监听对象 addObserver:监听者 forKeyPath:@"被监听对象属性" options:NSKeyValueObservingOptionNew context:nil];

//移除监听
[被监听者 removeObserver:监听者 forKeyPath:@"被监听对象属性" context:nil];

//获取监听事件改变
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    
}
注意点

KVO添加监听和移除监听必须成对出现,并且在被监听对象销毁前,需要移除被监听对象的监听事件,否则在被监听对象销毁的时候导致程序崩溃。崩溃提示信息:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance *** of class Observer was deallocated while key value observers were still registered with it.

Demo : KVO使用及注意点