使用Masonry框架来构造iOS布局
之前一直都是在使用storyboards
来创建iOS布局,突然某一天看到使用代码布局后,界面元素的清晰易懂,就迷上了。。。
所以这次简单学习一下使用Masonry
帮助构建iOS
界面元素。
在阅读了Masonry
的github主页之后,学习并安装了框架。
框架的安装
安装框架非常简单,我们只需要在podfile
中加上下面一句:
pod 'Masonry'
之后为了语法的缩写以及代码自动补全我们来创建Code Snippets
mas_make -> [<#view#> mas_makeConstraints:^(MASConstraintMaker *make) { <#code#> }];
mas_update -> [<#view#> mas_updateConstraints:^(MASConstraintMaker *make) { <#code#> }];
mas_remake -> [<#view#> mas_remakeConstraints:^(MASConstraintMaker *make) { <#code#> }];
将上述语句放到~/Library/Developer/Xcode/UserData/CodeSnippets
中,之后我们在写相关代码的时候就会有代码提示了。
我们在要使用Masonry
的文件要频繁的导入"Masonry.h"
头文件,所以为了方便,我们创建一个Supporting Files
文件夹,并在其中创建一个prefix.pch
文件。文件内容为:
//
// MBMasonry-Prefix.pch
#import <Availability.h>
// Include any system framework and library headers here that should be included in all compilation units.
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
//define this constant if you want to use Masonry without the 'mas_' prefix
// 只要添加了这个宏,就不用带mas_前缀
#define MAS_SHORTHAND
//define this constant if you want to enable auto-boxing for default syntax
// 只要添加了这个宏,equalTo就等价于mas_equalTo
#define MAS_SHORTHAND_GLOBALS
//在这里导入头文件。
#import "Masonry.h"
#endif /* MBMasonry_Prefix_pch */
之后我们在使用的时候,就不用每个文件都导入一遍头文件了。
使用方法
原声的iOS代码,对界面布局使用的是NSLayoutAttribute
,用了Masonry
后,我们使用封装好的MASViewAttribute
。具体的属性等价关系如下图所示:
我们来举例说明一下,假入我们想要创建一个登陆界面。界面中要有Name
和Pass
两个TextField
,并对应两个Label
。
那么我们可以按照如下方式来写:
//
// ViewController.m
// FirstRAC
//
// Created by 梁中豪 on 2017/10/17.
// Copyright © 2017年 梁中豪. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic,strong) UIView *login;
@property(nonatomic,strong) UILabel *nameLabel;
@property(nonatomic,strong) UILabel *passLabel;
@property(nonatomic,strong) UITextField *name;
@property(nonatomic,strong) UITextField *pass;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.login = [[UIView alloc] initWithFrame:CGRectZero];
_name = [[UITextField alloc] init];
_name.borderStyle = UIFontWeightBold;
_name.font = [UIFont systemFontOfSize:15];
_name.placeholder = @"Enter Name";
[_login addSubview:_name];
_pass = [[UITextField alloc] init];
_pass.borderStyle = UIFontWeightBold;
_pass.font = [UIFont systemFontOfSize:15];
_pass.placeholder = @"Enter Pass";
[_login addSubview:_pass];
_nameLabel = [[UILabel alloc] init];
_nameLabel.backgroundColor = UIColor.whiteColor;
_nameLabel.font = [UIFont systemFontOfSize:14.0];
_nameLabel.lineBreakMode = NSLineBreakByTruncatingTail;
_nameLabel.text = @"Name";
[_login addSubview:_nameLabel];
_passLabel = [[UILabel alloc] init];
_passLabel.backgroundColor = UIColor.whiteColor;
_passLabel.font = [UIFont systemFontOfSize:14.0];
_passLabel.lineBreakMode = NSLineBreakByTruncatingTail;
_passLabel.text = @"Pass";
[_login addSubview:_passLabel];
[self.view addSubview:_login];
[self buildElem];
}
//为所创建的控件,创建约束
- (void)buildElem{
[_login mas_makeConstraints:^(MASConstraintMaker *make){
make.left.right.and.bottom.equalTo(self.view);
make.top.equalTo(self.mas_topLayoutGuide);
}];
[_nameLabel mas_makeConstraints:^(MASConstraintMaker *make){
make.top.equalTo(_login.top);
make.height.equalTo(@20);
make.left.equalTo(_login.left).with.offset(20);
}];
[_passLabel mas_makeConstraints:^(MASConstraintMaker *make){
make.top.equalTo(_nameLabel.bottom).with.offset(10);
make.centerX.equalTo(_nameLabel.centerX);
make.height.equalTo(@20);
}];
[_name mas_makeConstraints:^(MASConstraintMaker *make){
make.left.equalTo(_nameLabel.right).with.offset(5);
make.centerY.equalTo(_nameLabel.centerY);
make.height.equalTo(@20);
}];
[_pass mas_makeConstraints:^(MASConstraintMaker *make){
make.left.equalTo(_passLabel.right).with.offset(5);
make.centerY.equalTo(_passLabel.centerY);
make.height.equalTo(@20);
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
从上面可以看到,我们为元素创建了约束。当然这只是基本的方法。还有更多的API
接口可以使用。我们这里就不在过多说明了。
大概知道上面的用法,我们就可以开心的撸代码写界面去了。
:smile:smile:smile:smile:smile:smile:smile:
ReactiveCocoa初步了解
ReactiveObjC
是一个受到函数响应式编程启发而开发的OC框架,对应的Swift
框架叫做ReactiveCocoa or ReactiveSwift
。简称为RAC
。
不同于使用那些被替代和修改的可编辑变量,RAC提供了一个signals (represented by RACSignal)
用于捕获当前或者未来的值。这种做法工作起来类似于KVO
,但是却没有那么繁琐。
RAC的一大优势就是提供了一个统一的方法去解决异步表现,这些表现包括:委托方法,回调函数块,target-action机制,通知和KVO。
有如下例子:
//当self.name改变后,输出新的名字到控制台
//
//无论何时改变了值,RACObserve(self, username)都会创造了一个新的RACSignal用于发送当前self.name
// 不管在什么时候signal发送了一个新的值,-subscribeNext: 都将执行块方法.
[RACObserve(self, username) subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];
但是不想KVO通知,signals是可以被链接和操作的:
// 只输出以"j"开头的名字.
//
// -filter returns a new RACSignal that only sends a new value when its block
// returns YES.
[[RACObserve(self, username)
filter:^(NSString *newName) {
return [newName hasPrefix:@"j"];
}]
subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];
其实上面代码也可以复杂的写成:
RACSignal *usernameSourceSignal = self.username;
RACSignal *filteredUsername =[usernameSourceSignal
filter:^(NSString *newName) {
return [newName hasPrefix:@"j"];
}];
[filteredUsername subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];
这是因为RACSignal
的每个操作都会返回一个RACsignal
,这在术语上叫做连贯接口(fluent interface)
。这个功能可以让你直接构建管道,而不用每一步都使用本地变量。
Signals
也可以被使用去获取状态。区别于观察属性和设置其他属性来反应新的值,RAC
可以按照信号和操作来表达属性:
// Creates a one-way binding so that self.createEnabled will be
// true whenever self.password and self.passwordConfirmation
// are equal.
//
// RAC() is a macro that makes the binding look nicer.
//
// +combineLatest:reduce: takes an array of signals, executes the block with the
// latest value from each signal whenever any of them changes, and returns a new
// RACSignal that sends the return value of that block as values.
RAC(self, createEnabled) = [RACSignal
combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ]
reduce:^(NSString *password, NSString *passwordConfirm) {
return @([passwordConfirm isEqualToString:password]);
}];
还有许多用法这就不举例了,详细用例可以查看官方文档
使用时机
刚开始,可能很难理解RAC
,因为这个ReactiveObjC
很抽象,很难了解什么时机应该使用它,怎样去解决问题。这里有几个推荐的使用时机:
- 解决异步或者时间驱动的数据源
- 链接依赖操作(尤其在网络处理方面)
- 并行独立的工作
- 简化集合的转变
消息转发机制的简单实现
第一种
首先我们来实现这两个方法:
+(BOOL)resolveInstanceMethod: (SEL)sel
+(BOOL)resolveClassMethod:(SEL)sel
这两个方法用于方法的动态解析,当一个对象执行一个方法时,发现没有找到所对应的实现。这是就要靠上面的方法来动态添加实现了
举例来说:
//
// Person.h
// 消息转发
#import <Foundation/Foundation.h>
@interface Person : NSObject
//接口中声明一个方法
-(void)run;
@end
//
// Person.m
// 消息转发
#import "Person.h"
#import <objc/runtime.h>
@implementation Person //在部署的时候我们没有实现接口中声明的run方法
//编写C函数实现代码
void addrun (id self, SEL _cmd){
NSLog(@"%@ %s",self,sel_getName(_cmd));
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel ==@selector(run)) {
//如果响应run方法,我们动态将上面实现的addrun方法添加到当前类中。
class_addMethod(self, sel, (IMP)addrun, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
// main.m
// 消息转发
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//执行代码
Person *man = [Person new];
[man run];
}
return 0;
}
结果为:
说明执行了我们添加的那个方法,而并没有报错。
第二种
我们可以动态的向类中添加方法来解决无选择子的问题,我们也可以选择转换接受者来选择可以处理该选择子的对象。
首先当我们,没有处理第一种方法的时候,运行期系统会给我们第二个机会,询问是否有备用的接受者。我们就要实现orwardingTargetForSelector
了
我们举例来看:
首先我们先创建一个新类Car,并且创建一个run
方法:
然后在Person.m中,不对resolveInstanceMethod
进行任何处理,在forwardingTargetForSelector
中返回一个Car
实例:
然后继续执行main.m
文件,运行为:
说明系统已经将所要实现方法的对象转换为Car
的实例对象。
第三种
如果前两种我们都没有实现的话,就会进入第三种,执行完整的消息转发机制。
就会调用两个方法methodSignatureForSelector
和forwardInvocation
methodSignatureForSelector
方法用来指定方法签名,返回nil
表示不处理,否则会进入下一步调用forwardInvocation
方法。其中这个签名就是给forwardInvocation
中的参数NSInvocation
调用的。
开头我们要找的错误unrecognized selector sent to instance
原因,原来就是因为methodSignatureForSelector
这个方法中,由于没有找到run
对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。
所以我们需要做的是自己新建方法签名,再在forwardInvocation
中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。
关于生成签名的类型"v@:"
解释一下。每一个方法会默认隐藏两个参数,self
、_cmd
,self
代表方法调用者,_cmd
代表这个方法的SEL
,签名类型就是用来描述这个方法的返回值、参数的,v
代表返回值为void
,@
表示self
,:
表示_cmd
。
所以第二个和第三个字符必须是“@:”
,第一个字符是返回类型.
具体的可以看一下了解:Type Encodeing
iOS基础概念总结(一)
- 1. OC的前向声明
- 2. #import和#include的区别?
- 3. 用NSLog函数输出一个浮点类型,结果四舍五入,并保留一位小数
- 4.property属性的修饰符有什么样的作用
- 5. self.name=@object和_name=@object有什么不同?
1. OC的前向声明
由于在编译 Person
类的文件时,不需要知道Dog类的全部细节(Dog类中的方法),若使用 #import "Dog.h"
则必须知道Dog.h
的全部细节,而Person类中只需要知道类名Dog就可以了,可用通过以下方式告诉编译器@class Dog;
这种方式叫向前声明 (forward declaring)
,当.m
文件中要了解实现细节的时候在引入dog.h
//Person.h
#import <Foundation/Foundation.h>
@class Dog; //前向声明
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) Dog *dog;
- 向前声明解决了两个类互相引用的问题。
- 向前声明可用降低编译时间,除非必要,则可以使用向前申明,并在实现文件中映入头文件,这样做可用尽量降低类之间的耦合。
2. #import和#include的区别?
#import
是Objective-C
导入头文件的语法,可保证不会重复导入。
#include
是C/C++导入头文件的语法,如果是objective-c与C/C++混编码,对于C/C++类型的文件,还是使用#include来引入,这种写法需要添加防重复导入的语法。
3. 用NSLog函数输出一个浮点类型,结果四舍五入,并保留一位小数
float money = 1.011;
NSLog(@"%.1f", money);
4.property属性的修饰符有什么样的作用
property
是属性访问声明,扩号内支持以下几个属性:
- getter=getName、setter=setName:设置setter与getter的方法名
readwrite、readonly:设置可供访问级别
assign:方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题
retain:其setter方法对参数进行release旧值再retain新值,所有实现都是这个顺序
strong:此特质表明该属性定义了一种“拥有关系”(owning relationship)。为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。
weak 此特质表明该属性定义了一种“非拥有关系”(nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。
unsafe_unretained 此特质的语义和assign相同,但是它适用于“对象类型”(object type),该特质表达一种“非拥有关系”(“不保留”,unretained),当目标对象遭到摧毁时,属性值不会自动清空(“不安全”,unsafe),这一点与weak有区别。
copy:其setter方法进行copy操作,与retain处理流程一样,先对旧值release,再copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。
nonatomic:非原子性访问,不加同步, 多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。
5. self.name=@object和_name=@object有什么不同?
self.name =”object”:
会调用对象的setName()
方法;name = “object”:
会直接把"object"
字符串赋值给当前对象的name
属性。后者的效率会很高,因为没有调用set方法而是直接方法问内存。
Copyright © 2017 Powered by LZH, Theme used GitHub CSS.