iOS |知识点整理(6)

419 阅读23分钟

延续上一篇iOS |知识点整理(5)

禁止UIScrollView下拉.

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    if (scrollView.contentOffset.y < 0) {
        [scrollView setContentOffset:CGPointMake(0, 0)];
        scrollView.bounces = NO;
    }
    if (scrollView.contentOffset.y == 0){
        scrollView.bounces = YES;
    }
    else scrollView.bounces = YES;
}

ref:stackoverflow.com/questions/5…

Don’t read self.view in -loadView. Only set it, don’t get it.

The self.view property accessor calls -loadView if the view isn’t currently loaded. There’s your infinite recursion.

ref: stackoverflow.com/questions/5…


呈现视图的顺序

它内部的顺序是这样的,先走init方法,如果有自定义视图,则走自定义视图。如果没有,则走xib或storyborad,如果这个也没有则走loadView方法,loadView方法系统调用,也可以重载它。当我们重载它时,可以[super loadView] 父类帮助我们创建一个空的view,如果不调用父类,那么就要自己创建一个UIView,并且self.view = UIView。

在viewDiload中调用一些网络访问的动作,可以在初始化方法中做一些关于模型数据的准备工作。

如果覆盖了loadView,则必须创建UIViewController的view属性。如果没有覆盖该方法,UIViewController会默认调用initWithNibName方法来初始化并加载view。

-[UIViewController init] just executes [self initWithNibName:nil bundle:nil].

ref:

  1. stackoverflow.com/questions/7…
  2. lcepy.github.io/2015/02/16/…

UIButton touch is delayed when in UIScrollView

  1. Try to set UIScrollView delaysContentTouches property to NO.
  2. Ok I’ve solved this by subclassing UIScrollView and overriding touchesShouldCancelInContentView

Now my UIButton that was tagged as 99 highlights properly and my scrollview is scrolling!

myCustomScrollView.h:

@interface myCustomScrollView : UIScrollView  {

}
@end

and myCustomScrollView.m:

@implementation myCustomScrollView

    - (BOOL)touchesShouldCancelInContentView:(UIView *)view
    {
        NSLog(@"touchesShouldCancelInContentView");

        if (view.tag == 99)
            return NO;
        else 
            return YES;
    }

ref: stackoverflow.com/questions/3…


在interface builder中静态设置UIScrollView中的内容.

  1. 先调整好最上层的view,将最上层view的高度设置成所需要显示内容的总高度,这一步是为方便把所有的内容都呈现出来
  2. 将UIScrollView设置成帖四个边的约束.
  3. 将UISCrollView中contentview的高度约束设置成实际的高度,以及贴四边约束,和UIScrollView等宽约束. 这样在运行时,最上层的view,和UIScrollView都会被设置成实际的尺寸,而其内的contentview为实际高度,这样,就会使它滚动了.

NSCountedSet

  1. NSCountedSet也是不能储存重复的对象的,查看Apple文档中对这个类的描述有这么一句: Each distinct object inserted into an NSCountedSet object has a counter associated with it. 插入NSCountedSet对象的每个不同的对象都有一个与之相关的计数器(google翻译)
  1. 也就是说如果遇到重复对象的加入,这个对象的计数器就会+1。所以可以到这个类有个名叫- (NSUInteger)countForObject:(id)object;的方法来统计重复对象的个数。
NSArray *array = @[@1, @2, @2, @1];
NSCountedSet *set = [[NSCountedSet alloc]initWithArray:array]; 
[set enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
    NSLog(@"%@ => %d", obj, [set countForObject:obj]);
}];
//打印
//2014-05-29 00:35:22.741 CoreData[3235:60b] 1 => 2
//2014-05-29 00:35:22.742 CoreData[3235:60b] 2 => 2

ref: bawn.github.io/ios/2014/05…


关于instancetype关键字

为什么会需要instancetype:

  1. alloc 和 init 的返回类型都是 id ,然而在Xcode中,编译器会检查所有正确类型。它是怎么做到的呢? 在Cocoa中,约定 alloc 或 init 的方法总是返回接收器类实例的对象。据说这些方法有一个相关返回类型。

  2. 虽然类构造方法也是返回 id ,但是类构造方法并没有做同样的类型检查,因为它们不遵循命名规范。

[[[NSArray alloc] init] mediaPlaybackAllowsAirPlay]; //  "No visible @interface for `NSArray` declares the selector `mediaPlaybackAllowsAirPlay`"
[[NSArray array] mediaPlaybackAllowsAirPlay]; // (No error)
  1. instancetype 关键字,它可以表示一个方法的相关返回类型。例如:
@interface Person
+ (instancetype)personWithName:(NSString *)name;
@end

ref: nshipster.cn/instancetyp…


Ios只能解析标准格式的json

iOS只能解析标准格式的json 比如 “key”:”value”这种形式,如果出现不带双引号的key或只有单引号的value,就是解析出错。这点不像安卓,可以强转。


对UIButton同添加点击响应和点击手势时

如果对按钮同时添加UIControlEventTouchUpInside和UITapGestureRecognizer时,那么只有UITapGestureRecognizer会起作用,

这时,如果想让UIControlEventTouchUpInside起作用的话,可进行如下操作.

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    TestButtion * btn=[[UIButton alloc]initWithFrame:CGRectMake(20, 40, 50, 50)];

    [self.view addSubview:btn];

    btn.backgroundColor=[UIColor redColor];
    [btn addTarget:self action:@selector(bthmethod:) forControlEvents:UIControlEventTouchUpInside];

    UITapGestureRecognizer * tap11=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapmethod11:)];
    tap11.delegate=self;
    [btn addGestureRecognizer:tap11];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
        return ![touch.view isKindOfClass:[UIButton class]];
}

-(void)tapmethod11:(UITapGestureRecognizer *)tap

{
    NSLog(@"%@-->TAP",tap);
}

-(void)bthmethod:(UIButton *)btn

{
    NSLog(@"%@-->BTN",btn);
}

在调试的时候显示AlignmentRects.

To do this using the scheme editor in Xcode:

In the Xcode menu Product -> Scheme -> Edit Scheme (or ⌘-<)

In Debug, the Arguments tab, Arguments passed on launch, click the plus button and enter this:

-UIViewShowAlignmentRects YES (for iOS projects) 

or

-NSViewShowAlignmentRects YES (for Mac OS projects)

You’ll need the dash at the start

Tap OK

You should have a bunch of yellow outlines when you run


将自定义的view关联到controller

//in xxxViewController
- (void)loadView{
    CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
    UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];
    contentView.backgroundColor = [UIColor blackColor];
    self.view = contentView;

    levelView = [[LevelView alloc] initWithFrame:applicationFrame viewController:self];
    [self.view addSubview:levelView];
}

如果要添加约束的话,可以在 loadView里,也可以在自定义view的initWithFrame里面.

ref:matthewmorey.com/creating-ui…

[UIView contraints] 返回该UIView持有的constraints

- (void)viewDidLoad {
    [super viewDidLoad];
    UILabel* lab=[[UILabel alloc] init];
    _lbl=lab;
    lab.translatesAutoresizingMaskIntoConstraints=NO;
    lab.text=@"huhuhuhu";
    [self.view addSubview:lab];
    //下面constraints被添加到self.view当中,也就是说held by self.view, 虽然他是与UILabel相关的.  那么self.view.contraints将包含这下面的constraints. 然而,
    //lab.contraints将不包含
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:lab attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:lab attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
}
-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    NSLog(@"%@",_lbl.constraints);
    NSLog(@"=========================");
    NSLog(@"%@",self.view.constraints);
}
输出:

2015-05-02 18:20:05.179 TestConstraints[16843:991717] (
    "<NSContentSizeLayoutConstraint:0x7f8883f378c0 H:[UILabel:0x7f8883e13400'huhuhuhu'(76.5)] Hug:250 CompressionResistance:750>",
    "<NSContentSizeLayoutConstraint:0x7f8883f2da60 V:[UILabel:0x7f8883e13400'huhuhuhu'(20.5)] Hug:250 CompressionResistance:750>"
)
2015-05-02 18:20:05.180 TestConstraints[16843:991717] =========================
2015-05-02 18:20:05.180 TestConstraints[16843:991717] (
    "<_UILayoutSupportConstraint:0x7f8883e0f6a0 V:[_UILayoutGuide:0x7f8883ca5160(20)]>",
    "<_UILayoutSupportConstraint:0x7f8883e12b30 V:|-(0)-[_UILayoutGuide:0x7f8883ca5160]   (Names: '|':UIView:0x7f8883ca4e50 )>",
    "<_UILayoutSupportConstraint:0x7f8883e14e00 V:[_UILayoutGuide:0x7f8883f32e20(0)]>",
    "<_UILayoutSupportConstraint:0x7f8883e16190 _UILayoutGuide:0x7f8883f32e20.bottom == UIView:0x7f8883ca4e50.bottom>",
    "<NSLayoutConstraint:0x7f8883f35700 UILabel:0x7f8883e13400'huhuhuhu'.centerX == UIView:0x7f8883ca4e50.centerX>",
    "<NSLayoutConstraint:0x7f8883ca6370 UILabel:0x7f8883e13400'huhuhuhu'.centerY == UIView:0x7f8883ca4e50.centerY>",
    "<NSLayoutConstraint:0x7f8883f2b090 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7f8883ca4e50(375)]>",
    "<NSLayoutConstraint:0x7f8883f2b0e0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x7f8883ca4e50(667)]>",
    "<NSAutoresizingMaskLayoutConstraint:0x7f8883f36a50 h=-&- v=-&- 'UIView-Encapsulated-Layout-Left' H:|-(0)-[UIView:0x7f8883ca4e50]   (Names: '|':UIWindow:0x7f8883f321c0 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x7f8883f36ae0 h=-&- v=-&- 'UIView-Encapsulated-Layout-Top' V:|-(0)-[UIView:0x7f8883ca4e50]   (Names: '|':UIWindow:0x7f8883f321c0 )>"
)

intrinsicContentSize对约束的影响

- (CGSize)intrinsicContentSize {
 return CGSizeMake(300, 20);
}

UIView base implementation of updateConstraints will call the intrinsicContentSize and it will use the size returned from it to add constraints to AwesomeView.

In the above example of (30a, 20) size, the following constraints will be added:

<NSContentSizeLayoutConstraint:0x7fef48d52580 H:[AwesomeView:0x7fef48ead7f0(300)] Hug:250 CompressionResistance:750>,
<NSContentSizeLayoutConstraint:0x7fef48d4d110 V:[AwesomeView:0x7fef48ead7f0(20)] Hug:250 CompressionResistance:750>

只有定义了intrinsicContentSize, Compression Resistance and Content Hugging才有意义

A UIView Subclass should never add constraints on its size

Every view is responsible about setting the constraints on its superview, however a view should NEVER! set its own constraints, wether these constraints are constraints to self (such as NSLayoutAttributeWidth and NSLayoutAttributeHeight) or they are constraints relative to its super view.

If a view wants to specify its height or width, it should do that by implementing intrinsicContentSize

A UIView Subclass should never add constraints to its superview

For the same reason above, a subview should never add constraints on its superview. The position of subview its a choice of the superview.

update the layout immediately

How do you ask auto layout to update the layout? Call [view setNeedsLayout] if you want auto layout to update the layout on the next turn of the run loop. However, if you want it to update the layout immediately, so you can immediately access the new bounds value later within your current function, or at another point before the turn of the run loop, then you need to call [view setNeedsLayout] and [view layoutIfNeeded] ref:stackoverflow.com/questions/1…


How View Controllers Participate in the View Layout Process

When the size of a view controller’s view changes, its subviews are repositioned to fit the new space available to them. The views in the controller’s view hierarchy perform most of this work themselves through the use of layout constraints and autoresizing masks. However, the view controller is also called at various points so that it can participate in the process. Here’s what happens:

  • The view controller’s view is resized to the new size.

  • If autolayout is not in use, the views are resized according to their autoresizing masks.

  • The view controller’s viewWillLayoutSubviews method is called.

  • The view’s layoutSubviews method is called. If autolayout is used to configure the view hierarchy, it updates the layout constraints by executing the following steps:

    • The view controller’s updateViewConstraints method is called.
    • The UIViewController class’s implementation of the updateViewConstraints method calls the view’s updateConstraints method.
    • After the layout constraints are updated, a new layout is calculated and the views are repositioned.
    • The view controller’s viewDidLayoutSubviews method is called.

    Ideally, the views themselves perform all of the necessary work to reposition themselves, without requiring the view controller to participate in the process at all. Often, you can configure the layout entirely within Interface Builder. However, if the view controller adds and removes views dynamically, a static layout in Interface Builder may not be possible. In this case, the view controller is a good place to control the process, because often the views themselves only have a limited picture of the other views in the scene. Here are the best approaches to this in your view controller: Use layout constraints to automatically position the views (iOS 6 and later). You override updateViewConstraints to add any necessary layout constraints not already configured by the views. Your implementation of this method must call [super updateViewConstraints]. For more information on layout constraints, see Auto Layout Guide. Use a combination of autoresizing masks and code to manually position the views (iOS 5.x). You override layoutSubviews and use it to reposition any views whose positions cannot be set automatically through the use of resizing masks. For more information on the autoresizing properties of views and how they affect the view, see View Programming Guide for iOS.


AutoLayout中的默认宽度问题

H:|-[Find]-[FindNext]-[FindField(>=20)]-|

水平布局,Find距离父view左边缘默认间隔宽度,之后是FindNext距离Find间隔默认宽度;再之后是宽度不小于20的FindField,它和FindNext以及父view右边缘的间距都是默认宽度。(竖线’|‘ 表示superview的边缘) 对于上面所说的父view与子view左边缘默认间隔宽度,以及子view与子view之间的默认间隔宽度,是多少??? 标准宽度

I’ve found the “standard Aqua space” to be 8.0 between sibling views, and 20.0 between a view and its superview.

NSView* view = [NSView new] ;
NSLayoutConstraint* constraintWithStandardConstantBetweenSiblings = [NSLayoutConstraint constraintsWithVisualFormat:@"[view]-[view]"  options:0  metrics:nil  views:NSDictionaryOfVariableBindings(view) ] [0] ;
CGFloat standardConstantBetweenSiblings = constraintWithStandardConstantBetweenSiblings.constant ;    // 8.0

NSView* superview = [NSView new] ;
[superview addSubview:view] ;
NSLayoutConstraint* constraintWithStandardConstantBetweenSuperview = [NSLayoutConstraint constraintsWithVisualFormat:@"[view]-|"  options:0  metrics:nil  views:NSDictionaryOfVariableBindings(view) ] [0] ;
CGFloat standardConstantBetweenSuperview = constraintWithStandardConstantBetweenSuperview.constant ;    // 20.0

图片引用:

developer.apple.com/library/ios…

ref: stackoverflow.com/questions/1…

NSDitionaryOfVariableBindings生成字典

UIButton *cancelButton = … 
UIButton *acceptButton = … 
viewsDictionary = NSDitionaryOfVariableBindings(cancelButton,acceptButton); 

生成的字典为

{ acceptButton = “”; cancelButton = “”; }

高级自动布局笔记

在视图被显示之前,自动布局引入了两个额外的步骤:更新约束和布局视图。每一步都是基于前一步操作的;显示基于布局视图,布局视图基于更新约束。

  • 第一步:更新约束,可以被认为是一个“计量传递”。这发生于自下而上(从子视图到父视图),并准备设置视图frame所需要的布局信息。你可以通过调用setNeedsUpdateConstraints来触发这个传递,同时,你对约束条件系统做出的任何改变都将自动触发这个方法。无论如何,通知自动布局关于自定义视图中任何可能影响布局的改变是非常有用的。谈到自定义视图,你可以在这个阶段重写updateConstraints来为你的视图增加需要的本地约束
  • 第二步:布局,发生于自上而下(从父视图到子视图)。这种布局传递实际上是通过设置frame(在OS X中)或者center和bounds(在iOS中)将约束条件系统的解决方案应用到视图上。你可以通过调用setNeedsLayout来触发这个传递,这并不会立刻应用布局,而是注意你稍后的请求。因为所有的布局请求将会被合并到一个布局传递,所以你不需要为经常调用这个方法而困扰。

你可以调用layoutIfNeeded/layoutSubtreeIfNeeded(iOS/OS X)来强制系统立即更新视图树的布局。如果你下一步操作依赖于更新后视图的frame,这将非常有用。在你自定义的视图中,你可以重写layoutSubviews/layout来获得控制布局变化的所有权。

基于约束条件的布局是一个迭代的过程

需要牢记的是,这三步并不是单向的。基于约束条件的布局是一个迭代的过程,布局传递可以基于前一个布局方案做出更改,这将再次接着另一个布局传递后触发更新约束条件。这可以被用来创建高级的自定义视图布局,但是如果你每一次调用自定义layoutSubviews都会导致另一个布局传递,那么你将会陷入一个无限循环中。

固有内容大小(Intrinsic Content Size )

  • 固有内容大小是一个视图期望为其显示特定内容得到的大小。比如,UILabel有一个基于字体的首选高度,一个基于字体和显示文本的首选宽度。一个UIProgressView仅有一个基于其插图的首选高度,但没有首选宽度。
  • 一个没有格式的UIView既没有首选宽度也没有首选高度。 如果你自定义的视图有一个固有内容大小,你必须决定,根据内容来显示,而且你需要指定这个大小。
  • 为了在自定义视图中实现固有内容大小,你需要做两件事:

重写 intrinsicContentSize为内容返回恰当的大小,无论何时有任何会影响固有内容大小的改变发生时,调用invalidateIntrinsicContentSize。如果这个视图只有一个方向的尺寸设置了固有大小,那么为另一个方向的尺寸返回UIViewNoIntrinsicMetric/NSViewNoIntrinsicMetric。 需要注意的是,固有内容大小必须是独立于视图frame的。例如,不可能返回一个基于frame特定高宽比的固有内容大小。


Compression Resistance and Content Hugging

每个视图在两个方向上都分配有内容压缩阻力优先级和内容吸附性优先级。只有当视图定义了固有内容大小时这些属性才能起作用,如果没有定义内容大小,那就没发阻止被压缩或者吸附了

在后台中,固有内容大小和这些优先值被转换为约束条件。一个固有内容大小为{100,30}的label,水平/垂直压缩阻力优先值为750,水平/垂直的内容吸附性优先值为250,这四个约束条件将会生成:

H:[label(<=100@250)]
H:[label(>=100@750)]
V:[label(<=30@250)]
V:[label(>=30@750)]

关于iOS UIImage渲染模式 imageWithRenderingMode

设置UIImage的渲染模式:UIImage.renderingMode

着色(Tint Color)是iOS7界面中的一个设置UIImage的渲染模式,你可以设置一个UIImage在渲染时是否使用当前视图的Tint Color。UIImage新增了一个只读属性:renderingMode,对应的还有一个新增方法:imageWithRenderingMode:,它使用UIImageRenderingMode枚举值来设置图片的renderingMode属性。该枚举中包含下列值:

UIImageRenderingModeAutomatic  // 根据图片的使用环境和所处的绘图上下文自动调整渲染模式。
UIImageRenderingModeAlwaysOriginal   // 始终绘制图片原始状态,不使用Tint Color。
UIImageRenderingModeAlwaysTemplate   // 始终根据Tint Color绘制图片,忽略图片的颜色信息。

比如说我们设置这么一句话:

childVc.tabBarItem.selectedImage= [[UIImageimageNamed:selectImage]imageWithRenderingMode:
UIImageRenderingModeAlwaysOriginal];

设置与不设置的区别:

设置时:

DD95E549-1FB3-4EBF-B75F-BA2A55BD2232.jpg

未设置时:

6418B334-5C5E-4B41-8FDD-FE631E13280A.jpg

我们发现图片的原始状态是橙色,设置渲染时始终绘制图片的原始状态就是上面的效果了。而下面蓝色的效果是apple原来就有的


关于UIAppearance

iOS applies appearance changes when a view enters a window, it doesn’t change the appearance of a view that’s already in a window. To change the appearance of a view that’s currently in a window, remove the view from the view hierarchy and then put it back. 用法:

[[UINavigationBar appearance] setBarTintColor:myNavBarBackgroundColor];
[[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], nil] setBackgroundImage:myNavBarButtonBackgroundImage forState:state barMetrics:metrics];

One gotcha is that UIAppearance swizzles all setters that have a default apperance, and tracks when they get changed, so that UIAppearance doesn’t override your customizations. This is a problem here, since we use setters in the initializer, and for UIAppearance it now looks as though we already customized the class ourselves. Lesson: Only use direct ivar access in the initializer for properties that comply to UI_APPEARANCE_SELECTOR:

在init方法当中,不能通过直接使用self. 来调用UI_APPEARANCE_SELECTOR标记的方法. 因为如果这样做的话,就相当于自己单独设置了,而UIAppearance不会覆盖自己设置.

ref: UIAppearance for Custom Views

关于maskView和AutoLayout

maskView只有在布局完成时设置才有效果. As Zev explained, the mask view lives outside of the ordinary view hierarchy and so can't be used together with Auto Layout. I got around this by placing it manually in my view controller's viewDidLayoutSubviews:

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];
    CGRect viewToMaskRect = self.viewToMask.bounds;
    CGRect maskRect = CGRectMake(viewToMaskRect.origin.x + 50.0, viewToMaskRect.origin.y + 50.0, 100.0, 100.0);
    [self.theMask setFrame:maskRect];
    [self.viewToMask setMaskView:self.theMask];
}

关于NSLocalizedString

The default macros for getting localized strings look for the strings files (aka “string tables”) in the main app bundle. These are the definitions of the 4 default macros and you can see that the bottom two have a way to specify the bundle to get the strings from whereas the first two hardcode the mainBundle.

#define NSLocalizedString(key, comment) \
            [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]
#define NSLocalizedStringFromTable(key, tbl, comment) \
            [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)]
#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
            [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
#define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) \
            [bundle localizedStringForKey:(key) value:(val) table:(tbl)]

You can create your own custom macro or category or what-have-you to have a shortcut, but to promote understanding here’s the entire code that we need to first get an NSBundle instance from our resource bundle and then get one string from it. Note that specifying a table of nil means that the strings file is called “Localizable.strings”, for a table name of “Name” the file is called “Name.strings”

// get the resource bundle
NSString *resourceBundlePath = [[NSBundle mainBundle] pathForResource:@"DTPinLockController" ofType:@"bundle"];
NSBundle *resourceBundle = [NSBundle bundleWithPath:resourceBundlePath];
 
// get a string
NSString *string = NSLocalizedStringFromTableInBundle(@"Set Passcode", @"DTPinLockController", resourceBundle, @"PinLock");

We probably want to have a category on NSBundle specific to our project to load and cache in a static variable the resource bundle. And to go with that a localized string macro that hard codes this resource bundle.

[NSBundle mainBundle] 并不一定返回应用程序的主bundle. 比如IB可能是在一个helpler app中运行的. 一个解决办法就是使用

+[NSBundle bundleForClass:]来获取应用程序的主bundle.

Why? [NSBundle mainBundle] returns the primary bundle of the currently running app. When you call that from a framework, you're getting a different bundle returned based on which app is loading your framework. When you run your app, your app loads the framework. When you use the control in IB, a special Xcode helper app loads the framework. Even if your IB-designable control is in your app target, Xcode is creating a special helper app to run the control inside of IB.

The solution? Call +[NSBundle bundleForClass:] instead (or NSBundle(forClass:) in Swift). This gets you the bundle containing the executable code for whichever class you specify. (You can use[self class]/self.dynamicType there, but beware the result will change for subclasses defined in different bundles.)


关于后台线程中进行异步操作.

If you do decide that your best option is to execute the drawing code in the background, the solution is quite simple. Take the code in your drawRect: method and put it in an operation. Then replace the original view with an image view that gets updated once the operation has completed. In your drawing method, use UIGraphicsBeginImageContextWithOptions instead of UIGraphicsGetCurrentContext:

UIGraphicsBeginImageContextWithOptions(size, NO, 0);
// drawing code here
UIImage *i = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return i;

By passing in 0 as the third parameter, the scale of the device’s main screen will be automatically filled in, and the image will look great on both retina and non-retina devices. If you do custom drawing in table view or collection view cells, it makes sense to put all that into operation subclasses. You can add them to a background operation queue, and cancel them when the user scrolls cells out of bounds from the didEndDisplayingCell delegate method.

UIKit calls -drawRect: on the main queue • You can draw an image for your view on another queue • Drawing APIs are safe to use from any queue… if they begin and end in the same operation! • Must call -[UIImageView setImage:] on the main queue

将UIImage转换成CGImage时会丢失scale属性,使用CGImageGetWidth/Height时得到的是像素尺寸。 需要注意scale的地方

  1. CALayer的contentsScale默认为1.0,只有在使用Core Graphics在drawRect:中自定义绘图时系统才会根据当前屏幕的情况设置,因此以下情况需要设置合适的contentsScale

    • 直接设置CALayer的contents时。
    • 创建新的CALayer时。例如,CATextLayer在Retina屏幕时如果不设置contentsScale,所显示的文字就会模糊。
  2. 在使用Image Context时需要注意:UIGraphicsBeginImageContext以1.0的比例系数创建Bitmap,所以当屏幕为Retina时,在渲染时可能会显得模糊。要创建比例系数为其它值的图片,需要使用 UIGraphicsBeginImageContextWithOptions

ref:理解contentsScale

关于CAShaperLayer的Rect问题

CAShapeLayer *_chartLine = [CAShapeLayer layer];
_chartLine.backgroundColor=[UIColor redColor].CGColor;
_chartLine.lineCap = kCALineCapRound;
_chartLine.lineJoin = kCALineJoinBevel;
_chartLine.fillColor   = [[UIColor redColor] CGColor];
_chartLine.lineWidth   = 2.0;
_chartLine.path = progressline.CGPath;
_chartLine.strokeStart = 0;
_chartLine.strokeEnd   = 1.0;
[self.layer addSublayer:_chartLine];

可以发现,设置的backgroundColor并不起作用,原因是_chartLine的rect大小一直为零. 而上面的绘制的路径是可以正常的显示的 其实它是显示在_chartLine rect之个的,如果设置_chartLine的masksToBounds为YES的,就会发现连路径也无法正常绘制了.

关于旋转的判断

249AA00A-0DDD-4B8E-9070-B9ABEAE67517.png

可见使用左手法则,就可以判断 github.com/AttackOnDob…

将旋转中心点的transform

static UIImage *frameImage(CGSize size, CGFloat radians) {
    UIGraphicsBeginImageContextWithOptions(size, YES, 1); {
        [[UIColor whiteColor] setFill];
        UIRectFill(CGRectInfinite);
        CGContextRef gc = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(gc, size.width / 2, size.height / 2);   //坐标系移动
        CGContextRotateCTM(gc, radians);    //坐标系旋转
        CGContextTranslateCTM(gc, size.width / 4, 0);   //坐标系再移动,沿旋转后的X轴方向.
        [[UIColor redColor] setFill];
        CGFloat w = size.width / 10;
        CGContextFillEllipseInRect(gc, CGRectMake(-w / 2, -w / 2, w, w));
    }
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

将旋转中心点的transform

Layer的旋转是基于其anchor point,而Path的旋转是基于其origin point. 如果想要基于

将多个图形当作一个

UIGraphicsBeginImageContextWithOptions(CGSizeMake(320, 320),NO ,[UIScreen mainScreen].scale);
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGFloat wd = 250;
CGFloat ht = 250;
CGSize myShadowOffset = CGSizeMake (5, -5);
CGContextSetShadow (myContext, myShadowOffset, 3);
CGContextBeginTransparencyLayer (myContext, NULL);
CGContextSetRGBFillColor (myContext, 0, 1, 0, 1);
CGContextFillRect (myContext, CGRectMake (wd/3+ 20,ht/2 ,wd/4,ht/4));
CGContextSetRGBFillColor (myContext, 0, 0, 1, 1);
CGContextFillRect (myContext, CGRectMake (wd/3-20,ht/2-50,wd/4,ht/4));
CGContextSetRGBFillColor (myContext, 1, 0, 0, 1);
CGContextFillRect (myContext, CGRectMake (wd/3,ht/2-20,wd/4,ht/4));
CGContextEndTransparencyLayer (myContext);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
self.imageView.image = image;
UIGraphicsEndImageContext();

5FAAADA3-0DA2-4AB9-840F-A6BF7525B262.png Transparency Layers · Sunny

Blend Modes

我们在使用Quartz 2D画图的时候,经常遇到图形叠加的情况。在多个图形重叠的时候有时候我们想重叠的部分透明阿,或者重叠的部分颜色混合在一起阿。这时候就要用到 Quartz 2D 的混合模式了 Blend Modes。通过 Blend Modes 我们可以把几个图片组合起来绘制到已经有图形的 graphic context上。 How to use Blend Modes

  1. 首先先画背景。
  2. 使用 CGContextSetBlendMode 设置 Blend Modes
  3. 在画我们想和背景图形合成的图片 用 CGContextDrawImage 绘制。其实不只是图片。接下来绘制的无论是图片 矩形 文字都会使用 Blend Modes 和背景组合在一起。
UIImage *foregroundImage = [UIImage imageNamed:@"abc.jpg"];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(320, 320),NO ,[UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat y = 0;
CGFloat height = 50 ;
for (int i = 0; i<5; i++) {
    y  += height ;
    CGRect rect = CGRectMake(0, y, 320, height);
    UIColor *color = [self colorWihtIndex:i];
    CGContextSetFillColorWithColor(context, color.CGColor);
    CGContextFillRect(context, rect);
    CGContextSaveGState(context);
}

CGContextScaleCTM(context, 1.0f, -1.0f);
CGContextTranslateCTM(context, 0.0f, -320);

CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, CGRectMake(0, 50, 320, 240), foregroundImage.CGImage);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
self.imageView.image = image;
UIGraphicsEndImageContext();


-(UIColor *)colorWihtIndex:(NSInteger) index{
    if (index == 0)
        return [[UIColor yellowColor] colorWithAlphaComponent:0.8];
    else if (index== 1)
        return [[UIColor purpleColor] colorWithAlphaComponent:0.8];
    else if (index== 2)
        return [[UIColor redColor]colorWithAlphaComponent:0.8];
    else if (index== 3)
        return [[UIColor greenColor] colorWithAlphaComponent:0.8];
    else if (index== 4)
        return [[UIColor blueColor] colorWithAlphaComponent:0.8];
    return [UIColor purpleColor];
}

ref:Blend Modes · Sunny

关于CGPathCreateCopyByStrokingPath

63E29F6F-0D23-4737-A96E-0BEFF286723C.png

CGMutablePathRef arc = CGPathCreateMutable();
CGPathMoveToPoint(arc, NULL, startPoint.x, startPoint.y);
CGPathAddArc(arc, NULL, centerPoint.x, centerPoint.y, radius, startAngle, endAngle, YES);

下面我要得到以10point粗细的线条,来绘制上面的曲线时. 得到的CGPath Then when you have that path (the single arc) you can create the new segment by stroking it with a certain width. The resulting path is going to have the two straight lines and the two arcs. The stroke happens from the center an equal distance inwards and outwards.

CGFloat lineWidth = 10.0;
CGPathRef strokedArc =
    CGPathCreateCopyByStrokingPath(arc, NULL,
                                   lineWidth,
                                   kCGLineCapButt,
                                   kCGLineJoinMiter, // the default
                                   10); // 10 is default miter limit

ref: Draw segments from a circle or donut

添加内部阴影

@implementation TView
// Only override drawRect: if you perform custom drawing.
- (void)drawRect:(CGRect)rect {
    CGContextRef ref=UIGraphicsGetCurrentContext();
    UIBezierPath* path=[UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width/2.0f, self.bounds.size.height/2.0f) radius:self.bounds.size.height/2.0f startAngle:-M_PI endAngle:0 clockwise:YES];
    [self drawInnerShadowInContext:ref withPath:path.CGPath shadowColor:[UIColor lightGrayColor].CGColor offset:CGSizeMake(1, 1) blurRadius:10];
}
- (void)drawInnerShadowInContext:(CGContextRef)context
                        withPath:(CGPathRef)path
                     shadowColor:(CGColorRef)shadowColor
                          offset:(CGSize)offset
                      blurRadius:(CGFloat)blurRadius {

    CGContextSaveGState(context);
    CGContextAddPath(context, path);
    CGContextClip(context);
    CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);
    CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));

   //在透明的layer上才会blend
    CGContextBeginTransparencyLayer(context, NULL);
    CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
   //下面kCGBlendModeSourceOut规则,是指在叠加的时候 destination pixel= source pixel*(1-source alpha)
    CGContextSetBlendMode(context, kCGBlendModeSourceOut);
    CGContextSetFillColorWithColor(context, opaqueShadowColor);
    CGContextAddPath(context, path);
    CGContextFillPath(context);
    CGContextEndTransparencyLayer(context);

    CGContextRestoreGState(context);
    CGColorRelease(opaqueShadowColor);
}
@end

1B59FCE4-0E91-443D-88F1-C8463D09D3E6.png

ref Demystifying Inner Shadows in Quartz — Helftone

添加内部阴影其它方法

- (void)viewDidLoad {
    [super viewDidLoad];
    //这里的获取context时,opaque为NO.因为生成的图片要覆盖在imageView,所以要设置为生成透明图片.
    UIGraphicsBeginImageContextWithOptions(self.imageView.frame.size,NO ,[UIScreen mainScreen].scale);
    CGContextRef context = UIGraphicsGetCurrentContext();

    UIColor* shadow = [UIColor greenColor];
    CGFloat shadowBlurRadius = 45;

    UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height)];
    [[UIColor clearColor] setFill];
    [rectanglePath fill];
    //生成在图片外部边框的Path
    CGRect rectangleBorderRect = CGRectInset([rectanglePath bounds], -shadowBlurRadius, -shadowBlurRadius);

    //获取外部轮廓的Path,其实就是上面的外部边框的Path
    rectangleBorderRect = CGRectInset(CGRectUnion(rectangleBorderRect, [rectanglePath bounds]), -1, -1);

    //这里添加图片的Path
    UIBezierPath* rectangleNegativePath = [UIBezierPath bezierPathWithRect: rectangleBorderRect];
    [rectangleNegativePath appendPath: rectanglePath];

    //注意这里的填充规则是重要的,设置下面的填充规则,就可以只填充上面两个路径中间的部分
    rectangleNegativePath.usesEvenOddFillRule = YES;
    //下面的代码开始生成shadow
    CGContextSaveGState(context);
    CGFloat xOffset = round(rectangleBorderRect.size.width);

    //注意这里shadow的偏移,与下面的transform相适应,这样操作主要是因为要生成shadow,得有填充操作. 进行下面的操作,可以让填充不影响到图片.
    CGContextSetShadowWithColor(context,
                                CGSizeMake(xOffset + copysign(0.1, xOffset),  copysign(0.1, 0)),
                                shadowBlurRadius,
                                shadow.CGColor);
    [rectanglePath addClip];
    CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(rectangleBorderRect.size.width), 0);
    [rectangleNegativePath applyTransform: transform];
    [[UIColor grayColor] setFill];
    [rectangleNegativePath fill];
    CGContextRestoreGState(context);

    [[UIColor clearColor] setStroke];
    rectanglePath.lineWidth = 1;
    [rectanglePath stroke];
    UIImage *shadowImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    //将生成的layer放在图片上,就生成的内部阴影的效果
    CALayer *shadowLayer = [CALayer layer];
    shadowLayer.frame = CGRectMake(0, 0, self.imageView.frame.size.width,   self.imageView.frame.size.height);
    shadowLayer.contents = (__bridge id)(shadowImage.CGImage);
    [self.imageView.layer addSublayer:shadowLayer];
}

CAKeyframeAnimation

任何动画要表现出运动或者变化,至少需要两个不同的关键状态,而中间的状态的变化可以通过插值计算完成,从而形成补间动画,表示关键状态的帧叫做关键帧.

65cc0af7gw1dxlv01a1jmj.jpg

CABasicAnimation其实可以看作一种特殊的关键帧动画,只有头尾两个关键帧.CAKeyframeAnimation则可以支持任意多个关键帧,关键帧有两种方式来指定,使用path或者使用values,path是一个CGPathRef的值,且path只能对CALayer的 anchorPoint 和 position 属性起作用,且设置了path之后values就不再起效了.而values则更加灵活.

keyTimes这个可选参数可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的.

还可以通过设置可选参数timingFunctions(CAKeyframeAnimation中timingFunction是无效的)为关键帧之间的过渡设置timingFunction,如果values有n个元素,那么timingFunctions则应该有n-1个.但很多时候并不需要timingFunctions,因为已经设置了够多的关键帧了,比如没1/60秒就设置了一个关键帧,那么帧率将达到60FPS,完全不需要相邻两帧的过渡效果(当然也有可能某两帧 值相距较大,可以使用均匀变化或者增加帧率,比如每0.01秒设置一个关键帧).

在关键帧动画中还有一个非常重要的参数,那便是calculationMode,计算模式.其主要针对的是每一帧的内容为一个座标点的情况,也就是对anchorPoint 和 position 进行的动画.当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算.

calculationMode目前提供如下几种模式 kCAAnimationLinear

kCAAnimationDiscrete
kCAAnimationPaced
kCAAnimationCubic
kCAAnimationCubicPaced
kCAAnimationLinear calculationMode的默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算;
kCAAnimationDiscrete 离散的,就是不进行插值计算,所有关键帧直接逐个进行显示;
kCAAnimationPaced 使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效;
kCAAnimationCubic 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,对于曲线的形状还可以通过tensionValues,continuityValues,biasValues来进行调整自定义,这里的数学原理是Kochanek–Bartels spline,这里的主要目的是使得运行的轨迹变得圆滑;
kCAAnimationCubicPaced 看这个名字就知道和kCAAnimationCubic有一定联系,其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的.

即刻收藏动画原理

846E3327-82CA-46C8-AFA2-E74EC2016A64.gif

实现这个效果需要三个元素

  1. 先对整个view添加一个心形的maskLayer,这样整个view就在一个心形的可视范围内了self.maskLayer.contents = (id)[_maskImage CGImage];
  2. 底层一个镂空的 ImageView 用于显示爱心边框
  3. 最上层需要一个用于显示填充动画的View,这样填充完的时候,就看不到边框了. 这里要注意的一点是,对view添加maskLayer的时候,那么view中的子layer,也都在maskLayer的影响下

multiple animations

  1. The important part to take away is that when a property of a stand-alone layer changes, an animation object is added to the layer automatically.

  2. As we all know, views don’t implicitly animate. This is because the view returns NSNull when asked to provide an action (except when inside of an animation block).

  3. An animation is only canceled if another animation is added for the same key.

  4. If there are two (or more) animations for the same property, the first will apply its intermediate value and the next will apply its intermediate value on top of that, overwriting the first value.

  5. 先显示动画,后隐式动画,This means that in the beginning of the animation, the explicit animation sets its intermediate values and the implicit animation overwrites it with its own intermediate values.

  6. 另外一种阻止隐式动画与显示动画复合的方法: As pointed out on twitter, another way to achieve the correct behavior is to update the model first and use the key path as the key when adding the explicit animation. This works because the implicit animations already uses the key paths when added to the layer, meaning that the explicit animation cancel out the implicit animation. 引用:ronnqvi.st/multiple-an…


CABasicAnimation does not animate correctly when I update model layer

关于additive动画

  1. additive动画中的fromValue和toValue, 动画的行为变为: 当前状态+fromValue 结束状态变为了:当前状态+toValue

  2. 下面动画的效果,使用addtive动画,使得动画的值为变化量.

heartLoop.gif

let followHeartShape = CAKeyframeAnimation(keyPath: "position")
followHeartShape.additive = true
followHeartShape.path     = heartPath
followHeartShape.duration = 5
followHeartShape.repeatCount     = HUGE
followHeartShape.calculationMode = "paced"

let circleAround = CAKeyframeAnimation(keyPath: "position")
circleAround.additive = true
circleAround.path     = circlePath
circleAround.duration = 0.275
circleAround.repeatCount     = HUGE
circleAround.calculationMode = "paced"

layer.addAnimation(followHeartShape, forKey: "follow a heart shape")
layer.addAnimation(circleAround,     forKey: "loop around")
  1. 关于additive动画的一个技巧: 将fromValue设置为旧状态值与新状态值的差, 然后先将model值变为新状态值. 然后将toValue设置为0. This works by updating the model value and creating additive animations that animate to zero from minus the difference between the new and old values. What makes this so powerful is that if the model value changes during the animation, the first animation can continue until it’s contribution becomes zero while a new, similarly constructed additive animation is added on top of it.

引用:ronnqvi.st/multiple-an…


隐式动画与显示动画的复合

CATransform3D updatedTransform = [self newTransformWithCurrentTransform];
// Location 1
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
transformAnimation.duration = 1;
transformAnimation.fromValue = [NSValue valueWithCATransform3D:self.layerBeingAnimated.transform]; // Does not work without this.
transformAnimation.toValue = [NSValue valueWithCATransform3D:updatedTransform];
// Location 2
[self.layerBeingAnimated addAnimation:transformAnimation forKey:kTransformAnimationKey];
// Location 3

将self.layerBeingAnimated.transform = updatedTransform;分别放在Location1,2,3的区别如下:
放在Location1:
注意使用fromValue和toValue时的影响.
注意三个位置隐式动画与用户动画的添加先后顺序,对整体动画效果的影响. 同时还要注意,隐匿动画和用户添加的动画,都是 "设值” 动画.注意与additive动画的区别.

详细查看:CABasicAnimation does not animate correctly when I update model layer


隐式动画

Core Animation在每个run loop周期中自动开始一次新的事务(run loop是iOS负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的东西),即使你不显式的用[CATransaction begin]开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。

明白这些之后,我们就可以轻松修改变色动画的时间了。我们当然可以用当前事务的+setAnimationDuration:方法来修改动画时间,但在这里我们首先起一个新的事务,于是修改时间就不会有别的副作用。因为修改当前事务的时间可能会导致同一时刻别的动画(如屏幕旋转),所以最好还是在调整动画之前压入一个新的事务。

修改后的代码见清单7.2。运行程序,你会发现色块颜色比之前变得更慢了。

清单7.2 使用CATransaction控制动画时间

- (IBAction)changeColor
{
    //begin a new transaction
    [CATransaction begin];
    //set the animation duration to 1 second
    [CATransaction setAnimationDuration:1.0];
    //randomize the layer background color
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    //commit the transaction
    [CATransaction commit];
}

当然返回nil并不是禁用隐式动画唯一的办法,CATransaction有个方法叫做+setDisableActions:,可以用来对所有属性打开或者关闭隐式动画。如果在清单7.2的[CATransaction begin]之后添加下面的代码,同样也会阻止动画的发生:

[CATransaction setDisableActions:YES];

总结一下,我们知道了如下几点

  • UIView关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖-actionForLayer:forKey:方法,或者直接创建一个显式动画(具体细节见第八章)。
  • 对于单独存在的图层,我们可以通过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画。

[007]AttackOnDobby/iOS-Core-Animation-Advanced-Techniques

简化动画代码的编写

1.书写比较形象的动画代码:

[UIView animateWithDuration:0.5
             animations:^{
                 myView.center = newPosition;
             }];

与之对应的代码:

CABasicAnimation *move = [CABasicAnimation animationWithKeyPath:@"position"];
move.duration = 0.5;
move.fromValue = [NSValue valueWithCGPoint:myView.center];
myView.center = newPosition;  //改变model layer的状态.
[myView.layer addAnimation:move forKey:@"move my view"];

2.使用keyframe动画:

CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
rotate.fromValue = @0;
rotate.toValue   = @(M_PI);
rotate.duration  = 1.5;
[myLayer addAnimation:rotate forKey:@"rotate around Y"];

与之对应的keyframe动画代码:

CAKeyframeAnimation *rotate = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.y"];
rotate.values   = @[@0, @(M_PI)];
rotate.keyTimes = @[@0, @1];
rotate.duration = 1.5;
[myLayer addAnimation:rotate forKey:@"rotate around Y"];

引用:ronnqvi.st/clear-anima…


view大小对矩阵变换影响

_tview的frame {{0,0}{200,200}}

分别应用下面的变换1:

CGAffineTransform t=CGAffineTransformIdentity;
t=CGAffineTransformScale(t, 0.8, 0.8);
t=CGAffineTransformTranslate(t, 0, 100);
_tview.transform=t;
NSLog(@"%@",NSStringFromCGRect(_tview.frame));
输出:  {{20, 100}, {160, 160}}

变换2:

CGAffineTransform t=CGAffineTransformIdentity;
t=CGAffineTransformTranslate(t, 0, 100);
t=CGAffineTransformScale(t, 0.8, 0.8);
_tview.transform=t;
NSLog(@"%@",NSStringFromCGRect(_tview.frame));
输出: {{20, 120}, {160, 160}}

IOS中使用的是行向量,右乘

矩阵运算的顺序

先对坐标系进行t变换,得到坐标系t, 然后再对t进行scale变换 CATransform3DScale(t,sx,sy,sz) 注意下面的是左乘.

%---K-VCQ9DABCFM5KBW11H.jpg

先对坐标系进行b变换,得到坐标系b, 然后再对b进行a变换 CATransform3DConcat(a,b) 注意下面是的右乘.

--ZWZ_JV5W84ZI-X~W-ZK4S.jpg

关于view矩阵变换的origin

-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
    UIView* view=[[UIView alloc] init];
    CGRect frame=CGRectMake(10, 10, 100, 100);
    view.frame=frame;
    [self.view addSubview:view];
    CGAffineTransform t1 = CGAffineTransformMakeTranslation(0, 100);
    CGAffineTransform t2 = CGAffineTransformMakeScale(.8, .8);
    CGAffineTransform t3 = CGAffineTransformConcat(t1, t2);
    view.transform=t3;
    CGRect rect = CGRectApplyAffineTransform(frame, t3);
    NSLog(@"transform rect:%@", NSStringFromCGRect(rect));
    NSLog(@"transform view rect:%@", NSStringFromCGRect(view.frame));
}
//output:
transform rect:{{8, 88}, {80, 80}}
transform view rect:{{20, 100}, {80, 80}}

从上面的代码中可以看到,对view进行矩阵变换与对同样的rect进行矩阵变换后的结果是不一样的:
如果view.layer.anchorPoint=CGPointMake(1, 1); 那么transform view rect:{{10+20, 10+80+20}, {80, 80}}  ,其中的20是由于view本身变小带来距离
如果view.layer.anchorPoint=CGPointMake(0, 0); 那么transform view rect:{{10+0, 10+80+0}, {80, 80}}

因为矩阵变换是基于anchor Point的

Now let's look at the description of the transformproperty from UIView documentation :

The origin of the transform is the value of the center property, or the layer’s anchorPoint property if it was changed. (Use the layer property to get the underlying Core Animation layer object.) The default value is CGAffineTransformIdentity. Since you didn't change the layer's anchor point, the transform is applied from the center of the view.

The original center is (60,60). The transformed center is (60,140), since the scaling issue doesn't affect the origin point (which is the center). You now have a (80,80) rect centered on (60,140) point : you can find your {{20, 100}, {80, 80}} rectangle.

ref: stackoverflow.com/questions/3…

关于IOS中矩阵变换的理解

equation02.gif

  1. The result is in a different coordinate system, the one transformed by the variable values in the transformation matrix. The following equations are the definition of the previous matrix transform:

  2. 对于CGAffineTransformConcat,采用右乘:

     CGAffineTransform a= CGAffineTransformMake(1, 1, 2, 3, 0, 0);
     CGAffineTransform b= CGAffineTransformMake(1, 0, 4, 5, 0, 0);
     NSLog(@"右乘:%@",NSStringFromCGAffineTransform(CGAffineTransformConcat(a,b)));
     NSLog(@"右乘:%@",NSStringFromCGAffineTransform(CGAffineTransformConcat(b,a)));
     输出:
     2016-02-16 14:37:53.032 TEST_Animi_COPY[75412:433902] 右乘:[5, 5, 14, 15, 0, 0]
     2016-02-16 14:37:53.033 TEST_Animi_COPY[75412:433902] 右乘:[1, 1, 14, 19, 0, 0]
    

对于第一个:

1 1 0     1 0 0       5   5   0
2 3 0  *  4 5 0  =    14  15  0
0 0 1     0 0 1       0   0   1

矩阵变换的顺序

CGAffineTransformConcat(a,b) 结果是a*b     
CGAffineTransformTranslate(a,x,y) 结果是 [1,0,0,1,x,y]对应的矩阵 * a

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    UIView* view=[[UIView alloc] init];
    view.backgroundColor=[UIColor redColor];
//    view.layer.anchorPoint=CGPointMake(0, 0);
    CGRect frame=CGRectMake(10, 10, 100, 100);
    view.frame=frame;
    [self.view addSubview:view];
    CGAffineTransform t1 = CGAffineTransformMakeTranslation(0, 100);
    CGAffineTransform t2 = CGAffineTransformMakeScale(.8, .8);

    CGAffineTransform t3 = CGAffineTransformConcat(t2, t1);
    CGAffineTransform t4 = CGAffineTransformTranslate(t2, 0, 100);
    view.transform=t4;
    CGRect rect = CGRectApplyAffineTransform(frame, t3);
    CGRect rect1 = CGRectApplyAffineTransform(frame, t4);
    NSLog(@"t3:%@",NSStringFromCGAffineTransform(t3));
    NSLog(@"t4:%@",NSStringFromCGAffineTransform(t4));
    NSLog(@"transform rect:%@", NSStringFromCGRect(rect));
    NSLog(@"transform rect1:%@", NSStringFromCGRect(rect1));
    NSLog(@"view rect:%@", NSStringFromCGRect(view.frame));
}

结果:

 t3:[0.80000000000000004, 0, 0, 0.80000000000000004, 0, 100]
 t4:[0.80000000000000004, 0, 0, 0.80000000000000004, 0, 80]
 transform rect:{{8, 108}, {80, 80}}
 transform rect1:{{8, 88}, {80, 80}}
 view rect:{{20, 100}, {80, 80}}

变换后Rect的计算

UIView* v=[[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
v.backgroundColor=[UIColor redColor];
UIView* v1=[[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
v1.backgroundColor=[UIColor greenColor];
[self.view addSubview:v];
[self.view addSubview:v1];
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    v.transform=CGAffineTransformMakeTranslation(50, 200);
});
{
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        CGRect r=[self.view convertRect:v.bounds fromView:v];
        v1.frame=r;
    });
}
//v和v1的位置重合

关于UIView中的动画选项

UIViewAnimationOptionBeginFromCurrentState 可以使得正在进行中的动画, 再次开始动画的时候,从当前位置开始.

  [UIView animateWithDuration:0.3 delay:0.0 
  options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState 
  animations:^{
  }

Clip CGContext

/** Create THE MASK Image **/
UIGraphicsBeginImageContext(CGSizeMake(TB_SLIDER_SIZE,TB_SLIDER_SIZE));
CGContextRef imageCtx = UIGraphicsGetCurrentContext();

CGContextAddArc(imageCtx, self.frame.size.width/2  , self.frame.size.height/2, radius, 0, ToRad(self.angle), 0);
[[UIColor redColor]set];

//Use shadow to create the Blur effect
CGContextSetShadowWithColor(imageCtx, CGSizeMake(0, 0), self.angle/20, [UIColor blackColor].CGColor);

//define the path
CGContextSetLineWidth(imageCtx, TB_LINE_WIDTH);
CGContextDrawPath(imageCtx, kCGPathStroke);

//save the context content into the image mask
CGImageRef mask = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();

/** Clip Context to the mask **/
//在Clip Context之前先保存一下CGContext 状态, clip操作完成之后,再restore一下,这样clip操作就不会影响之后的操作.
CGContextSaveGState(ctx);
CGContextClipToMask(ctx, self.bounds, mask);
CGImageRelease(mask);

/** THE GRADIENT **/
//list of components
CGFloat components[8] = {
    0.0, 0.0, 1.0, 1.0,     // Start color - Blue
    1.0, 0.0, 1.0, 1.0 };   // End color - Violet

CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, components, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;

//Gradient direction
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));

//Draw the gradient
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(ctx);

关于CACurrentMediaTime

值得指出的是,实际上我们创建的动画对象在被添加到 layer 时立刻就复制了一份。这个特性在多个 view 中重用动画时这非常有用。比方说我们想要第二个火箭在第一个火箭起飞不久后起飞:

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.byValue = @378;
animation.duration = 1;

[rocket1.layer addAnimation:animation forKey:@"basic"];
rocket1.layer.position = CGPointMake(455, 61);

animation.beginTime = CACurrentMediaTime() + 0.5;

[rocket2.layer addAnimation:animation forKey:@"basic"];
rocket2.layer.position = CGPointMake(455, 111);

设置动画的 beginTime 为未来 0.5 秒将只会影响 rocket2,因为动画在执行语句 [rocket1.layer addAnimation:animation forKey:@"basic"]; 时已经被复制了,并且之后 rocket1 也不会考虑对动画对象的改变。

animation.beginTime=CACurrentMediaTime()+1.0f 相当于,将动画的开始时间向后延迟1秒.

避免动画的反复

为了避免动画的反复,在开始新的动画前,就当先移除之前加在上面的动画. 使用下面的api:

    [_baseBtn.layer removeAllAnimations]   调用这一个就可以了
    [_baseBtn.layer removeAnimationForKey:]

CAReplicatorLayer中item的颜色

CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
replicatorLayer.frame              = CGRectMake(0, 0, 100, 100);
replicatorLayer.borderWidth        = 0.5f;
replicatorLayer.borderColor        = [UIColor blackColor].CGColor;
replicatorLayer.position           = self.view.center;
[self.view.layer addSublayer:replicatorLayer];


replicatorLayer.instanceCount     = 8;
CATransform3D transform           = CATransform3DIdentity;
transform                         = CATransform3DRotate(transform, 45.f*M_PI/180.f, 0, 0, 1);
replicatorLayer.instanceTransform = transform;
replicatorLayer.instanceGreenOffset = 0.3;
replicatorLayer.instanceRedOffset   = 0.3;
replicatorLayer.instanceBlueOffset=-0.3;
replicatorLayer.instanceColor=[UIColor redColor].CGColor;

// Create Layer.
CALayer *layer        = [CALayer layer];
layer.frame           = CGRectMake(0, 0, 8, 40);
layer.backgroundColor = [UIColor whiteColor].CGColor; //只有这里设置是白色,上面的instanceColor才会生效.
[replicatorLayer addSublayer:layer];
还有一个需要注意的是:
//这里的translationX,要大于item layer的宽度才会看到间隔.这里的translateX,是相对于上一个itemLayer的变换.
clayer.instanceTransform=CATransform3DMakeTranslation(itemGap+itemWidth, 0, 0);

用CALayer给UIView添加一个mask

CALayer* layer=[CALayer layer];
layer.frame=self.view.bounds;
layer.backgroundColor=[[UIColor lightGrayColor] colorWithAlphaComponent:0.7].CGColor;
[self.view.layer addSublayer:layer];

注意上面的代码,虽然在view的layer上面添加了一层layer,但由于layer不参与事件机制,所以不影响原来view的事件响应.


关于UIApplicationSignificantTimeChangeNotification

当新的一天来到,或者说当运营商时间更新的时候,UIApplication会下发一个通知来告诉你时间改变了。可以在程序中监听 UIApplicationSignificantTimeChangeNotification 事件来对界面进行更新或者做任何你需要做的事。

当系统的区域格式,或者时间格式(是否24小时制)改变时,UIApplication也会下发一个通知来告诉你这个变化。可以在程序中监听 NSCurrentLocaleDidChangeNotification 事件来对界面进行更新或者做任何你需要做的事。


关于UIColor的colorSpace

  1. [UIColor blackColor]并不属性 kCGColorSpaceModelRGB colorSpace,而是属于kCGColorSpaceModelMonochrome colorSpace.

  2. 通过CGColorGetNumberOfComponents(startColor.CGColor)来获取color components的个数,如果数量是4个,则可以转换成RGB. 如果是2个,说明是在Monochrome color space.

  3. 判断是否是RGB颜色空间:

if (CGColorSpaceGetModel(CGColorGetColorSpace(startColor.CGColor)) != kCGColorSpaceModelRGB) {
        NSLog(@"no rgb colorspace");
}

ref: [000]颜色空间