2016-02-25 11:39:03 qq_29892943 阅读数 4943

在ios中更改app的项目名字是一个麻烦事,不过有时候根据需要我们不得不去修改,正好这两天我也修改了一下我的app名字,就去研究了一下,今天拿来给大家分享。
下面,我就把一个app名字为‘西游记’的项目改为’水浒传‘把。

一:先看一下我们的项目,然后打开项目。

这里写图片描述

二:双击项目名字,将其改为‘水浒传’,点击确定。

这里写图片描述

接下来,会出现这样一个界面,这是xcode默认帮我们修改的一些东西,直接rename就行了,然后点ok完成修改。

这里写图片描述

有时候,我们更改完名字之后可能上面的运行部分变成了mac,一个齿轮。这时候我们关闭项目从新打开,看看可以不,如果不可以我们就点击下图我标识的地方创建一个新的sehemes,把老的更改为mac的那个删掉。

这里写图片描述

注意,这时候我们看这个项目的路径,相对路径已经变成了绝对路径,那么这时候在你的电脑上是可以的,如果你把这个项目发给别人的时候,或者把这个项目放到别的文件夹里面,这时候你再打开项目,项目就会变成红色。这时候,我们需要将项目删除,然后重新添加,这时候项目工程的相对路径才会正确。

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

三:修改这两个文件,直接双击,将其中的‘西游记’改为‘水浒传’

这里写图片描述

这里写图片描述

相信大多数人在项目的搭建里面,都不会是虚目录,这些文件夹都是确确实实在我们的项目根目录的,那么我们接下来就改一下根目录的名字,点击项目名字 右键 show in finder

这里写图片描述

这里我们发现,我们虽然在项目中改了 但是在根目录里面并没有改掉,接下来我们把根目录的文件夹名字也改过来

这里写图片描述

打开项目我们会发现,出问题了,项目中这两个文件夹下的所有的文件都变成红色的了。

这里写图片描述

这时我们点一下水浒传这个文件夹 然后去看一下右边的属性栏,会发现它的路径还是指向原来的西游记路径

这里写图片描述

这时候我们点击红色圆圈里面的图标,给他从新选择正确的根目录路径,选择choose就可以了。

这里写图片描述

同样的道理,我们点击项目中的水浒传Tests,在右边属性栏选择新的目录路径。这时候我们会发现,所有的红色文件又都可以用了。

四:点击项目的搜索功能,将其改为Replace更换功能,输入西游记搜索然后更换为水浒传,这里面大部分是注释。

这里写图片描述

点击Replace All更换掉,更改完之后上面会出现绿色的对号。但是还有三处需要我们自己手动更改。如图根据箭头所指方向,我们手动将里面的‘西游记’改为‘水浒传’。

这里写图片描述

这里写图片描述

然后我们build一下,这时候会出现一个错误

这里写图片描述

然后我们再项目中去搜索这个‘西游记/Info.plist’,找到之后发现有两个,我们将其改为‘水浒传/Info.plist’就可以了。

这里写图片描述

五:本来到这里,项目已经可以运行,并且成功改好名字了,但是为了让我们的项目更加完美,在任何地方都找不到老项目的名字,我们还需最后一步。
关闭,xcode。打开项目的根路径,找到目录。如果我们的项目有.xcworkspace,那么我们会发现这个名字还没有改,双击现在将其改过来。
这里写图片描述

然后分别在如图两个工程下显示包内容

这里写图片描述

这里写图片描述

将这两个包里面的所有文件夹都点开一遍,凡是发现有‘西游记’的文件都将其改为‘水浒传’。改完之后,这回才叫一个干净彻底,项目从头到尾再也不会有旧名字了。

2018-07-13 15:34:42 chmod_R_755 阅读数 1607

iOS项目架构分为以下几步,这些都是总结的经验之谈


1.架构

  • 1.架构这个概念很抽象,但是用一句话来说叫看菜吃饭,有些人喜欢先看UI在架构,有些人喜欢先把该有的东西有了再看UI,其实都可以…..
  • 2.cocoapod 这个是必备的,现在第三方包基本用这个管理
  • 3.尽量找出项目中相似的东西:操作行为(eg:需求登录,需求权限,需求授权,分享等),视图行为(字体,字号,有规律控件);我们需要将这些相同的或者相似的东西尽量多找出来,写一些工具类或者写一个基类让自己的类继承
  • 4.不建议使用xib等可视图工具或插件开发iOS,不为什么,经验
  • 5.Xcode自带的svn/git 工具挺好,国内私有代码托管码云不错;githu是国际化路线私有收费不建议,而且天朝国情github速度性能托管代码没有码云好
  • 6.代码每一个固定的周期备份,在完成一个比较重要的模块的时候记得备份
  • 7.某个模块有独立的功能的时候,把模块独立出来,同样把模块的资源文件也独立出来;项目自带的Images.xcassets 里面文件分类最好对应相应的UIView
  • 8.开发证书先弄好,正式&开发 推送等证书先弄好,或许现在用不上用上的时候再搞听操蛋的 ; 各种秘钥先申请好 微信分享,微博分享,推送,统计,支付等等 一大推的秘钥我是建议在开发之前先申请好,别老是更新APP的名字或是icon 要改的地方很多 。。。
  • 9.建议iPhone和iPad分成两个项目来搞(小厂另说,若UI设计的很相似的话如微信可共用一套)

2.UI

  • 1.首先导入Masonry 不解释,肯定要用的
  • 2.确定下UI的布局形式:a)字号不变 b)字号变化,这里单独拿出来是因为ios的几个机型宽度高度存在着一定的差异,iPhoneX 和 iPhone6 同宽不同高,iPhone5和iPhone6及iPhone6Plus的关系应该是 iPhone5 ->@2x.png而 iPhone6Plus对应的是->@3x.png ,并不是iPhone6->*@2x.png ;但是平时UI妹子(也有汉子)在设计的时候会以iPhone6为准那么在640x1136的屏幕上怎么呈现需要先说明,别到最后BB的那就蛋疼了 ,然后是字号怎么变化的问题先说好
  • 3.android有android的UI风格,同样ios有ios的UI风格,SB的UI会说做成一样的;ios的状态栏,导航条,tabbar栏(tabbar里面的图片大小,文字大小其实都有一套标注的),toolbar栏的高度是固定,自定义UI另说
  • 4.iOS中可以使用第三方字体,注意版权的问题 (还是推荐使用苹果自己的字体)小厂没事情,大厂就瞎了 。。。。 不解释
  • 5.阿里icon里面有好多可以用的icon,同样注意版权问题
  • 6.icon需要1024x1024的(商店需要),有很多工具(有在线的)可以生产icon,所以你只需要一张1024的图就行,别自己ps处理浪费时间
  • 7.APPstore中 iPhone的图 可以截屏或自定定义;但是iPad的图可以截屏如果要自定义的话图片中必须有iPad(图片中必须画一个iPad,然后在iPad上画自定义UI,否则审核可能会不过的,不能直接拿着iPhone的图放大)

3.网络

  • 1.网络框架AFNetworking这个不解释,AFNetworking 是异步操作
  • 2.MVC这我我要说的是Model,应该这个会伴随整个项目,你选用的基础model(Manually,YYModel,FastEasyMapping,JsonModel,Mantle,MJExtension)可能会影响你写代码,我推荐使用YYModel,当然还有配套的工具 JSONModel 方便格式化成对象
  • 3.建议接口调用加密,加密方式我总计代码加密和证书加密;代码加密很简单,就是在传入参数的时候通过一定的排列规则组合成一个秘钥当参数传入,可以防止别人恶意刷接口(例如短信验证码),写一套加密规则;证书加密就比较麻烦,分单认证和双向认证,金融类的APP可以这么高,小厂APP这么搞时间成本高不建议(通常是服务端吃不消,小厂的服务端不像大厂,况且小厂哪有那么多屁事搞得跟真的似得)
  • 4.更新图片的时候图片需要重命名(这个有必要跟接口说下,有些接口就是SB),ios图片缓存是根据名称来缓存的
  • 5.AFNetworking其实自带缓存功能,网络请求方式有很多种、常用的是 POST和GET,其实还有DELETE,PUT,HEAD,PATCH,TRACE,OPTIONS(其实远不止这些但是get和post足够了)
  • 6.参数传递可以通过header和parameter两种方式

4.数据存储

  • 1.FMDB是个好的sqlite框架,这个不解释12K+的star
  • 2.最好有一点SQL基础,因为你可能需要自己写SQL语句
  • 3.数据库还有考虑以后增加字段等问题,数据库的类型不好把握数字(整形)用Integer其他用Text
  • 4.ios中支持可视化的数据库CoreData,而且CoreData有增删改查对应的方法或者事件,至于怎么选择看自己的习惯或者说看业务的要求
  • 5.CoreData也有很多优秀的开源项目

5.安全

  • 1.手机被越狱之后安全不是ios开发工程师考虑的
  • 2.网络请求这块建议https+证书认证,参数加盐加密(别只是SB的MD5),做到这两点可以抵挡99%非法操作(如果受剩下的1%入侵,我觉得你们厂至少在Hong Kong上市。。。都能在Hong Kong上市,那肯定不差钱请安全砖家了)
  • 3.如果说是图片类的公司,最好搞个神马水印上面,实在是搞不了水印的保存到本地可以把图片二进制文件打乱再保存,视频类文件也是一样的
  • 4.ios有加密APP的工具,不过收费的居多,但是app商店能抵挡一些恶意操作,总体感觉是加密的话成本较大不推荐
  • 5.授权安全:ios不像android,android偷偷地,后台偷偷地干些事情;iOS会有提醒,最常用的授权就是通信录、相机、相册、位置等
  • 6.注意私有API的使用,部分框架中包含了私有api,实时关注第三方API的更新情况

6.统计、bug管理

  • 1.统计无可厚非肯定是第三方统计,原因:技术成熟框架稳定;可以自己写统计,但是其一麻烦,其二维护难;统计现在好多公司在做,友盟,sharesdk,极光都在搞统计,友盟的统计是比较受认可的,友盟本来就是统计出生的,然后友盟的爹是阿里,不解释 ; 统计用友盟!!!
  • 2.统计这块会牵扯到一个问题,那就是版本:也就是项目必须有一个版本管理的模块,从项目准备做开始就得要设计这个模块
  • 3.统计为什么和bug管理扯上关系了,bug 管理也是和版本有关的 ,我们常用的远程bug工具是腾讯的bugly,友盟应该也有类似的工具,但是腾讯的这个还可以
  • 4.iOS不支持热修复 ,死了这条心
  • 5.版本号,其实这个没有神马共识,爱怎来怎么来,但是最好是出一套规定,版本号怎么更新
  • 6.统计,bug管理,版本管理最好在项目一开始就要干

7.分享

  • 1.分享为什么要单独拿出来了?首先分享有两大家, sharesdk 和 友盟,还有其他乱七八糟的不多介绍了,个人建议都用友盟的,因为你的统计用了友盟的了,你再用sharesdk分享的话,你的APP会大很多,减少APP大小从我做起
  • 2.截屏分享的功能苹果官方是禁止的,若查到无法上架的(凭运气)
  • 3.有支付和没有支付的分享导入的第三方包不一样
  • 4.几乎所有的第三方分享都支持用户自定义分享界面

8.推送

  • 1.推送几大家族: 极光,个推,百度云,友盟等 … 这么多推送其实都是为了照顾android,因为ios自己的推送本身很稳定,或者说很容易实现
  • 2.推送证书最好提前准备,正式版和测试版,名称、密码最好搞一样的,省的以后麻烦,当然你都导入了友盟统计和分享,推送也可以用友盟的,省事;但是android可能就没有那么走运了,中兴华为小米各个推送分分钟让人想自杀。。。。。
  • 3.推送证书有有效期的,推送不灵了,注意查看证书的有效期
  • 4.可以自定义推送,最好是能将推送与登录用户关联起来
  • 5.推送需要用户主动授权,不授权就瞎了

9.支付

  • 1.支付宝+微信
  • 2.支付宝开发难度低,微信开发难度超级难;主要是微信的文档太操蛋了,根本看不懂,跟水平无关
  • 3.客户端开发很简单,主要难点在服务端,但是有现成的集成框架;加密密钥全部在服务端,客户端要做的是请求接口获取支付参数就行,也无需神马高级的配置
  • 4.有集成好的第三方工具,ping++ 但是:收费
  • 5.支付回调,订单信息更新都需要服务端操作,禁止客户端改变订单状态

10.其他

  • 1.前期对需要越明确越好
  • 2.尽量控制APP的大小,当然是越小越好,能有颜色代码就不用图片;大图片资源建议压缩 tinypng不错
  • 3.有很多比较好的类扩展,比如md5,NSArray,NSDate等扩展肯定能用上,尽量平时多收集扩展
  • 4.频繁操作的函数独立出来,URL地址,通知,还有些宏定义最好都独立出来
  • 5.OC代码可以直接调用C和C++ 但是调用swift的时候需要一个中间文件
  • 6.测试调试尽量用真机
  • 7.ios支持png和jpg还有gif等格式

这里写图片描述

2016-01-15 10:39:00 sinat_23659935 阅读数 2007

献给初学iOS的小盆友们——微博app项目开发之一 项目初始化

本人自学iOS也有七八个月了,不敢说学到很深入了,但也算入了门。此次微博app项目参考了传智播客培训教材,主要学习内容有架构思想,封装思想,代码重构,业务逻辑等内容,项目涵盖面广泛,讲解易懂,且采用纯代码方式搭建UI,希望对那些没有时间看视频的初学者们有所帮助。相信学习完本套项目,初学者会在编程思想上有一个很大的提升。


内容

  • 项目素材获取
  • 环境配置
  • 自定义tabBarController
  • 修改tabBar内部结构
  • 划分结构

本节资料

第一节资料下载


1.1 项目素材获取

首先模仿一个项目,需要图片等素材,单凭自己是做不出来的。本项目提供了基本的图片素材供下载使用,一般项目素材的获取步骤如下:

  • 打开苹果电脑iTunes应用,选择顶部栏“AppStore”项
  • 然后在搜索框内输入“微博”并搜索
  • 找到微博并点击,进去后点击微博图标下的“取得”按钮
  • 输入 iCloud账户密码后即可下载
  • 左上角下载按钮可以显示下载进度
  • 点击顶部栏“我的iPhone应用按钮”
  • 找到微博应用,右键点击,选择“在Finder中显示”
  • 可以看到下载的是ipa类型的文件,使用解压文件解压后即可得到微博应用文件夹
  • 进入文件夹后,选择Payload下的Weibo.app文件,右键点击后选择“显示包内容”,即可看到微博应用所需的所有图片,以后项目模仿都会用得到

1.2 环境配置

开发任何一个大型的应用都需要提前对开发环境进行配置,本次微博项目对Xcode进行了能满足我们模仿要求的简单设置。配置过程也就是修改info.plist文件而已,点击“微博模拟”项目出现的设置页面就是info.plist的图形化界面。配置过程如下:

  • Bundle Identifier 设置
    Bundle Identifier 主要作用有app在上传app store时为了区分不同程序 时使用,开发推送功能时需要,在这里设置成YGWeibo.- - - - 。
  • Version 版本号
    以后迭代开发时,版本号必须比之前的大,在这里不需要设置
  • Development Target
    选择7.0以后的都可以
  • Devices
    选择iPhone
  • Main Interface
    此次项目采用纯代码创建,所以不需要加载storyboard,在这里设置为空,并且把左侧Main.Storyboard,ViewController.h,ViewController.m 文件删除。
  • Device orientation
    只选择portrait
  • Status Bar Style
    选择default后,勾选Hide Status Bar

这里讲一讲,怎么用纯代码得到跟加载main.storyboard有相同效果的界面。首先苹果应用程序的启动步骤是这样的,在一开启时,首先进入main函数,main 函数内主要执行三个步骤,首先创建UIApplication对象,然后创建AppDelegate对象,并且成为UIApplication对象的代理属性,然后开启主线程循环,最后加载info.plist文件,判断是否有main.storyboard,如果有,就会加载main.storyboard。因为我们这里才用纯代码开发,所以info.plist就没有main.storyboard文件了,需要在AppDelegate.m里的第一个代理方法中设置窗口,以及创建并加载视图控制器,代码如下,但是此代码非最终的tabBarVC的设置代码,以后会有修改此处只做演示用。此代码就相当于加载Storyboard的步骤。


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 创建窗口
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

    // 为了展示效果 设置背景色为黄色
    self.window.backgroundColor = [UIColor yellowColor];

    // 创建tabBarViewController
    UITabBarController *tabBarVc = [[UITabBarController alloc] init]; 
    tabBarVc.view.backgroundColor = [UIColor redColor];

    // 管理子控制器
    // 首页
    UIViewController *home = [[UIViewController alloc] init];
    home.view.backgroundColor = [UIColor greenColor];
    [tabBarVc addChildViewController:home];

    // 消息
    UIViewController *message = [[UIViewController alloc] init];
    message.view.backgroundColor = [UIColor blueColor];
    [tabBarVc addChildViewController:message];

    // 发现
    UIViewController *discover = [[UIViewController alloc] init];
    discover.view.backgroundColor = [UIColor purpleColor];
    [tabBarVc addChildViewController:discover];

    // 我
    UIViewController *profile = [[UIViewController alloc] init];
    profile.view.backgroundColor = [UIColor lightGrayColor];
    [tabBarVc addChildViewController:profile];

    // 设置窗口的根控制器
    self.window.rootViewController = tabBarVc;

    // 显示窗口
    [self.window makeKeyAndVisible];
    return YES;
}
  • 设置AppIcon
    直接拖拽素材文件夹内的AppIcon文件夹到Xcode里Assets.xcassets内的AppIcon里即可
  • 设置启动图片
    在项目General 设置里面找到Launch Images Source 栏,点击后出现对话框,选择migrate,然后点击其后的灰色按钮,会发现自动跳转到Brand Assets 栏内。打开素材文件夹中的LaunchImages文件夹,拖拽文件到Brand Assets 内,出现绿色加号后放开即可,最后删除Launch Screen File 栏内的东西。
    但是这种方法不如直接加载Launch Screen.storyboard文件夹更强大,因为Launch Screen.storyboard可以展示更多的东西,且不需要美工出很多启动图片,因为其可以自动布局以适应各种大小屏幕。如果不删除Launch Screen.storyboard,则会优先加载Launch Screen.storyboard内容。

1.3 自定义tabBarController

1.3.1 更改AppDelegate.m

因为AppDelegate.m文件以后会越来越大,应该自定义一个类专门用于创建和管理tabBarController。在新建文件之前,要设置个前缀,以方便区别和管理。选择左边栏微博模拟项目,找到最右边栏有个“Class Prefix”栏,写上你想起的前缀名称即可,如下图所示。
设置项目前缀
更改AppDelegate.m内容为如下代码:(首先要新建YGTabBarController)

#import "AppDelegate.h"
#import "YGTabBarController.h"
@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    // 创建窗口

    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

    //创建自定义tabbarcontroller

    YGTabBarController *tabBarVc = [[YGTabBarController alloc]init];

    // 设置窗口的根控制器

    self.window.rootViewController = tabBarVc;

    // 显示窗口

    [self.window makeKeyAndVisible];
    return YES;
}

在此,提醒下UITabBarController内的view在一创建控制器的时候[[YGTabBarController alloc]init]就会加载View,并执行ViewDidLoad方法,所以其View不是懒加载的。但是一般的UIViewController的View是懒加载的。

1.3.2 新建YGTabBarController。

新建一个YGTabBarController类继承自UITabBarController后,然后把之前在AppDelegate内创建与加载子控制器的代码封装在YGTabBarController.m中,并把tabBarVc更改为self。
本小节主要任务就是为了代码的简洁性,抽取两个方法,一个是从ViewDidLoad中抽取设置tabBarController子控件的方法:

-(void)setUpAllChildViewController

因为每加一个子控制器所写的代码都是类似的,所以又在上个方法内又抽取了设置一个子控件的方法。因为每个子控制器都要设置标题,颜色,图片以及其他属性,所以把这些属性都设置为参数传进到方法内。这里提醒一下,如果参数中会传入中文的话,一般把此参数放到最后,例如title参数。

- (void)setUpOneChildViewController:(UIViewController *)vc image:(UIImage *)image selectedImage:(UIImage *)selectedImage title:(NSString *)title

下面就要讲如何设置tabBar按钮上面的文字与图片了。
首先tabBar上的按钮是由对应的子控制器的tabBarItem属性决定。导入素材中tabBar文件夹拖入到项目中。设定按钮title和image。在ios7之后,默认会把UITabBar上面的按钮图片渲染成蓝色,但大多情况下蓝色不是我们想要的颜色,我们想要让tabBar用美工设计好的图片颜色。更改的方法可以是,如图片所示, 把每张带有selected字符后缀的图片的Render As 属性设置为Original Image。

这里写图片描述

或者用代码更改,在这里我们就写一个UIImage的分类——UIImage+Image.h/.m。目的是为了让以后也方便设置图片渲染。代码如下:

#import "UIImage+image.h"

@implementation UIImage (image)

+ (instancetype)imageWithOriginalName:(NSString *)imageName
{
    UIImage *image = [UIImage imageNamed:imageName];

    return [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
}

@end

这里对上面的代码做下说明:为什么要使用工厂方法(返回值为instancetype)?因为其默认识别是哪个类调用此方法,并返回该类的对象。

然后在setUpOneChildViewController方法里设置按钮标题等内容。代码如下:

 #pragma mark - 添加一个子控制器
- (void)setUpOneChildViewController:(UIViewController *)vc image:(UIImage *)image selectedImage:(UIImage *)selectedImage title:(NSString *)title
{
    vc.tabBarItem.title = title;
    vc.tabBarItem.image = image;
    vc.tabBarItem.badgeValue = @"10";
    vc.tabBarItem.selectedImage = selectedImage;
    [self addChildViewController:vc];
}

但是在设置字体颜色的时候你会发现,不能用self.tabBarItem.textColor 来设置标题颜色,因为UITabBarItem是模型而不是控件,模型是用来保存数据以决定tabBar上按钮的内容。只有控件才有textColor属性,例如UILabel。所以通过观察UITabBarItem的头文件以及其父类的头文件,发现可以用setTitleTextAttributes 方法 也就是富文本来设置标题颜色。富文本不仅可以设置控件的文字颜色,也可以设置字体空心,阴影,图文混排等。通过UIKit框架内的NSAttributesString.h头文件,可以查找到设置富文本字典等key。这里我们采用全局方法+ initialize 来设置颜色。但在+load方法内也可以。但是两者的调用事件不同,+initialize方法是在第一次使用这个类或者子类的时候调用。+load方法是在程序一启动的时候就调用,然后把所有的类加载到内存,且先于main函数执行。
这里写图片描述

initialize 代码如下:

+ (void)initialize
{
    // 这行代码是获取所有的tabBarItem外观标识,这样就容易把不希望改动到外观也更改了
    //  UITabBarItem *item = [UITabBarItem appearance];
    //一般采用带 self的方法,这里self就是指 YGTabBarController

    // 获取当前这个类下面的所有tabBarItem
    UITabBarItem *item = [UITabBarItem appearanceWhenContainedIn:self, nil];
    NSMutableDictionary *att = [NSMutableDictionary dictionary];

    //这里也可以用[att setObject:[UIColor orangeColor] forKey:NSForegroundColorAttributeName];
    //来设置富文本字典,两种方法都可以。
    att[NSForegroundColorAttributeName] = [UIColor orangeColor];

    [item setTitleTextAttributes:att forState:UIControlStateSelected];
}

1.4 修改tabBar内部结构

在观察实际微博界面会发现,tabBar中间有个用来发微博的加号按钮。这就需要首先调整系统tabBar 结构,改变子控件位置,然后在中间加上加号按钮 。系统的tabBar按钮位置是不能改动的,所以智能自定义一个YGTabBar,继承自UITabBar。然后把系统TabBar替换成自定义的YGTabBar。代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    //添加子控制器
    [self setUpAllChildViewController];

    // 自定义tabBar
    YGTabBar *tabBar = [[YGTabBar alloc] initWithFrame:self.tabBar.frame];

    // 利用KVC更改readonly的属性
    [self setValue:tabBar forKeyPath:@"tabBar"];
}

代码说明: 这里有两点需要注意,一要判断在哪个方法里去替换系统tabBar。经过测试发现,系统tabBar上的button是在执行完ViewDidLoad 和ViewWillAppear方法后才添加上去的。如下图所示:

这里写图片描述

从上如可以看到没给方法执行的顺序,所以我们要在添加tabBarButton前替换系统tabBar,这样的话,tabBarButton就会添加到自定义的YZTabBar上面。

第二个问题就是,但是这样赋值self.tabBar == tabBar 会出现错误。因为tabBar是readOnly属性,不能这样赋值。但是我们可以使用KVC赋值,如上面代码所示。

替换完系统tabBar之后,就要重写YGTabBar.m 内的 layOutSubviews方法,来计算每个item的位置。在重写之前先添加一个发微博按钮。代码如下:

- (UIButton *)plusButton
{
    if (_plusButton == nil) {

        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setImage:[UIImage imageNamed:@"tabbar_compose_icon_add"] forState:UIControlStateNormal];
        [btn setImage:[UIImage imageNamed:@"tabbar_compose_background_icon_add"] forState:UIControlStateHighlighted];
        [btn setBackgroundImage:[UIImage imageNamed:@"tabbar_compose_button"] forState:UIControlStateNormal];
        [btn setBackgroundImage:[UIImage imageNamed:@"tabbar_compose_button_highlighted"] forState:UIControlStateHighlighted];

        // 默认按钮的尺寸跟背景图片一样大
        // sizeToFit:默认会根据按钮的背景图片或者image和文字计算出按钮的最合适的尺寸
        [btn sizeToFit];

        _plusButton = btn;

        [self addSubview:_plusButton];

    }
    return _plusButton;
}

重写layoutsubviews方法,调整子控件位置,代码如下:

- (void)layoutSubviews
{

    [super layoutSubviews];
    CGFloat w = self.bounds.size.width;
    CGFloat h = self.bounds.size.height;

    CGFloat btnX = 0;
    CGFloat btnY = 0;
    CGFloat btnW = w / (self.items.count + 1);
    CGFloat btnH = self.bounds.size.height;
    int i = 0;
    // 调整系统自带的tabBar上的按钮位置
    for (UIView *tabBarButton in self.subviews) {
        // 判断下是否是UITabBarButton
        if ([tabBarButton isKindOfClass:NSClassFromString(@"UITabBarButton" )]) {
            if (i == 2) {
                i = 3;
            }
            btnX = i * btnW;
            tabBarButton.frame = CGRectMake(btnX, btnY, btnW, btnH);
            i++;
        } 
    }
    // 设置添加按钮的位置
    self.plusButton.center = CGPointMake(w * 0.5, h * 0.5);   
}

代码说明:在取出tabBar每个button子控件时,需要判断类,可以利用NSClassFromString来根据一个字符串得出其类名。在interface 内添加一个UIButton 属性,并且懒加载,利用sizeToFit来默认按钮尺寸和背景图片一样大。但是我们发现tabBar上的UIBadgeView (如图所示)是继承自UIView的,是系统自带的,没法通过设置图片的方法更改。下一个微博将会讲到如何设置自定义的badgeView。
这里写图片描述

1.5 划分结构

YGTabbar上现在有五个模块,每个模块都有自己的功能,如果像我们这样直接创建每个模块的ViewController,就不能重写每个控制器的viewDidLoad方法,也就没法添加每个模块的各种功能了。这就需要我们为每个模块创建各自的MVC文件,划分结构如图所示:

这里写图片描述

2016-06-08 16:33:09 offbye 阅读数 4910

近期开始研究Facebook f8app项目,目标是理解Facebook官方React Native f8app的整体技术架构,给公司目前几个的React Native项目开发提供官方经验借鉴,并对原生开发和React Native开发进行框架层面的融合。
本文分析f8app iOS代码的结构和技术实现,阅读本文的前提是对React Native和iOS开发有一定的了解。
f8app ios项目使用了CocosPod管理模块,现在RN的最新版本创建的项目默认已经不再使用CocosPods了,直接通过工程引用。f8app还是用了CocosPod,因此我们首先需要在ios目录下运行pod install,安装好依赖的项目,然后用Xcode打开F8v2.xcworkspace工作空间,注意不是打开F8v2.xcodeproj工程文件,我经常犯这个错误,实在不喜欢用CocosPods啊。

iOS f8app效果展示

先看下效果吧,在iOS模拟器上动效还是很好的。

f8appiOS

ios工程结构

首先还是先看下ios工程的结构:

.
├── Default-568h@2x.png
├── F8Scrolling.h
├── F8Scrolling.m
├── F8v2
│   ├── AppDelegate.h
│   ├── AppDelegate.m
│   ├── Base.lproj
│   │   └── LaunchScreen.xib
│   ├── Images.xcassets
│   │   ├── AppIcon.appiconset
│   │   │   ├── AppIcon@2x.png
│   │   │   ├── AppIcon@3x.png
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── Info.plist
│   └── main.m
├── F8v2.xcodeproj
├── F8v2.xcworkspace
├── F8v2Tests
│   └── Info.plist
├── PodFile
├── Podfile.lock
├── Pods
│   ├── Bolts
│   │   ├── Bolts
│   │   │   ├── Common
│   │   │   └── iOS
│   │   ├── LICENSE
│   │   └── README.md
│   ├── FBSDKCoreKit
│   │   ├── FBSDKCoreKit
│   │   │   └── FBSDKCoreKit
│   │   ├── LICENSE
│   │   └── README.mdown
│   ├── FBSDKLoginKit
│   │   ├── FBSDKLoginKit
│   │   │   └── FBSDKLoginKit
│   │   ├── LICENSE
│   │   └── README.mdown
│   ├── FBSDKShareKit
│   │   ├── FBSDKShareKit
│   │   │   └── FBSDKShareKit
│   │   ├── LICENSE
│   │   └── README.mdown
│   ├── Headers
│   │   ├── Private
│   │   │   ├── Bolts
│   │   │   ├── CodePush
│   │   │   ├── FBSDKCoreKit
│   │   │   ├── FBSDKLoginKit
│   │   │   ├── FBSDKShareKit
│   │   │   ├── React
│   │   │   ├── react-native-fbsdkcore
│   │   │   ├── react-native-fbsdklogin
│   │   │   └── react-native-fbsdkshare
│   │   └── Public
│   │       ├── Bolts
│   │       ├── CodePush
│   │       ├── FBSDKCoreKit
│   │       ├── FBSDKLoginKit
│   │       ├── FBSDKShareKit
│   │       ├── React
│   │       ├── react-native-fbsdkcore
│   │       ├── react-native-fbsdklogin
│   │       └── react-native-fbsdkshare
│   ├── Local\ Podspecs
│   │   ├── CodePush.podspec.json
│   │   ├── React.podspec.json
│   │   ├── react-native-fbsdkcore.podspec.json
│   │   ├── react-native-fbsdklogin.podspec.json
│   │   └── react-native-fbsdkshare.podspec.json
│   ├── Manifest.lock
│   ├── Pods.xcodeproj
│   │   ├── project.pbxproj
│   │   ├── xcshareddata
│   │   │   └── xcschemes
│   └── Target\ Support\ Files
│       ├── Bolts
│       │   ├── Bolts-dummy.m
│       │   ├── Bolts-prefix.pch
│       │   └── Bolts.xcconfig
│       ├── CodePush
│       │   ├── CodePush-dummy.m
│       │   ├── CodePush-prefix.pch
│       │   └── CodePush.xcconfig
│       ├── FBSDKCoreKit
│       │   ├── FBSDKCoreKit-dummy.m
│       │   ├── FBSDKCoreKit-prefix.pch
│       │   └── FBSDKCoreKit.xcconfig
│       ├── FBSDKLoginKit
│       │   ├── FBSDKLoginKit-dummy.m
│       │   ├── FBSDKLoginKit-prefix.pch
│       │   └── FBSDKLoginKit.xcconfig
│       ├── FBSDKShareKit
│       │   ├── FBSDKShareKit-dummy.m
│       │   ├── FBSDKShareKit-prefix.pch
│       │   └── FBSDKShareKit.xcconfig
│       ├── Pods-F8v2
│       │   ├── Pods-F8v2-acknowledgements.markdown
│       │   ├── Pods-F8v2-acknowledgements.plist
│       │   ├── Pods-F8v2-dummy.m
│       │   ├── Pods-F8v2-frameworks.sh
│       │   ├── Pods-F8v2-resources.sh
│       │   ├── Pods-F8v2.debug.xcconfig
│       │   └── Pods-F8v2.release.xcconfig
│       ├── React
│       │   ├── React-dummy.m
│       │   ├── React-prefix.pch
│       │   └── React.xcconfig
│       ├── react-native-fbsdkcore
│       │   ├── react-native-fbsdkcore-dummy.m
│       │   ├── react-native-fbsdkcore-prefix.pch
│       │   └── react-native-fbsdkcore.xcconfig
│       ├── react-native-fbsdklogin
│       │   ├── react-native-fbsdklogin-dummy.m
│       │   ├── react-native-fbsdklogin-prefix.pch
│       │   └── react-native-fbsdklogin.xcconfig
│       └── react-native-fbsdkshare
│           ├── react-native-fbsdkshare-dummy.m
│           ├── react-native-fbsdkshare-prefix.pch
│           └── react-native-fbsdkshare.xcconfig
├── Settings.bundle
│   ├── About.plist
│   ├── Root.plist
│   └── en.lproj
│       └── Root.strings
├── Splash@2x.png
└── build

PodFile文件分析

PodFile是CocosPod的配置文件,是ruby语言写的,定义了用到的第三方模块,和一些处理过程。

source 'https://github.com/CocoaPods/Specs.git'

target 'F8v2' do
  pod 'React', :subspecs => [
    'Core',
    'RCTActionSheet',
    'RCTImage',
    'RCTNetwork',
    'RCTText',
    'RCTWebSocket',
    'RCTPushNotification',
    'RCTLinkingIOS',
    'RCTVibration',
  ], :path => '../node_modules/react-native'
  pod 'react-native-fbsdkcore', :path => '../node_modules/react-native-fbsdk/iOS/core'
  pod 'react-native-fbsdklogin', :path => '../node_modules/react-native-fbsdk/iOS/login'
  pod 'react-native-fbsdkshare', :path => '../node_modules/react-native-fbsdk/iOS/share'
  pod 'CodePush', :path => '../node_modules/react-native-code-push'
end

# Start the React Native JS packager server when running the project in Xcode.

start_packager = %q(
if nc -w 5 -z localhost 8081 ; then
  if ! curl -s "http://localhost:8081/status" | grep -q "packager-status:running" ; then
    echo "Port 8081 already in use, packager is either not running or not running correctly"
    exit 2
  fi
else
  open $SRCROOT/../../node_modules/react-native/packager/launchPackager.command || echo "Can't start packager automatically"
fi
)

post_install do |installer|
  target = installer.pods_project.targets.select{|t| 'React' == t.name}.first
  phase = target.new_shell_script_build_phase('Run Script')
  phase.shell_script = start_packager
end

通过CocosPod引入了React,react-native-fbsdkcore,react-native-fbsdklogin,react-native-fbsdkshare,CodePush这些模块,
最后会尝试启动React Native packager。

入口类AppDelegate.m代码分析

从项目的入口类AppDelegate.m看起,

#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <CodePush/CodePush.h>

#import "AppDelegate.h"

#import "RCTRootView.h"
#import "RCTPushNotificationManager.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURL *jsCodeLocation;

#ifdef DEBUG
  NSString *ip = [[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ip" ofType:@"txt"] encoding:NSUTF8StringEncoding error:nil] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\n"]];

  if (!ip) {
    ip = @"127.0.0.1";
  }

  jsCodeLocation = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:8081/index.ios.bundle?platform=ios&dev=true", ip]];
#else
  jsCodeLocation = [CodePush bundleURL];
#endif
  NSLog(jsCodeLocation.absoluteString);

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"F8v2"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];

  NSArray *objects = [[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil];
  UIImageView *loadingView = [[[objects objectAtIndex:0] subviews] objectAtIndex:0];
  loadingView = [[UIImageView alloc] initWithImage:[loadingView image]];
  loadingView.frame = [UIScreen mainScreen].bounds;

  rootView.loadingView = loadingView;

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [[UIViewController alloc] init];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [[UIApplication sharedApplication] setStatusBarHidden:NO];
  [self.window makeKeyAndVisible];

  return YES;
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
  [FBSDKAppEvents activateApp];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
  return [[FBSDKApplicationDelegate sharedInstance] application:application
                                                        openURL:url
                                              sourceApplication:sourceApplication
                                                     annotation:annotation];
}

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
  [RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings];
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
  [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
{
  [RCTPushNotificationManager didReceiveRemoteNotification:notification];
}

@end

可以看到主要用了FBSDKCoreKit,CodePush热更新,RCTPushNotificationManager推送。
首先创建了RCTRootView *rootView,RCTRootView就是RN页面的容器,我们只要在iOS ViewController中添加RCTRootView就可以展示RN的页面了。
然后从LaunchScreen.xib中取出一个子View作为rootView的加载页面loadingView,设置view的frame,创建一个rootViewController,并把它的view设置成RCTRootView *rootView,然后把UIWindow的rootViewController设成刚才创建的rootViewController,这些代码还是很简单的。
didRegisterUserNotificationSettings,didRegisterForRemoteNotificationsWithDeviceToken,didReceiveRemoteNotification几个方法是对推送通知的处理,也比较简单。

F8Scrolling.m滚动UI组件代码分析

然后看下F8Scrolling.m,这个是RN的滚动UI组件,在f8app/js/common/ListContainer.js中用到了这个组件的js代码。我们看看代码里做了哪些事情:

#import <UIKit/UIKit.h>
#import <CoreGraphics/CoreGraphics.h>

#import "F8Scrolling.h"
#import "RCTUIManager.h"
#import "RCTScrollView.h"

@interface F8Scrolling () {
  NSMapTable *_pinnedViews;
  NSMapTable *_distances;
}

@end

@implementation F8Scrolling

@synthesize bridge = _bridge;

RCT_EXPORT_MODULE()

- (instancetype)init
{
  if (self = [super init]) {
    _pinnedViews = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableWeakMemory capacity:20];
    _distances = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:20];
  }
  return self;
}

- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

RCT_EXPORT_METHOD(pin:(nonnull NSNumber *)scrollViewReactTag
                  toView:(nonnull NSNumber *)pinnedViewReactTag
                  withDistance:(nonnull NSNumber *)distance)
{
  UIView *pinnedView = [self.bridge.uiManager viewForReactTag:pinnedViewReactTag];
  UIView *scrollView = [self.bridge.uiManager viewForReactTag:scrollViewReactTag];
  if ([scrollView isKindOfClass:[RCTScrollView class]]) {
    RCTScrollView *reactScrollView = (RCTScrollView *)scrollView;
    [_pinnedViews setObject:pinnedView forKey:reactScrollView.scrollView];
    [_distances setObject:distance forKey:reactScrollView.scrollView];
    [reactScrollView setNativeScrollDelegate:self];
    [self scrollViewDidScroll:reactScrollView.scrollView];
  }
}

RCT_EXPORT_METHOD(unpin:(nonnull NSNumber *)scrollViewReactTag)
{
  UIView *scrollView = [self.bridge.uiManager viewForReactTag:scrollViewReactTag];
  if ([scrollView isKindOfClass:[RCTScrollView class]]) {
    RCTScrollView *reactScrollView = (RCTScrollView *)scrollView;
    [_pinnedViews removeObjectForKey:reactScrollView.scrollView];
    [_distances removeObjectForKey:reactScrollView.scrollView];
    [reactScrollView setNativeScrollDelegate:nil];
  }
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
  UIView *pinnedView = [_pinnedViews objectForKey:scrollView];
  if (!pinnedView) {
    return;
  }

  CGFloat distance = [[_distances objectForKey:scrollView] doubleValue];
  CGFloat y = MAX(0, distance - scrollView.contentOffset.y);
  pinnedView.transform = CGAffineTransformMakeTranslation(0, y);
}

@end

首先是类定义,oc里面是用@interface定义类的,这个和Java的interface很不一样,protocol对应Java的接口interface。
@interface F8Scrolling : NSObject <RCTBridgeModule, UIScrollViewDelegate>
init构造函数初始化了_pinnedViews和_distances2个变量,都是NSMapTable类型,NSMapTable(顾名思义)更适合于一般意义的映射。这取决于它是如何构造的,NSMapTable可以处理的“key-to-object”样式映射的NSDictionary,但它也可以处理“object-to-object”的映射 - 也被称为“associative array”或简称为“map”。_pinnedViews的key和value都是weak引用,_distances的key是weak引用,value是强引用。

@synthesize bridge=_bridge;意思是说,bridge 属性为 _bridge 实例变量合成访问器方法。
也就是说,bridge属性生成存取方法是setBridge,这个setWindow方法就是_bridge变量的存取方法,它操作的就是_bridge这个变量。通过这个看似是赋值的这样一个操作,我们可以在@synthesize 中定义与变量名不相同的getter和setter的命名,籍此来保护变量不会被不恰当的访问。

methodQueue返回了main_queue,规定这个组件运行在UI线程,因为它是UI组件啊
然后是几个方法的定义,RCT_EXPORT_METHOD宏提供导出方法到js的能力,可以用RCT_REMAP_METHOD重新定义在js中的函数名,还可以让js方法异步返回Promise,下面是它的定义

/**
 * Wrap the parameter line of your method implementation with this macro to
 * expose it to JS. By default the exposed method will match the first part of
 * the Objective-C method selector name (up to the first colon). Use
 * RCT_REMAP_METHOD to specify the JS name of the method.
 *
 * For example, in ModuleName.m:
 *
 * - (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b
 * { ... }
 *
 * becomes
 *
 * RCT_EXPORT_METHOD(doSomething:(NSString *)aString
 *                   withA:(NSInteger)a
 *                   andB:(NSInteger)b)
 * { ... }
 *
 * and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`.
 *
 * ## Promises
 *
 * Bridge modules can also define methods that are exported to JavaScript as
 * methods that return a Promise, and are compatible with JS async functions.
 *
 * Declare the last two parameters of your native method to be a resolver block
 * and a rejecter block. The resolver block must precede the rejecter block.
 *
 * For example:
 *
 * RCT_EXPORT_METHOD(doSomethingAsync:(NSString *)aString
 *                           resolver:(RCTPromiseResolveBlock)resolve
 *                           rejecter:(RCTPromiseRejectBlock)reject
 * { ... }
 *
 * Calling `NativeModules.ModuleName.doSomethingAsync(aString)` from
 * JavaScript will return a promise that is resolved or rejected when your
 * native method implementation calls the respective block.
 *
 */
#define RCT_EXPORT_METHOD(method) \
  RCT_REMAP_METHOD(, method)

方法pin从名字就可以知道,功能是固定view的。通过self.bridge.uiManager viewForReactTag方法获取到view。
方法scrollViewDidScroll最后定义了pinnedView的transform动画效果,pinnedView.transform = CGAffineTransformMakeTranslation(0, y);

F8v2目录下的代码文件基本上就介绍完了,Info.plist文件定义了项目的一些基本属性,包括CodePush key等的自定义属性。

总结

f8app iOS的代码量还是比较少的,本文主要分析了AppDelegate.m和 F8Scrolling UI组件的代码。项目还用了BVLinearGradient渐变UI组件,通过工程引用的,代码也比较简单。另外还通过CocosPod引入了React,react-native-fbsdkcore,react-native-fbsdklogin,react-native-fbsdkshare,CodePush这些模块,可以参考ios/PodFile。

[本文独立博客地址](http://www.offbye.com

2019-08-23 17:31:25 HD6870 阅读数 42

1、本地化实现ios项目的APP桌面多国语言名称,以中、日、韩、英四国语言为例,默认项目是英文。建好项目后先点PROJECT这里面的项目名,再点下面Localizations选项里的Language,添加中日韩三国语言。弹出窗口全部勾选。

2、再新建一个Resorce里面的Strings文件,

3、保存的文件名一定要叫 InfoPlist.strings 。

4、然后点 InfoPlist.strings 文件,在右边的Locallzation下面点Localize,弹出窗口选择默认的English,再点Localize按钮。

5、这时候Locallzation下面多了个框,里面显示English已经选了,把剩下中日韩三国语言也都勾上。

6、勾完之后刚好对应左边的InfoPlist.strings项目的中日韩语言分类。

7、英语这里写 "CFBundleName" = "Tim Cook";   苹果CEO库克。

8、中文这里写 "CFBundleName" = "姚明";

日问这里写 "CFBundleName" = "はなざわ かな";  花泽香菜的日文名。

韩文这里写 "CFBundleName" = "최성국";  金馆长,崔成国的韩文名。

9、然后运行。手机语言是中文,就显示姚明。

10、把手机改成韩文系统,就显示 金馆长!

iOS App Thinning

阅读数 599

没有更多推荐了,返回首页