如何在一个背景图上设置点击块
项目过程中遇到一个这样的问题,有一个大的背景图(如下图所示),图上设置好了1,2,3这样的按键快,我们需要给这些无规则的快设置点击事件!
解决思路,首先把图片放到界面中,犹豫不同机型尺寸不同,这个地方需要注意调整界面尺寸:
UIScrollView*scrollView=[[UIScrollView alloc]initWithFrame:SPFrame(0, 0, SPScreen_W, SPScreen_H)]; scrollView.contentInsetAdjustmentBehavior=UIScrollViewContentInsetAdjustmentNever; scrollView.bounces=NO; scrollView.showsVerticalScrollIndicator=NO; CGFloat h=SPScreen_W*1056/375.0f; scrollView.contentSize=CGSizeMake(SPScreen_W,h ); UIImageView*bgImageView=[[UIImageView alloc]initWithFrame:SPFrame(0, 0, SPScreen_W, h)]; bgImageView.image=[UIImage imageNamed:@"course_a"]; [scrollView addSubview:bgImageView]; [self.view addSubview:scrollView];
然后我们给背景图添加一个点击的手势
bgImageView.userInteractionEnabled=YES; UITapGestureRecognizer*tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapGestureEvent:)]; [bgImageView addGestureRecognizer:tapGesture];
现在我们需要确定这些点击块的中心,这边当然不要一个一个去测量,作为一个程序员当然要用程序来解决,这边可以在点击手势的事件中直接打印点击的点的坐标,记录下来即可,然后将这些点做一下处理保存到数组points中,处理方法如下
-(NSValue*)changeOriginalPoint:(CGPoint)opoint{ CGFloat w=SPScreen_W; CGFloat h=w*1056.0f/375.0f; CGFloat x=opoint.x*w/375.0f; CGFloat y=opoint.y*h/1056.0f; CGPoint point=CGPointMake(x, y); return [NSValue valueWithCGPoint:point]; }
现在我们需要处理的就是,判断我们点击的点是否在点击块中间
首先我们要利用上面点击块的中心点来设计一个点击区域
-(CGRect)center:(CGPoint)center expandArea:(CGFloat)number{ CGRect rect=SPFrame(center.x-number, center.y-number, number*2, number*2); return rect; }
然后实现点击手势事件,判断当前点击的点是否在点击块中即可
-(void)tapGestureEvent:(UITapGestureRecognizer*)sender{ CGPoint point = [sender locationInView:sender.view]; if([self directPoint:point]>=0&&[self directPoint:point]<32){ ActivityViewController*AVC=[[ActivityViewController alloc]init]; NSInteger n=[self directPoint:point]; AVC.message=[CourseMessage yy_modelWithDictionary:self.courseList[n]]; [self.navigationController pushViewController:AVC animated:YES]; } } -(NSInteger)directPoint:(CGPoint)point{ NSInteger n=9999; 循环块:0-self.points { CGPoint opoint=[self.points[i] CGPointValue]; CGRect frame=[self center:opoint expandArea:16]; if(CGRectContainsPoint(frame, point)){ n=I; break; } } return n; }
录音波形图的实现
在项目中我们需要实现一个动画特效,就是根据录音的分贝数来显示动画,线条长度代表分贝数,线条左右位置,逐渐变淡来代表时间,话不多说,下面介绍代码
1.创建一个视图view的子类:RecordWaveView
一个数组属性:waves,用来存储分贝数据
一个方法:-(void)setRecordWaveWithDecibel:(CGFloat)decibel,用来传入分贝数据,显示动画
初始化视图
-(instancetype)initWithFrame:(CGRect)frame{ if(self=[super initWithFrame:frame]){ self.backgroundColor=[UIColor clearColor]; } return self; }
画动画图,设置一个数组waves,waves中存储的是分贝数,然后将数组画在试图上:
-(void)drawRect:(CGRect)rect{ CGFloat w=self.frame.size.width/2; CGFloat h=self.frame.size.height; { CGFloat n=[self.waves[i] floatValue]; UIBezierPath*linePath=[UIBezierPath bezierPath]; [linePath moveToPoint:SPPoint(w-30-i*5, (h-n)/2)]; [linePath addLineToPoint:SPPoint(w-30-i*5, (h+n)/2)]; linePath.lineWidth=2; [[UIColor colorWithRed:250/255.0 green:127/255.0 blue:0/255.0 alpha:1-i/10.0f] setStroke]; [linePath stroke]; }循环块:0-self.waves.count的循环; { CGFloat n=[self.waves[i] floatValue]; UIBezierPath*linePath=[UIBezierPath bezierPath]; [linePath moveToPoint:SPPoint(w+30+i*5, (h-n)/2)]; [linePath addLineToPoint:SPPoint(w+30+i*5, (h+n)/2)]; linePath.lineWidth=2; [[UIColor colorWithRed:250/255.0 green:127/255.0 blue:0/255.0 alpha:1-i/10.0f] setStroke]; [linePath stroke]; }循环块:0-self.waves.count的循环; }
实现方法,让画的视图动起来
-(void)setRecordWaveWithDecibel:(CGFloat)decibel{ if(self.waves.count<10){ [self.waves insertObject:[NSNumber numberWithFloat:decibel] atIndex:0]; } else{ [self.waves removeLastObject]; [self.waves insertObject:[NSNumber numberWithFloat:decibel] atIndex:0]; } [self setNeedsDisplay]; }
项目中的工具类
项目中我们一般需要新建一个工具类,用来实现一些项目中经常使用的功能,方便复用,这边我收集了一些自己经常使用的功能
1.添加文字提示框
+(void)showHubTipWithString:(NSString *)string offsetY:(CGFloat)y{ if (string && string.length > 0) { dispatch_async(dispatch_get_main_queue(), ^{ MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:[[UIApplication sharedApplication] keyWindow] animated:YES]; hud.mode = MBProgressHUDModeText; hud.detailsLabel.font = [UIFont boldSystemFontOfSize:17.0]; hud.detailsLabel.text =string; hud.margin = 20.f; hud.offset=SPPoint(0, y); hud.removeFromSuperViewOnHide = YES; [hud hideAnimated:YES afterDelay:0.8]; }); } }
2.添加加载提示框
+(MBProgressHUD*)showLoadingHubAddedTo:(UIView*)view{ MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:view]; hud.contentColor=[UIColor whiteColor]; hud.bezelView.backgroundColor=[UIColor colorWithWhite:0.1 alpha:1]; hud.mode = MBProgressHUDModeIndeterminate; hud.detailsLabel.font=SPFont(17); hud.minShowTime=2; hud.userInteractionEnabled = YES; hud.removeFromSuperViewOnHide = YES; [view addSubview:hud]; return hud; }
3.手机号验证
+(BOOL)validateMobile:(NSString*)mobile{ NSString*mobileRegex=@"^(1[123456789][0-9]{9}$)"; NSPredicate*mobileTest=[NSPredicate predicateWithFormat:@"SELF MATCHES %@",mobileRegex]; return [mobileTest evaluateWithObject:mobile]; }
4.密码验证
+(BOOL)validatePassword:(NSString*)password{ NSString*passwordRegex=@"^([a-zA-Z0-9]{6,12}$)"; NSPredicate*passwordTest=[NSPredicate predicateWithFormat:@"SELF MATCHES %@",passwordRegex]; return [passwordTest evaluateWithObject:password]; }
5.获取段落行高
+(CGFloat)GetCellHeightWithString:(NSString *)string width:(CGFloat)width attributes:(NSDictionary *)attributes{ CGRect sizestring = [string boundingRectWithSize:CGSizeMake(width,MAXFLOAT) options: NSStringDrawingUsesLineFragmentOrigin| NSStringDrawingUsesFontLeading attributes:attributes context:nil]; return ceil(sizestring.size.height); }
6.秒数到时间到转化
+(NSString*)changeNumberToTime:(NSInteger)number{ NSString*minute=[NSString stringWithFormat:@"%02ld",number%3600/60]; NSString*second=[NSString stringWithFormat:@"%02ld",number%60]; return [NSString stringWithFormat:@"%@:%@",minute,second]; }
简单的网络加载图的实现
在编写项目的时候我们需要进行网络的加载,那么网络加载需要时间和重新加载的选项,所以我们可以需要一个加载界面,在网络加载的过程中,重新加载的时候呈现
编辑一个简单的网络加载界面,我们需要一个加载动画,以及加载成功或者失败的处理方法
1.建立一个UIView的子类,LoadingView
2.我们需要的属性: a:CAShapeLayer*_loadingShapeLayer(制作简单动画); b:UILabel*promptLabel(提示文字); c:UIButton*reloadButton(重新加载按键) ; d:LoadingFailBlock failBlock(加载失败的事件处理块)
3.初始化界面
+(LoadingView*)ShowLoadingViewFrame:(CGRect)frame withSuperView:(UIView *)view{ LoadingView*loadingview=[[LoadingView alloc]initWithFrame:frame]; [view addSubview:loadingview]; [loadingview loadingShapeLayerAnimation]; return loadingview; } -(instancetype)initWithFrame:(CGRect)frame{ if(self=[super initWithFrame:frame]){ self.backgroundColor=[UIColor whiteColor]; _loadingShapeLayer=[[CAShapeLayer alloc]init]; _loadingShapeLayer.frame=SPFrame(frame.size.width/2-40, frame.size.height/2-100, 80,80 ); _loadingShapeLayer.path=[UIBezierPath bezierPathWithOvalInRect:SPFrame(0, 0, 80,80 )].CGPath; _loadingShapeLayer.fillColor=nil; _loadingShapeLayer.strokeColor=[UIColor lightGrayColor].CGColor; _loadingShapeLayer.lineWidth=2; _loadingShapeLayer.lineJoin=kCALineCapRound; _loadingShapeLayer.lineDashPattern=@[@15,@8]; [self.layer addSublayer:_loadingShapeLayer]; promptLabel=[[UILabel alloc]initWithFrame:SPFrame(frame.size.width/2-40, frame.size.height/2-100, 80,80 )]; promptLabel.backgroundColor=[UIColor clearColor]; promptLabel.text=@"正在加载"; promptLabel.textColor=[UIColor lightGrayColor]; promptLabel.font=SPFont(15); promptLabel.textAlignment=NSTextAlignmentCenter; [self addSubview:promptLabel]; } return self; }
4.实现方法:加载成功
-(void)LoadingSuccessComplation:(void (^)(void))complation{ SPSelf; [UIView animateWithDuration:2 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{ weakSelf.transform=CGAffineTransformMakeScale(0.001, 0.001); } completion:^(BOOL finished) { [weakSelf removeFromSuperview]; }]; if(complation){ complation(); } }
5.实现方法:加载失败
- (void)LoadingFailComplation:(void (^)(void))complation{ [self stopLoadingShapeLayerAnimation]; promptLabel.text=@"加载失败"; [self createReloadingButton]; SPSelf; if(complation){ weakSelf.failBlock = complation; } } -(void)createReloadingButton{ reloadButton=[[UIButton alloc]initWithFrame:SPFrame(0, 0, 100, 30)]; reloadButton.center=SPPoint(self.frame.size.width/2, self.frame.size.height/2+50); [reloadButton setTitle:@"重新加载" forState:UIControlStateNormal]; [reloadButton setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal]; reloadButton.backgroundColor=[UIColor whiteColor]; reloadButton.layer.cornerRadius=14; reloadButton.layer.masksToBounds=YES; reloadButton.layer.borderWidth=1; reloadButton.layer.borderColor=[UIColor orangeColor].CGColor; reloadButton.titleLabel.font=SPFont(14); [reloadButton addTarget:self action:@selector(reloadEvent) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:reloadButton]; } /* *重新加载按键点击事件* */ -(void)reloadEvent{ [reloadButton removeFromSuperview]; [self loadingShapeLayerAnimation]; self.failBlock(); }
6.动画实现
/* *load圈转动* */ -(void)loadingShapeLayerAnimation{ CABasicAnimation*rotate=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; rotate.fromValue=0; rotate.toValue=@(M_PI*2); rotate.duration=3; rotate.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; rotate.repeatCount=HUGE; rotate.fillMode=kCAFillModeForwards; rotate.removedOnCompletion=NO; [_loadingShapeLayer addAnimation:rotate forKey:rotate.keyPath]; }
轮播图的封装
新产品项目中需要编写一个轮播图的功能,效果如下图所示:
其中我们需要用到一个第三方库:iCarousel,这是一个做轮播图非常强大的库,基本可以满足日常开发的需求,我们将在此库的基础上编写实现我们需要的轮播图
首先建立一个基础与UIView的子类:NSRoastView
需要2个属性,1.NSTimer用来实现图片的自动轮播;2.iCarousel第三方库
初始化
实现iCarousel代理方法
实现方法让他能自动轮播
推荐一个原型制作工具墨刀
最近一周都是在商量新产品的功能和制作简易的产品原型,这边我用的墨刀来进行原型的制作
在墨刀,你既可以创建移动端项目(iPhone/Android)、平板项目、也可以创建Web/电视项目、Watch项目,并可以自定义尺寸,为各种屏幕创建原型。
登陆墨刀之后,点击“新建项目”即可。
当然,也可以选择“从模板中创建项目”。
鼠标悬浮在模板上方,点击“使用模板”,就能创建该模板项目。
创建好项目后,进入到工作区
1.添加页面
添加同级页面:选中一个页面,再点击页面列表顶部的「添加新页面」,便可以新建该页面的同级页面。
添加子级页面:鼠标悬浮某页面上,点击「...」展开更多按钮,单击「添加子页面」即可为该页面添加子级页面。目前最多支持创建7级页面。
2.修改页面名称
双击修改页面名称:可使用“数字”+“页面功能”方式命名,既便于页面识别,又可弥补多级列表演示时的不足,更清晰地展示页面逻辑关系。<> /p
3.调整页面顺序
拖拽调整页面顺序:拖动某页面在页面列表上下移动位置即可。如果将页面 A 移动为页面 B 的次级页面,直接拖拽 A 至 B 区域处即可。
4.复制/转移/删除页面
鼠标悬浮某页面,页面名称右侧会显示「...」更多按钮,点击更多按钮展开「创建副本」、「移动到」以及「删除」的按钮。
5.编辑页面式样
在右侧的“页面设置面板”,你可以选择页面是“竖屏”还是“横屏”演示,可以修改背景颜色。
可以选择是否显示“布局”,并可以修改“列数”/"间隔"/“尺寸”。
可以选择是否显示“网格”,并可以调节网格大小。
可以查看当前页面链接数,并在这里一键删除。
6.添加组件和图标
在最右的设置面板左侧里有“组件”、“我的组件”、“图标”库,点击即可打开相应的素材库。
组件(官方组件)库:墨刀官方提供丰富的组件库,除了基础组件,还有苹果的 iOS, 谷歌的Material design, 微信的 WeUI,蚂蚁金服的 Ant Design, Windows 10 等多套组件库。这些都可以直接使用,画原型效率大大提升。
我的组件:你自定义的组件库。
图标:覆盖各领域的图标,来自 Font Awesome, Material Design 和墨刀图标系列。
墨刀目前有两种方式添加组件/图标到画布上:
1 双击组件/图标
2 拖拽组件/图标到画布
对于左侧(在“推荐”模式下)的“快捷组件”来说,还有第三种添加方式:按住对应的快捷键然后鼠标画出。比如画“长方形”组件,就按住“R”。至于其他组件用什么快捷键,鼠标悬浮在“快捷组件”上就可以看到啦!
7.编辑组件属性
选中组件,右侧便会呈现出组件的设置面板。
在面板里可以编辑组件的各种属性,比如位置、大小、颜色、透明度、阴影、动效等等。
选中多个组件,可以编辑它们的布局方式,比如“左对齐”、“右对齐”、“居中对齐“、“水平等间距”、“垂直等间距”…
利用好这些功能就能简单的绘制一个原型满足日常需求了
标签: 原型制作工具简单的功能介绍
runtime的一些其他常见用法
上篇我们利用runtime来实现了按键点击范围的扩大,其实runtime还有许多常见用法,比如:动态获取类名,动态获取类的成员变量,动态获取类的属性列表,动态获取类的方法列表,动态获取类所遵循的协议列表,动态添加新的方法,类的实例方法实现的交换,动态属性关联,消息发送与消息转发机制等。下面就介绍一些常见的用法
1.获取类名
动态的获取类名是比较简单的,使用class_getName(Class)就可以在运行时来获取类的名称。class_getName()函数返回的是一个char类型的指针,也就是C语言的字符串类型,所以我们要将其转换成NSString类型,然后再返回出去。下方的+fetchClassName:方法就是我们封装的获取类名的方法
2.获取成员变量
下方这个+fetchIvarList:这个方法就是我们封装的获取类的成员变量的方法。当然我们在获取成员变量时,可以用ivar_getTypeEncoding()来获取相应成员变量的类型。使用ivar_getName()来获取相应成员变量的名称。下方就是对获取成员变量的功能的封装。返回的是一个数组,数组的元素是一个字典,而字典中存储的就是相应成员变量的名称和类型。
3.获取成员属性
上面获取的是类的成员变量,那么下方这个+fetchPropertyList:获取的就是成员属性。当然此刻获取的只包括成员属性,也就是那些有setter或者getter方法的成员变量。下方主要是使用了class_copyPropertyList(Class,&count)来获取的属性列表,然后通过for循环通过property_getName()来获取每个属性的名字。当然使用property_getName()获取到的名字依然是C语言的char类型的指针,所以我们还需要将其转换成NSString类型,然后放到数组中一并返回。
4.获取类的实例方法
接下来我们就来封装一下获取类的实例方法列表的功能,下方这个+fetchMethodList:就是我们封装的获取类的实例方法列表的函数。在下方函数中,通过class_copyMethodList()方法获取类的实例方法列表,然后通过for循环使用method_getName()来获取每个方法的名称,然后将方法的名称转换成NSString类型,存储到数组中一并返回。
5.动态添加方法实现
下方就是动态的往相应类上添加方法以及实现。下方的+addMethod方法有三个参数,第一个参数是要添加方法的类,第二个参数是方法的SEL,第三个参数则是提供方法实现的SEL。稍后在消息发送和消息转发时会用到下方的方法。下方主要是使用class_getInstanceMethod()和method_getImplementation()这两个方法相结合获取相应SEL的方法实现。下方的IMP其实就是Implementation的方法缩写,获取到相应的方法实现后,然后再调用class_addMethod()方法将IMP与SEL进行绑定即可。
6.方法实现交换
下方就是讲类的两个方法的实现进行交换。如果将MethodA与MethodB的方法实现进行交换的话,调用MethodA时就会执行MethodB的内容,反之亦然。
利用runtime实现按键点击范围放大
在编写项目的过程中我们一般都会遇到这样的情况,按键设计的比较小,用户不方便点击,造成体验很差这种状况,我们就需要在不改变按键UI的情况下实现按键点击范围放大
这时候我们就需要用的runtime机制,在程序运行和编译的时候做一个小动作,改变一下按键的点击范围
首先我们先建立一个button的分类来扩充一下按键的方法
设定一些静态的常量
static char topNameKey
static char rightNameKey
static char bottomNameKey
static char leftNameKey
然后编写扩大范围的方法
返回一个扩大的范围
改写按键点击事件,让点击范围变大
数据存储方式FMDB
FMDB说iOS平台的SQLite数据库框架,使用起来更加面向对象,省去了很多麻烦,冗余的C语言代码,对比苹果自带的Core Data框架,更加轻量级和灵活,提供了多线程安全的数据库操作方法,有效的防止数据混乱
FMDB有三个主要的类
FMDatabase:一个FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句
FMResultSet:使用FMDatabase执行查询后的结果集
FMDatabaseQueue:用于在多线程执行多个查询或更新,它是线程安全的
打开数据库,通过指定SQLite数据库文件路径来创建FMDatabase对象
FMDatabase*db=[FMDatabase databaseWithPath:path];
if(![db open]){NSLog(@"数据库打开失败");}
文件路径有三种情况
(1) 具体文件路径,如果不存在会自动创建
(2) 空字符串@“”,会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,数据库文件也被删除
(3) nil会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁
在FMDB中,除查询以外的所有操作,都被称为“更新” create drop insert update delete等
使用executeUpdate:方法执行更新
-(BOOL)executeUpdate:(NSString*)sql
-(BOOL)executeUpdateWithFormat:(NSString*)format,...
-(BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray*)arguments
查询方法
-(FMResultSet*)executeQuery:(NSString*)sql,...
-(FMResultSet*)executeQueryWithFormat:(NSString*)format,...
-(FMResultSet*)executeQuery:(NSString*)sql withArgumentInArray:(NSArray*)arguments
遍历结果集
while([rs next]){NSString*name = [rs stringForColumn:@"name"];int age=[rs intForColumn:@"age"];double score = [rs doubleForColumn:@"score"];}
示例代码
数据存储方式NSUserDefaults
很多iOS应用都支持偏好设置,比如保存用户账号,密码等,iOS提供了一套标准等解决方案来为应用加入偏好设置功能,每个应用都有个NSUserDefaults实例,通过它来存取偏好设置
保存数据
读取数据
补充说明
偏好设置是专门用来保存应用程序等配置信息的,一般情况不要在偏好设置中保存其他数据。如果利用偏好设置来存储数据,默认就是存储在Preferences文件夹下面的,偏好设置会将所有的数据都存储到同一个文件中
使用偏好设置对数据进行保存之后,它保存到系统的时间是不确定的,会在将来某个时间点自动将数据保存到Preferences文件夹下面,如果需要即刻将数据存储,可以使用[defaults synchronize]
所有的信息都写在一个文件中,对比简单的plist可以保存和读取基本的数据类型