普通视图

发现新文章,点击刷新页面。
昨天以前掘金专栏-会飞的金鱼

SwiftUI从入门到精髓

2024年2月28日 17:36

SwiftUI

序言

开年的第一篇文章,今天分享的是SwiftUI,SwiftUI出来好几年,之前一直没学习,所以现在才开始;如果大家还留在 iOS 开发,这门语言也是一个趋势; 目前待业中.... 不得不说已逝的2023,大家开始都抱着一解封,经济都会向上转好,可是现实不是我们想象那样;目前我也在学习 SwiftUI,并且努力找工作中....。至于 2024 年经济如何,咱们作为老百姓在大环境和全球经济影响下;坦然面对,提升自己。 这里不得不说国人坚韧不拔的精神。“卷” -- 努力吧Coding人

SwiftUI体验

Xcode创建项目之后出现工程默认创建的UI界面;如下

swiftUI

一开始心里对自己说:"SwiftUI作为iOS开发新的UI体系,为啥初创的项目这么多代码,给初学者看到,一种压迫感,心想这语法好复杂,不想学了";不管你是不是这样心里,我刚开始看见,这么一坨代码,没什么心思,于是索性删掉;按自己能理解学习的方式来操作;于是做了简化:

import SwiftUI
import SwiftData

struct ContentView: View {
   
    var body: some View {
        Text("hello,word")
    }
}

#Preview {
    ContentView()
        .modelContainer(for: Item.self, inMemory: true)
}

关键字 some

关键字some啥玩意儿,完全陌生;先看看View;点击进入源码结构查看:

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required ``View/body-swift.property`` property.
    associatedtype Body : View

    @ViewBuilder @MainActor var body: Self.Body { get }
}

一堆英文注解估计大家不喜欢看,我就没贴出来了;简单来说: View 是一个泛型协议,它定义了所有视图类型需要遵循的接口,通过some修饰;表示 "我返回一个满足View 协议的某种类型"。some关键字告诉 Swift,虽然我们知道body必须返回一个View,但我们不确定具体是哪种 View(例如,TextImageVStack 等)。

协议里有一个associatedtypebody,其实这种协议就是当作约束形式使用;只要遵守这种协议编译器每次闭包中返回的一定是一个确定,遵守View协议的类型。

那么苹果工程师利用Swift5.1 Opaque return types 特性,为开发者提供了一个灵活的开发模式,抹掉了具体的类型,不需要修改公共API来确定每次闭包的返回类型,也降低了代码书写难度。(学学苹果那些大神思想,不错)

在来看看Preview

struct ContentView_Previews:PreviewProvider{
    static var previews: some View{
        ContentView()
        
    }
}

PreviewProvider就一个协议类,它的作用提供swiftUI不用运行,就能直接看到UI渲染变化,我觉得这个挺好,减少开发人员对UI运行测试次数和时间,而previews就是一个静态属性,返回一个 View 对象,用于在预览面板中展示。

@State属性包装器

@State属性包装器解决UI界面上,数据同步以及及时刷新的功能。一般来说数据更新完,界面 UI 同时更新。在 SwiftUI里面,视图中声明的任何状态、内容和布局,源头一旦发生改变,会自动更新视图,因此,只需要一次布局,这个时候出现了@State,它来解决与UI之间数据状态问题。

它的概念就是:@State 是一个属性包装器 (property wrapper) ,用于声明状态属性 (state property) 当状态属性发生变化时,SwiftUI 会自动更新视图以反映最新的状态。

属性的值被存储在特殊的内存区域中,这个区域与 View struct 是隔离的 至于被它修饰的属性内存存储与分布现在无从得知,还没学习到那么深入,这事儿慢慢来,不是一天两天的,先上个代码看看它怎么使用的:

import SwiftUI

struct StateBootcamp: View {
    
    @State var bgkColor:Color = Color.blue
    @State var cut:Int = 0
    
    var body: some View {
        
        ZStack{
            
            bgkColor
                .ignoresSafeArea(.all)
            
            VStack(spacing: 20){
                
                Text("Hello, World!")
                    .font(.title)
                
                Text("count:\(cut)")
                    .font(.largeTitle)
                
                HStack(spacing: 20){
                    Button("Button01") {
                        cut+=1
                        bgkColor = Color.red
                    }
                    .font(.title)
                    .foregroundColor(.white)
                    
                    Button("Button02") {
                        cut-=1
                        bgkColor = .purple
                    }
                    .font(.title)
                    .foregroundColor(.white)
                }
                Button("默认"){
                    cut=0
                    bgkColor = .blue
                }
                .font(.title)
                .foregroundColor(.white)
            }
        }
    }
}

#Preview {
    StateBootcamp()
}

其实一看代码,就一幕了然,知道它的使用与作用;如果你写过swift代码,这些东西很好理解,但是只会OC,那么我建议你学习下swift;在来看 swiftUI 语法糖才更好理解。

在看看源码:

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen @propertyWrapper public struct State<Value> : DynamicProperty {
    public init(wrappedValue value: Value)
    public init(initialValue value: Value)
    public var wrappedValue: Value { get nonmutating set }
    public var projectedValue: Binding<Value> { get }
}


@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension State where Value : ExpressibleByNilLiteral {

    /// Creates a state property without an initial value.
    ///
    /// This initializer behaves like the ``init(wrappedValue:)`` initializer
    /// with an input of `nil`. See that initializer for more information.
    @inlinable public init()
}


可以看到State是一个结构体,由@propertyWrapper包装的。@propertyWrapper是属性包装器。property wrapper 做的事情大体如下:

-   为底层的存储变量`State<Int>`自动提供一组 **getter****setter** 方法,结构体内保存了`Int`的具体数值;

-   在 body 首次求值前,将`State<Int>`关联到当前`View`上,为它在堆中对应当前`View`分配一个存储位置。

-`@State`修饰的变量设置观察,当值改变时,触发新一次的`body`求值,并刷新 UI。

SwiftUI 基础组件

Spacer垫片 :先贴贴代码

import SwiftUI

struct SpacerBootcampDemo: View {
    var body: some View {
        Text("Spacer UP")
            .font(.largeTitle)
        
        Spacer()
            .frame(width: 37)
            .background(.blue)
        
        Text("Spacer Down")
            .font(.largeTitle)
        
    }
}

#Preview {
    SpacerBootcampDemo()
}

在看看效果图:

Spacer

总结:Spacer 是一个灵活的空间视图,它的主要作用是在布局中自动调整自身的高度和宽度,以填满特定的空间;简单来说,它就是一个垫片,调整自身视图的高度,如果它周围有其他视图,也会受到 Spacer 影响。

ScrollView 如果你之前使用 UIkit 框架开发,在用 SwiftUI ,一下有点不适应,代码和之前的 UIkit 开发模式不太一样,但是大大缩短UI编写时间;先上代码:


import SwiftUI

struct ScollViewBootcamp: View {
    
    var body: some View {
        
        ScrollView{
            LazyVStack{
                ForEach(0..<20){
                    (idx) in
                    
                    VStack {
                        
                        Text("Hello, World!")
                            .font(.title)
                            .foregroundStyle(.white)
                            .frame(width: UIScreen.main.bounds.width-20,height: 350)
                            .background(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<215)/255.0, green: CGFloat.random(in: 0..<235)/255.0, blue: CGFloat.random(in: 0...247)/255.0, alpha: 0.9)))
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        
                        Rectangle()
                            .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0...187)/255.0, green: CGFloat.random(in: 0..<210)/255.0, blue: CGFloat.random(in: 0...237)/255.0, alpha: 0.9)))
                            .frame(width: UIScreen.main.bounds.width-20,height: 530)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        
                        
                        ScrollView(.horizontal,showsIndicators: false,content: {
                            LazyHStack{
                                ForEach(0..<10){
                                    idx in
                                    Rectangle()
                                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0...167)/255.0, green: CGFloat.random(in: 0...131)/255.0, blue: CGFloat.random(in: 0...89)/255.0, alpha: 0.9)))
                                        .frame(width: 200, height: 300)
                                        .clipShape(RoundedRectangle(cornerRadius: 10))
                                    
                                }
                            }
                        })
                        .padding(.leading,10)
                        .padding(.trailing,10)
                        
                        
                    }
                }
            }
            .frame(width:UIScreen.main.bounds.width)
            
        }
    }
}


#Preview {
    ScollViewBootcamp()
}

上图看看效果:

ScrollView

简单几句就能实现 ScrollView 的滑动效果;非常方便。

LazyVGrid 网格布局,先上代码:

import SwiftUI

struct GridViewBootcamp: View {
    
    let columns=[        GridItem(.flexible(),spacing: 6   ,alignment: .center),        GridItem(.flexible(),spacing: 6    ,alignment: .center),        GridItem(.flexible(),spacing: 6  ,alignment: .center),    ]
    
    var body: some View {
        
        ScrollView{
            LazyVGrid(columns: columns,
                      alignment: .center,
                      spacing: 6,
                      pinnedViews: [.sectionHeaders],content:
                        {
                Section(content: {}, header: {
                    Text("section header 一")
                        .font(.largeTitle)
                        .foregroundStyle(.blue)
                        .frame(width: UIScreen.main.bounds.width,height: 100,alignment: .leading)
                })
                
                ForEach(0..<41){
                    index in
                    Rectangle()
                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<255)/255.0, green: CGFloat.random(in: 0..<255)/255.0, blue: CGFloat.random(in: 0...255)/255.0, alpha: 0.9)))
                        .frame(height: 50)
                }
                
                //-------
                Section {
                    
                } header: {
                    Text("section header 二")
                        .font(.largeTitle)
                        .foregroundStyle(.blue)
                        .frame(width: UIScreen.main.bounds.width,alignment: .leading)
                    
                }
                
                ForEach(0..<41){
                    index in
                    Rectangle()
                        .fill(Color.init(cgColor: CGColor(red: CGFloat.random(in: 0..<255)/255.0, green: CGFloat.random(in: 0..<255)/255.0, blue: CGFloat.random(in: 0...255)/255.0, alpha: 0.9)))
                        .frame(height: 50)
                }
                
            })
            .padding(.leading,6)
            .padding(.trailing,6)
            .background(.gray)
        }.background(.blue)
    }
}

#Preview {
    GridViewBootcamp()
}

效果图:

LazyVGrid

总结:LazyVGrid 大家看到这个单词有个 Lazy 懒加载的意思,它的内部加载item简单来说,就是当视图需要时,才会执行item内容渲染功能,展示UI上。也就这点注意。

SafeArea 安全区域:

import SwiftUI

struct SafeAreaBootcamp: View {
    var body: some View {
        GeometryReader{
            src in
            Rectangle()
                .fill(.blue)
                .frame(maxWidth: .infinity,
                       maxHeight: .infinity)
        }
    }
}

#Preview {
    SafeAreaBootcamp()
}

效果图:

safeArea

可以看到上下边距存在安全区域的,如果禁用安全区域,使用 ignoresSafeArea(.all) 可以去掉;这个就太简单了。

代码如下:

safeArea

最后说说SwiftUI函数表达

上上代码:


import SwiftUI

struct ExtractFunctionsBootcamp: View {
    
    @State var bgc:Color = .red
    
    var body: some View {
        normolView
    }
    
    var normolView : some View {
        setUI()// 函数调用
    }
    
    func chageColor() -> Void {
        self.bgc = .red
    }
    
    //函数定义
    func setUI()->some View {
        return ZStack{
            
            bgc
                .ignoresSafeArea(.all)
            
            VStack(spacing: 20, content: {
                
                Text("Hello, World!")
                    .font(.largeTitle)
                
                Button(action: {
                    bgc = .brown
                }, label: {
                    Text("Button")
                        .font(.largeTitle)
                        .foregroundStyle(.white)
                })
                
                Button {
                    self.chageColor()
                } label: {
                    Image(systemName: "button.horizontal.top.press")
                        .resizable()
                        .foregroundColor(.white)
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 50,height: 50)
                }
            })
        }
    }
}


#Preview {
    ExtractFunctionsBootcamp()
}

其实函数表达跟我们swift语法糖一样;func 命名;这点和swift语法糖没什么区别。

总结(说说我的感想)

SwiftUI优点:

简洁性:Swift,SwiftUI语法简洁,编写代码变得更加容易和快速。

安全性:是一种类型安全的编程语言,可以在编译时检测类型错误,这帮助我们避免许多常见的错误,提高代码的质量和可靠性。

互操作性:它与Objective-C语言无缝互衔接,是的OC与swift代码混编变的更加便捷。

SwiftUI缺点

功能限制:虽然SwiftUI提供了许多常见的UI组件,但与UIKit相比,功能仍然相对有限。在某些复杂的界面需求下,可能需要使用UIKit来实现。

错误提示不明确:有时SwiftUI, SwiftUI的错误提示可能不够明确,导致难以定位问题。

UIkit与SwiftUI缺乏无缝兼容:两者兼容性不够理想,这在业务开发中,你可能才能发现。

目前苹果与市面大量应用也在使用Swift,SwiftUI开发应用,这们语言在应用中占有读也是成倍增长。

路漫漫其修远兮,吾将上下而求索

提醒大家在的时候,注意身体劳逸结合;毕竟没有谁真的会在互联网这个行业干到退休,最终都会离开,把位置留给年轻人。

后续更新中..........

深入了解swift函数派发机制

2023年7月17日 19:38

函数派发

Swift中函数的派发机制有三种:静态派发,函数表派发,消息派发。

静态派发

静态派发是指在运行时不需要查表,直接跳转到方法进行执行。静态派发的性能也是最高的。c语言采用的是直接派发。

函数表派发

class类型采用函数表派发。当一个对象调用一个函数时,会从对象的头8字节找到该对象的元信息。从元信息的函数表中找到执行的函数地址,并执行函数。

对象的元信息会在编译时写入macho文件中。

class Animal {
    func speak() {
        print("Animal speak")
    }
    func eat() {
        print("Animal eat")
    }
    func sleep() {
        print("Animal sleep")
    }
}
class Dog: Animal {
    override func speak() {
        print("Dog speak")
    }
    override func eat() {
        print("Dog eat")
    }
    func run() {
        print("Dog run")
    }
}class Animal {
    func speak() {
        print("Animal speak")
    }
    func eat() {
        print("Animal eat")
    }
    func sleep() {
        print("Animal sleep")
    }
}
class Dog: Animal {
    override func speak() {
        print("Dog speak")
    }
    override func eat() {
        print("Dog eat")
    }
    func run() {
        print("Dog run")
    }
}

Dog继承于Animal。let dog: Dog = Dog()dog 变量指向Dog对象。Dog对象在堆内存中。Dog对象的前8字节存有Dog对象的元信息。Dog对象的元信息中有该类的函数指针表。Dog中override父类Animal的函数会替换掉父类的函数存于Dogd的函数指针中。

swift### 消息派发

继承于NSObject的采用的是消息派发。Swift可以通过dynamic修饰来支持消息派发机制。

函数派发场景分析

在选择派发方式时,在编译期间就能决定执行哪个函数就采用静态派发。需要在运行期间决定执行哪个函数的就需要用函数表派发或者消息派发。不具动态性的的场景默认采用静态派发,这样派发效率更高。

struct无法继承,也就不具有动态性,函数派发在编译期间就能确定。

class和协议的 extension无法被子类继承,函数派发在编译期间就能确定。

class和协议的初始化方法,因为绝大多数时候需要被override的,所以采用函数表派发。

class的其他方法,如果没有被override,一般是函数表派发,但编译器也可能优化成直接派发。

class的 @objcextension能够继承,函数派发在执行期决定,并且是采用的是消息派发。

不继承NSObject的纯Swift,@objc的extension,采用的消息派发有点迷。消息派发机制需要有Objective-C的运行时,不继承与NSObject能有运行时的信息吗。该类的对象,

class FunctionDispatchObject {
     func test1() {
        print("test1")
    }
}
extension FunctionDispatchObject {
    @objc public func test2() {
        print("test2")
    }
}
// test1采用函数表派发
// test2采用消息机制派发:test2虽然写在extension里,当家里@objc,具有了动态性,可以继承了。class FunctionDispatchObject {
     func test1() {
        print("test1")
    }
}
extension FunctionDispatchObject {
    @objc public func test2() {
        print("test2")
    }
}
// test1采用函数表派发
// test2采用消息机制派发:test2虽然写在extension里,当家里@objc,具有了动态性,可以继承了。

iOS APP启动全流程

2023年7月10日 17:13

1.在用户点击屏幕上的icon时,iOS系统用户体验层进程SpringBoad调用fork创建进程(复制进程),并执行execl函数将新的可执行代码载入内存,执行loadMachine去加载主Mach-o,进行__TEXT,__DATA的映射,加载UUID,创建主线程,代码签名验证,加载动态链器。

2.动态连接器加载完成后,动态链接器先加载主程序,再加载所依赖的动态库,这个加载的过程。

3.主程序进行初始化时,是递归进行的,依赖关系呈现的是一个类似树的有向图。递归过程是树的深度遍历。在向下递归时,遇到image没有依赖了时,也就是叶子节点,树形结构的最左边的叶子节点就是libsystem,会通执行_dyld_objc_notify_register里注册的load_image方法,进而进行category的加载,+load的方法的执行,然后初始化C&C++的静态化变量,然后调用 constructor 函数。而不是像市面是上的千篇一律的+load方法执行后,在初始化C&C++静态变量,然后调用constructor函数。一定要注意是递归。递归到最上层了,就是主程序了,也就是主程序的+load方法,在初始化C&C++静态变量,然后调用constructor函数。

iOS

真心送你一份iOS核心动画总结篇,赶紧上车,学完它才觉得物有所值,还有实战案例!!!

2023年7月3日 18:25

本文案列代码在篇尾,有小伙伴需要代码可以移至篇尾获取。

在iOS开发中,动画是提高用户体验重要的环节之一。一个设计严谨、精细的动画效果能给用户耳目一新的效果,这对于app而言是非常重要的。

简介

iOS动画主要是指Core Animation框架。官方使用文档地址为:Core Animation Guide。Core Animation是iOS和macOS平台上负责图形渲染与动画的基础框架。Core Animation可以作用与动画视图或者其他可视元素,为你完成了动画所需的大部分绘帧工作。你只需要配置少量的动画参数(如开始点的位置和结束点的位置)即可使用Core Animation的动画效果。Core Animation将大部分实际的绘图任务交给了图形硬件来处理,图形硬件会加速图形渲染的速度。这种自动化的图形加速技术让动画拥有更高的帧率并且显示效果更加平滑,不会加重CPU的负担而影响程序的运行速度。

Core Animation

Core Animation是一组非常强大的动画处理API,它的子类主要有4个:CABasicAnimation、CAKeyframeAnimation、CATransition、CAAnimationGroup。 Core Animation类的继承关系图:

Animation

属性

duration:动画的持续时间 beginTime:动画的开始时间 repeatCount:动画的重复次数 autoreverses:动画按照原动画返回执行 timingFunction:控制动画的显示节奏系统提供五种值选择,分别是:

  • kCAMediaTimingFunctionLinear 线性动画
  • kCAMediaTimingFunctionEaseIn 先快后慢
  • kCAMediaTimingFunctionEaseOut 先慢后快
  • kCAMediaTimingFunctionEaseInEaseOut 先慢后快再慢
  • kCAMediaTimingFunctionDefault 默认,也属于中间比较快

delegate:动画代理。能够检测动画的执行和结束。 path:帧动画中的执行路径 type:过渡动画的动画类型。主要有以下4中类型:

  • kCATransitionFade 渐变效果
  • kCATransitionMoveIn 进入覆盖效果
  • kCATransitionPush 推出效果
  • kCATransitionReveal 离开效果

subtype:过渡动画的动画方向。

  • kCATransitionFromRight 从右侧进入
  • kCATransitionFromLeft 从左侧进入
  • kCATransitionFromTop 从顶部进入
  • kCATransitionFromBottom 从底部进入

动画的使用

动画使用步骤:

  1. 初始化一个动画对象(CAAnimation)并设置一些动画相关属性.
  2. 添加动画对象到层(CALayer)中,开始执行动画.

CALayer中很多属性都可以通过CAAnimation实现动画效果, 包括opacity, position, transform, bounds, contents等,具体可以在API文档中查找

通过调用CALayer的addAnimation:forKey:增加动画到层(CALayer)中,这样就能触发动画了.通过调用removeAnimationForKey:可以停止层中的动画.

UIView

_demoView.frame = CGRectMake(0, SCREEN_HEIGHT/2-50, 50, 50); 
[UIView animateWithDuration:1.0f animations:^{
    _demoView.frame = CGRectMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-50, 50, 50); 
} completion:^(BOOL finished) { 
    _demoView.frame = CGRectMake(SCREEN_WIDTH/2-25, SCREEN_HEIGHT/2-50, 50, 50); 
}];

UIView [begin commit]

_demoView.frame = CGRectMake(0, SCREEN_HEIGHT/2-50, 50, 50);
[UIView beginAnimations:nil context:nil]; 
[UIView setAnimationDuration:1.0f];
_demoView.frame = CGRectMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-50, 50, 50); 
[UIView commitAnimations];

Core Animation

CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"position"]; 
anima.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, SCREEN_HEIGHT/2-75)]; 
anima.toValue = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-75)];
anima.duration = 1.0f;
[_demoView.layer addAnimation:anima forKey:@"positionAnimation"];

动画详解

CABaseAnimation

基础动画主要提供了对于CALayer对象中的可变属性进行简单动画的操作。比如:位移、透明度、缩放、旋转、背景色等等。 主要提供如下属性: fromValue:keyPath对应的初始值 toValue:keyPath对应的结束值 示例:

CABaseAnimation

1.呼吸动画 
CABasicAnimation *animation =[CABasicAnimation animationWithKeyPath:@"opacity"]; 
animation.fromValue = [NSNumber numberWithFloat:1.0f]; 
animation.toValue = [NSNumber numberWithFloat:0.0f]; 
animation.autoreverses = YES; //回退动画(动画可逆,即循环) 
animation.duration = 1.0f; 
animation.repeatCount = MAXFLOAT; 
animation.removedOnCompletion = NO; 
animation.fillMode = kCAFillModeForwards;//removedOnCompletion,fillMode配合使用保持动画完成效果 
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[self.alphaTagButton.layer addAnimation:animation forKey:@"aAlpha"]; 
2.摇摆动画 //设置旋转原点 
self.sharkTagButton.layer.anchorPoint = CGPointMake(0.5, 0); 
CABasicAnimation* rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; //角度转弧度(这里用1,-1简单处理一下) 
rotationAnimation.toValue = [NSNumber numberWithFloat:1]; 
rotationAnimation.fromValue = [NSNumber numberWithFloat:-1]; 
rotationAnimation.duration = 1.0f;
rotationAnimation.repeatCount = MAXFLOAT; 
rotationAnimation.removedOnCompletion = NO; 
rotationAnimation.autoreverses = YES; 
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 
rotationAnimation.fillMode = kCAFillModeForwards; 
[self.sharkTagButton.layer addAnimation:rotationAnimation forKey:@"revItUpAnimation"];

注意: 如果fillMode=kCAFillModeForwards和removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。这就相当于Android早期的View动画。

CAKeyframeAnimation

CAKeyframeAnimation和CABaseAnimation都属于CAPropertyAnimatin的子类。CABaseAnimation只能从一个数值(fromValue)变换成另一个数值(toValue),而CAKeyframeAnimation则会使用一个NSArray保存一组关键帧。

主要属性: values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧 path:可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略。 keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的。 示例:

CAKeyframeAnimation

values属性应用

-(void)setUpCAKeyframeAnimationUseValues { 
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position";
    NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(50, 50)]; 
    NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(kWindowWidth - 50, 50)];
    NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(kWindowWidth - 50, kWindowHeight-50)]; 
    NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(50, kWindowHeight-50)];
    NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(50, 50)]; 
    animation.values = @[value1,value2,value3,value4,value5]; 
    animation.repeatCount = MAXFLOAT; animation.removedOnCompletion = NO; 
    animation.fillMode = kCAFillModeForwards; 
    animation.duration = 6.0f; animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 
    [self.keyButton.layer addAnimation:animation forKey:@"values"]; 
}

path方式应用

-(void)setUpCAKeyframeAnimationUsePath { 
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position";
    CGMutablePathRef path = CGPathCreateMutable(); 
    //矩形线路 
    CGPathAddRect(path, NULL, CGRectMake(50,50, kWindowWidth - 100,kWindowHeight - 100)); 
    animation.path=path; CGPathRelease(path);
    animation.repeatCount = MAXFLOAT; 
    animation.removedOnCompletion = NO; 
    animation.fillMode = kCAFillModeForwards;
    animation.duration = 10.0f; 
    animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 
    [self.keyButton.layer addAnimation:animation forKey:@"path"]; 
}

keyTimes属性使用

-(void)setUpCAKeyframeAnimationUsekeyTimes { 
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; 
    animation.keyPath = @"position.x"; 
    animation.values = @[@0, @20, @-20, @20, @0]; 
    animation.keyTimes = @[ @0, @(1 / 6.0), @(3 / 6.0), @(5 / 6.0), @1 ]; 
    animation.duration = 0.5;
    animation.additive = YES; 
    [self.sharkTagButton.layer addAnimation:animation forKey:@"keyTimes"];
}

CAAnimationGroup

CAAnimationGroup(组动画)是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行。有点类似于Android的帧动画,不过这里的组动画是将一些基础的动画拼接而成的,比如同时缩小、旋转、渐变。

主要属性有: animations:用来保存一组动画对象的NSArray。 示例:

CAAnimationGroup

CABasicAnimation * animationScale = [CABasicAnimation animation]; 
animationScale.keyPath = @"transform.scale"; 
animationScale.toValue = @(0.1); 
CABasicAnimation *animationRota = [CABasicAnimation animation]; animationRota.keyPath = @"transform.rotation"; 
animationRota.toValue = @(M_PI_2); 
CAAnimationGroup * group = [[CAAnimationGroup alloc] init]; 
group.duration = 3.0; 
group.fillMode = kCAFillModeForwards; 
group.removedOnCompletion = NO; 
group.repeatCount = MAXFLOAT; 
group.animations = @[animationScale,animationRota]; 
[self.groupButton.layer addAnimation:group forKey:nil];

CATransition

CAAnimation的子类,用于做过渡动画或者转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。 重要属性有: type:动画过渡类型,官方提供了如下类型:

  • kCATransitionFade 渐变效果
  • kCATransitionMoveIn 进入覆盖效果
  • kCATransitionPush 推出效果
  • kCATransitionReveal 揭露离开效果

subtype:动画过渡方向。

  • kCATransitionFromRight 从右侧进入
  • kCATransitionFromLeft 从左侧进入
  • kCATransitionFromTop 从顶部进入
  • kCATransitionFromBottom 从底部进入
  • startProgress:动画起点(在整体动画的百分比)
  • endProgress:动画终点(在整体动画的百分比)

CATransition

示例:

MyViewController *myVC = [[MyViewController alloc]init]; 
CATransition *animation = [CATransition animation]; 
animation.timingFunction = UIViewAnimationCurveEaseInOut; 
animation.type = @"cube"; animation.duration =0.5f; 
animation.subtype =kCATransitionFromRight; //控制器间跳转动画 
[[UIApplication sharedApplication].keyWindow.layer addAnimation:animation forKey:nil]; 
[self presentViewController:myVC animated:NO completion:nil];

github.com/yongliangP

❌
❌