Xcode插件开发打断点
好多人说Xcode插件开发不能打断点,因为一旦Run了就安装了,其实是错误的.
可以通过设置
- 选择Xcode为可执行文件
Edit Scheme-Run-Info-Executable-Other-Xcode.app
- 关闭当前界面调试
取消 Edit Scheme-Run-Options-Enable user interface debugging
此时就可以正常打断点了
UIViewController是利用UIViewControllerAnimatedTransitioning协议把两个ViewController的View放在一个容器Container里进行UIView动画
补充:
转场动画实现需要用到2个协议
转场动画调用需要1个协议,三选一
根据VC出现的方法不同,需要选用不同的代理(3选1)来调用动画,常见的VC切换方法无非就是Present,NavigationPush,TabBarSwitch三种方式.三种方式的代理中,都通过返回一个遵循了UIViewControllerAnimatedTransitioning协议的指针来调用动画.
我们暂且称遵循了UIViewControllerAnimatedTransitioning协议的Object为动画控制器(AnimationController).
@interface ViewController () <UIViewControllerTransitioningDelegate>
@end
- (void)presentViewController {
ViewController *next = [[ViewController alloc]init];
next.transitioningDelegate = self;
[self presentViewController:next animated:YES completion:nil];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return AnimationController;
}
@interface ViewController () <UINavigationControllerDelegate>
@end
- (void)pushViewController {
self.navigationController.delegate = self;
ViewController *next = [[ViewController alloc]init];
[self.navigationController pushViewController:next animated:YES];
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return AnimationController;
}
@interface ViewController () <UITabBarControllerDelegate>
@end
- (void)switchViewController {
self.tabBarController.delegate = self;
}
- (id<UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return AnimationController;
}
AnimationController是任何一个实现了UIViewControllerAnimatedTransitioning代理方法的类
可以充当AnimationController的候选:
一般都使用第一种,2/3/4仅做讨论研究
为了防止出现各种意外情况,大多数都使用新建一个继承于NSObject的Class,然后用它来完成AnimationController
// AnimationController.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface AnimationController : NSObject <UIViewControllerAnimatedTransitioning>
@end
// AnimationController.m
#import "AnimationController.h"
@implementation AnimationController
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 2.0f;//动画时间
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
//动画效果
.......
}
@end
此时加载头文件,然后返回一个实例化的对象即可
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return [[AnimationController alloc]init];
}
关于AnimationController更多的讨论可以看文章末尾的[谁可以当AnimationController](## 扩展研究)
在完成AnimationController的构造之后,动画效果的具体实现是通过函数
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext;
完成的.
这里我们回顾开篇的话UIViewController是利用UIViewControllerAnimatedTransitioning协议把两个ViewController的View放在一个容器Container里进行UIView动画,具体怎么实现呢?可以看到函数的入参类型是一个遵循UIViewControllerContextTransitioning协议的Object,我们打开这个协议的头文件
UIKIT_EXTERN NSString *const UITransitionContextFromViewControllerKey NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UITransitionContextToViewControllerKey NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UITransitionContextFromViewKey NS_AVAILABLE_IOS(8_0);
UIKIT_EXTERN NSString *const UITransitionContextToViewKey NS_AVAILABLE_IOS(8_0);
开头迎面而来的是4个Key,这4个Key是用来取转场中两个ViewController或者他们的View的,我们拿个简单的效果举例
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
//1. 通过viewControllerForKey函数和Key值得到toViewController
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//2. 通过finalFrameForViewController函数获得到动画结束后toViewController的Frame
CGRect finalRect = [transitionContext finalFrameForViewController:toVC];
//3. 把toViewController的View加入到需要做动画的容器中,容器通过containerView函数获取
[[transitionContext containerView]addSubview:toVC.view];
//4. 动画开始的状态
toVC.view.frame = CGRectOffset(finalRect, 0, [[UIScreen mainScreen]bounds].size.height);
//5. 使用UIView层面上的动画,让toViewController.view和fromViewController.view做动画
//这里基本就到本质了,实质上是通过Context画布(元数据),让还没有DidAppear的ViewController的View传递过来,然后做两个View层上的动画
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
//6. 动画结束的状态
toVC.view.frame = finalRect;
} completion:^(BOOL finished) {
//7. 重要!!这里一定要声明转场动画已经结束了!!
[transitionContext completeTransition:YES];
}];
}
因为在转场动画进行的过程中,为了防止动画终端,此时用的是画布Context生成的View,整个过程中,App是无法进行响应Respond的,所以如果不进行completeTransition的声明,整个程序就无法响应任何事件,动画结束就进入卡死状态了.
当我们需要转场时,需要先找到一个遵循了Present/Navigation/TabBar对应协议的转场动画协议进行实现,然后返回一个遵循了UIViewControllerAnimatedTransitioning的AnimationController,在AnimationController实现的协议中,使用UIViewControllerContextTransitioning协议对动画进行具体实现
TabBar除了使用AnimationController实现转场之外,还可以使用另外一种方式进行动画转场,看TabBarController的头文件中
NS_CLASS_AVAILABLE_IOS(2_0) @interface UITabBarController : UIViewController <UITabBarDelegate, NSCoding>
@property(nonatomic,readonly) UITabBar *tabBar NS_AVAILABLE_IOS(3_0);
其本身是遵循UITabBarDelegate协议的,并且具有tabBar变量.那么我们可以实现UITabBarDelegate协议中的
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item;
// called when a new view is selected by the user (but not programatically)
这个函数,来拦截转换的过程并加入Layer层动画
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
CATransition* animation = [CATransition animation];
[animation setDuration:0.5f];
[animation setType:kCATransitionFade];
[animation setSubtype:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
[[self.view layer]addAnimation:animation forKey:@"switchView"];
}
在该函数和AnimationController动画同时存在时,会优先响应该函数,不执行AnimationController,所以在改动旧代码的时候需要检查一下,不然会造成新加入的动画无效.
如果仅仅是在某个ViewController中Present出另一个,可以把其本身当作AnimationController.
@interface ViewController () <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning>
@end
@implementation ViewController
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return self;
}
@end
在使用了Navigation的情况下,可以使用某一个栈中的ViewController作为AnimationController,但是不能使用Navigation本身.
这种情形下可以
@interface ViewController () <UINavigationControllerDelegate, UIViewControllerAnimatedTransitioning>
@end
@implementation ViewController
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return self;
}
@end
这种情形下不行,原因可能是由于self.navigationController.delegate = self相当于NavigationController中self.delegate = self,原因不确定,总之会Crash
//ViewController.h
@interface ViewController () <UINavigationControllerDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.delegate = self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return (NavViewController *)self.navigationController;
}
@end
//一个自定义的继承于UINavigationController的Navigation
@interface NavViewController : UINavigationController<UIViewControllerAnimatedTransitioning>
@end
与Navigation不同的是,如果才用TabBar本身作为AnimationController就不会Crash
//ViewController.h
@interface ViewController () <UITabBarControllerDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tabBarController.delegate = self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return (TabViewController *)self.tabBarController;
}
@end
//一个自定义的继承于UITabBarController的TabBar
@interface TabViewController : UITabBarController <UIViewControllerAnimatedTransitioning>
@end
而且如果始终TabBar中某一个ViewController作为AnimationController自然也可以.
@interface ViewController () <UITabBarControllerDelegate, UIViewControllerAnimatedTransitioning>
@end
@implementation ViewController
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return self;
}
@end
[1]http://kittenyang.com/uiviewcontrollertransitioning/
[2]http://objccn.io/issue-12-3/
先祭上一张大图,说明两种常见动画的层次关系
第一种就是常见的我们经常使用的UIView的闭包动画,记得使用这个动画,内部的两个控件要在同一个View上,不然会Crash.
//UIView层面的自定义动画1
[UIView animateWithDuration:0.5 animations:^{ }];
第二种是提交动画,由两句话控制,类似C序言的过程语言
//UIView层面的系统动画2
[UIView beginAnimations:@"animationID" context:nil];//动画开始
//设置动画属性开始
[UIView setAnimationDuration:0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:customView cache:YES];
//设置动画属性结束
[UIView commitAnimations];//动画提交
这里会用到几个概念
在设置好了以上数据后,就可以通过commitAnimations让动画开始执行.但是和方法1很大的不同是,这样的动画设置可以同时提交好多个,而且有系统的做好的动画效果.
有一个函数**setAnimationTransition:forView:cache:**是专门来控制转场效果,官方提供的转场是个枚举,而且这个函数只能在每个动画中有效一次.
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
//.h
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;
// current limitation - only one per begin/commit block
先来看下官方文档和头文件
You can nest sets of animations (by calling this method again before committing a previous set of animations) as needed.
Nesting animations groups them together and allows you to set different animation options for the nested group.
If you install a start or stop selector using the setAnimationWillStartSelector: or setAnimationDidStopSelector: method,
the values you specify for the animationID and context parameters are passed to your selectors at runtime.
You can use these parameters to pass additional information to those selectors.
你可以通过begin/commit执行多个动画,但是头文件中说了,这两个必须成对出现
+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context;
// additional context info passed to will start/did stop selectors. begin/commit can be nested
+ (void)commitAnimations;
// starts up any animations when the top level animation is commited必须在上一个动画commit后才行
而动画的拦截方法就是**setAnimationWillStartSelector:和setAnimationDidStopSelector:**两个代理方法,可以在里面获取到ID和context.
在Layer层的动画的上有很多,可以参考官方文档Core Animation Reference Collection.这里通过添加CATransition对象来举例添加动画
CATransition *animation = [CATransition animation];
animation.duration = 0.5f; //动画时长
animation.type = @"cube"; //过度效果
animation.subtype = @"formLeft"; //过渡方向
[self.view.layer addAnimation:animation forKey:@"animationID"];
CATransition本身分为type和subType两种,是通过字符串来传递属性的
/* The name of the transition. Current legal transition types include
* `fade', `moveIn', `push' and `reveal'. Defaults to `fade'. */
@property(copy) NSString *type;
/* An optional subtype for the transition. E.g. used to specify the
* transition direction for motion-based transitions, in which case
* the legal values are `fromLeft', `fromRight', `fromTop' and
* `fromBottom'. */
@property(nullable, copy) NSString *subtype;
根据官方文档查询他们的常量字符串是
//type
NSString * const kCATransitionFade;
NSString * const kCATransitionMoveIn;
NSString * const kCATransitionPush;
NSString * const kCATransitionReveal;
//subType
NSString * const kCATransitionFromRight;
NSString * const kCATransitionFromLeft;
NSString * const kCATransitionFromTop;
NSString * const kCATransitionFromBottom;
但是不知道为什么网上流传着一份全部的动画效果,可以通过字符串调用实现,比如例子中的”cube”动画就不在官方文档里,但是确实可以实现.
/* 过渡效果
fade //交叉淡化过渡(不支持过渡方向)
push //新视图把旧视图推出去
moveIn //新视图移到旧视图上面
reveal //将旧视图移开,显示下面的新视图
cube //立方体翻滚效果
oglFlip //上下左右翻转效果
suckEffect //收缩效果,如一块布被抽走(不支持过渡方向)
rippleEffect //滴水效果(不支持过渡方向)
pageCurl //向上翻页效果
pageUnCurl //向下翻页效果
cameraIrisHollowOpen //相机镜头打开效果(不支持过渡方向)
cameraIrisHollowClose //相机镜头关上效果(不支持过渡方向)
*/
/* 过渡方向
fromRight;
fromLeft;
fromTop;
fromBottom;
*/
把一个Image放在一个View的Layer上来放大的时候,如果用UIView来做,图片不会太多的失真和闪烁的效果,但是用Core Animation来做失真和闪烁现象会很严重,效果很不好。为了解决这个问题,只要把各自的动画放在一起就可以了.
[imageLayer addAnimation:[self animationOpen] forKey:@"Open"];
[UIView beginAnimations:@"zoom out" context:nil];
[UIView setAnimationDuration:1.f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
cover.transform = CGAffineTransformMakeScale(5.5,5.5);
cover.center = CGPointMake(629, 384);
[UIView commitAnimations];
- (CAAnimation *)animationOpen
{
CABasicAnimation *animationOpen
= [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
animationOpen.duration = 1;
animationOpen.autoreverses = NO;
animationOpen.delegate = self; //然后执行真正地打开书的内容
animationOpen.removedOnCompletion = NO;
animationOpen.fillMode = kCAFillModeForwards;
animationOpen.fromValue = [NSNumber numberWithFloat:-M_PI/5];
animationOpen.toValue = [NSNumber numberWithFloat:-M_PI/1.5];
return animationOpen;
}
我们公司用了一个坑坑的密码控件TextFiled,此时需要一个6位的支付密码框,由于密码控件是封装起来的,我们改变不了,于是我们决定使用折中方案
提出这个方案的是刘岩姐,于是便有了利用Core Graghics绘图的过程,绘图是通过重写自定义View的**drawRect:**方法
static const CGFloat cornerRadius = 8.0;
@interface PayPGTextFieldView () <OnCharDelegate, PGTextFieldDelegate>
@property (nonatomic, weak) UIView *contentView;
@property (nonatomic, strong) NSMutableArray *pointArray;
@end
#pragma mark - Draw
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
// Drawing code
CGFloat width = rect.size.width;
CGFloat height = rect.size.height;
//计算图标
CGFloat singleWidth = width/_textField.m_iMaxLen;
CGFloat everyWidth = singleWidth / 3;
CGFloat everyHeight = everyWidth;
//通过画布绘图
CGContextRef context = UIGraphicsGetCurrentContext();//获取当前画布
CGContextSetStrokeColorWithColor(context, LINE_COLOR.CGColor);//设定Stroke的颜色
CGContextSetLineWidth(context, 0.5);//设置划线的宽度
for(int idx = 1; idx < _textField.m_iMaxLen; idx++)
{
CGPoint sourcePoint = CGPointMake(singleWidth * idx, 0);
CGPoint targetPoint = CGPointMake(singleWidth * idx, height);
CGContextMoveToPoint(context, sourcePoint.x, sourcePoint.y);//画笔移动到起始点
CGContextAddLineToPoint(context, targetPoint.x, targetPoint.y);//画笔画到结束点
}
CGContextStrokePath(context);//把画笔画过的地方着色(Stroke)
//根据重绘的画布设置小圆点的Frame
for (int i = 0; i < 6; ++i) {
UIView *view = _pointArray[i];
[view setFrame:CGRectMake(i *singleWidth + everyWidth, (height - everyHeight) / 2, everyWidth, everyHeight)];
view.layer.cornerRadius = everyWidth / 2;
view.hidden = YES;
}
}
//Get the CGContext from this view
CGContextRef context = UIGraphicsGetCurrentContext();
//Draw a rectangle
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
//Define a rectangle
CGContextAddRect(context, CGRectMake(10.0, 150.0, 60.0, 120.0)); //X, Y, Width, Height
//Draw it
CGContextFillPath(context);
我们先来盗一张图
这张图是苹果官方文档给出的动画机制的层次结构,可见Core Graphics是在动画层之下,和OpenGL同层,听上去吊吊哒.
所以说Core Graphics里的绘图是是画布层面上,直接操作的像素点.会比动画更早响应,这是是为什么如果重写drawRect:之后不设置背景色,除了被Stroke的部分都是黑色,因为没有像素(0,0,0)嘛.
根据参考文档描述,动画和视图的渲染有一个单独的进程,在iOS5以前这个进程叫SpringBoard,在iOS6之后叫BackBoard。我们以下统一称为render server.
iOS设备的屏幕刷新频率是是60HZ。如果上面的这些步骤在一个刷新周期之内无法做完(1/60s),就会造成掉帧。
在苹果的2012年WWDC大会上,引入的一个CALayer内的BOOL属性,来控制是把Layer在一个离屏绘制的Context上计算好生成bitmap再提交,还是实时运算,默认是NO.
如果你有一堆复杂动画的Layer,但是这些Layer自己并不改变,应该设置成YES,如果Layer经常改变,应该设为NO,此时如果设为YES反而会变慢
离屏的绘制一般两种情况:
[1]http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=297
[2]http://stackoverflow.com/questions/19405741/when-should-i-set-layer-shouldrasterize-to-yes
写上传头像的时候用到的需求,有时候图片需要进行调整,如何根据用户的调整进行切图,使用的是Core Graphics的画布.
@interface YZFHeadImageAdjustViewController ()
@property (nonatomic, strong) UIImage *originalImage;
@end
- (UIImage *)clipEditedImage {
//最终切图rect
CGRect subImageRect = CGRectMake(x, y, width, height);
//通过Core Graphic切图
CGImageRef imageRef = _originalImage.CGImage;
CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef, subImageRect);
//获取subImage的大小
CGSize size;
size.width = subImageRect.size.width;
size.height = subImageRect.size.height;
//展开一个和Size大小一样的画布
UIGraphicsBeginImageContext(size);
//获得当前画布
CGContextRef context = UIGraphicsGetCurrentContext();
//在新画布上根据当前画布,画出新画布大小的,subImageRef的内容
CGContextDrawImage(context, subImageRect, subImageRef);
//通过新画布创建新的UIImage
UIImage* subImage = [UIImage imageWithCGImage:subImageRef];
//关闭新画布
UIGraphicsEndImageContext();
//返回小图
return subImage;
}
有些时候,需要在编译完成时自动做一些工作,如果你会写shell脚本(就是命令行脚本),可以添加进某个Target让它自动执行.
Project-Target-Build Phases-"+"-New Run Script Phases
凡是通用的shell命令行都可以用
例如我在文章#使用Bundle打包图片配合静态库使用#中,假设静态库的Project和主工程在一个WorkSpace,不想每次更改Resource里的图片都手动重新拷贝到主工程里(不然主工程里还是旧的图片),就可以通过脚本完成.
脚本1 这个脚本在编译时有效,打包的时候发现拷贝不进去,造成IPA包中无Resource.bundle
cp -R -f $BUILT_PRODUCTS_DIR/Resource.bundle $APP_PRODUCT_CONTENTS_FOLDER_PATH
脚本2 这个脚本在打包时有效,但是在编译时会报错,因为BUILT_PRODUCTS_DIR是个快捷方式(链接),找不到实际对象
cp -R -f $BUILT_PRODUCTS_DIR/Resource.bundle $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/
脚本3 最终求助linux的大哥,搞了个函数,先获取Resource.bundle的实际路径,然后进行拷贝
function getRealPath()
{
[[ `ls -lt $1 | head -1|awk '{print $10}'` == "->" ]] && realPath=`ls -lt $1 | head -1|awk '{print $11}'`|| realPath=$1
echo "$1 the real directory is:$realPath"
}
getRealPath "$BUILT_PRODUCTS_DIR/Resource.bundle"
cp -R -f $realPath $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/
脚本四 后来读了读Pods脚本,发现Pods有更好的RealPath的脚本
realpath() {
DIRECTORY="$(cd "${1%/*}" && pwd)"
FILENAME="${1##*/}"
echo "$DIRECTORY/$FILENAME"
}
ABSOLUTE_XCASSET_FILE=$(realpath "${PODS_ROOT}/$1")
//CategoryA.h
@interface UIView (CategoryA)
+ (UIView *)createLine:(CGRect)frame;
@end
//CategoryB.h
@interface UIView (CategoryB)
+ (UIView *)createLine:(CGRect)frame;
@end
由于Category都是使用UIView调用,写在不同的Category中编译时可能不报错,甚至通过Com+点击定位也正确,但是实际执行中可能想执行B的函数,实际执行的是A的
不同的project都对UIView做了扩展,造成在一个WorkSpace中的时候编译结束造成调用错误,而且很难被发现
由于静态库只能放代码不能带图片资源,有两种解决方案
第三种方法这么坑,万一哪个文件再搞混了….所以推荐用第二种
由于大部分时间引用图片都是用
[UIImage ImageNamed:name];
这种方法无法查到到我们自定义的Bundle里的图片,所以我们要写一个方法从特定的Bundle里读
以下是完成了一个从名称为”Resource.bundle”读取文件的示例,这样给别人静态库的时候需要新建一个为Resource.bundle的Target,添加进去图片,并且与.a一起交付给使用方.
//.h
#import <UIKit/UIKit.h>
@interface UIImage (Resource)
+ (UIImage *)resourceImageNamed:(NSString *)name;
@end
/.m
#import "UIImage+Resource.h"
@implementation UIImage (Resource)
+ (UIImage *)resourceImageNamed:(NSString *)name{
//先从默认目录里读
UIImage *imageFromMainBundle = [UIImage imageNamed:name];
if (imageFromMainBundle) {
return imageFromMainBundle;
}
//读不到再去Bundle里读
//此处Scale是判断图片是@2x还是@3x
NSInteger scale = (NSInteger)[[UIScreen mainScreen] scale];
for (NSInteger i = scale; i >= 1; i--) {
NSString *filepath = [self getImagePath:name scale:i];
UIImage *tempImage = [UIImage imageWithContentsOfFile:filepath];
if (tempImage) {
return tempImage;
}
}
return nil;
}
+ (NSString *)getImagePath:(NSString *)name scale:(NSInteger)scale{
NSURL *bundleUrl = [[NSBundle mainBundle] URLForResource:@"Resource" withExtension:@"bundle"];
NSBundle *customBundle = [NSBundle bundleWithURL:bundleUrl];
NSString *bundlePath = [customBundle bundlePath];
NSString *imgPath = [bundlePath stringByAppendingPathComponent:name];
NSString *pathExtension = [imgPath pathExtension];
//没有后缀加上PNG后缀
if (!pathExtension || pathExtension.length == 0) {
pathExtension = @"png";
}
//Scale是根据屏幕不同选择使用@2x还是@3x的图片
NSString *imageName = nil;
if (scale == 1) {
imageName = [NSString stringWithFormat:@"%@.%@", [[imgPath lastPathComponent] stringByDeletingPathExtension], pathExtension];
}
else {
imageName = [NSString stringWithFormat:@"%@@%ldx.%@", [[imgPath lastPathComponent] stringByDeletingPathExtension], (long)scale, pathExtension];
}
//返回删掉旧名称加上新名称的路径
return [[imgPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:imageName];
}
@end
由于Add Target里iOS下并没有Bundle,所以我们要从OS X创建,然后修改Build Setting两项
这里是指定为iOS的Bundle,否则是OS X用的
这两项一个是OSX下的名字,一个是iOS下的名字,改为NO才可以存图片,不然存进去是tiff
今天接了一个要求,分析找出满足以下条件的Class
原因
问题1解决了一半,问题目前还没找到解决方法
https://github.com/dblock/fui
fui是一个ruby脚本,可以找到某个工程路径下满足以下条件的文件
注意!是未被import不是init,如果另外一个文件import了它,但是没有init,也查不出
gem install fui
cd ~/yourproject
fui find
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true