iOS swift 3天30个swift项目之第二天

1 渐变TableView

1.1 效果

1.2 代码实现

直接是继承了UITableViewController这个控制器,这个应该是自带了一个列表。内部有一个tableView。

class ClearTableViewController: UITableViewController {

初始化:

override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = UIColor.black
        self.tableView.separatorStyle = UITableViewCellSeparatorStyle.none
        self.tableView.tableFooterView = UIView(frame: CGRect.zero)
        self.tableView.register(TableViewCell.self, forCellReuseIdentifier: "tableCell")        
}

这里绑定了TableViewCell。

class TableViewCell: UITableViewCell {

    let gradientLayer = CAGradientLayer()
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        gradientLayer.frame = self.bounds
        let color1 = UIColor(white: 1.0, alpha: 0.2).cgColor
        let color2 = UIColor(white: 1.0, alpha: 0.1).cgColor
        let color3 = UIColor.clear.cgColor
        let color4 = UIColor(white: 0.0, alpha: 0.05).cgColor
        
        gradientLayer.colors = [color1, color2, color3, color4]
        gradientLayer.locations = [0.0, 0.04, 0.95, 1.0]
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = self.bounds
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

这里把UITableViewCell的bounds给到了一个渐变层CAGradientLayer。

数据绑定:

 var tableData = ["Read 3 article on Medium", "Cleanup bedroom", "Go for a run", "Hit the gym", "Build another swift project", "Movement training", "Fix the layout problem of a client project", "Write the experience of #30daysSwift", "Inbox Zero", "Booking the ticket to Chengdu", "Test the Adobe Project Comet", "Hop on a call to mom"]

然后因为这个是UITableViewController,就无需重复设置代理了:

override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! TableViewCell
        
        cell.textLabel?.text = tableData[indexPath.row]
        cell.textLabel?.textColor = UIColor.white
        cell.textLabel?.backgroundColor = UIColor.clear
        cell.textLabel?.font = UIFont(name: "Avenir Next", size: 18)
        cell.selectionStyle = UITableViewCellSelectionStyle.none
        return cell
    }
    
    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cell.backgroundColor = colorforIndex(index: indexPath.row)
    }
    
    func colorforIndex(index: Int) -> UIColor {
        
        let itemCount = tableData.count - 1
        let color = (CGFloat(index) / CGFloat(itemCount)) * 0.6
        return UIColor(red: 1.0, green: color, blue: 0.0, alpha: 1.0)
    }

重点实现下这几个代理方法即可。颜色设置主要是通过willDisplay这个方法设置进去了。

2 登录动画

2.1 效果

2.2 故事版关系

这里是有两个场景Scene,每个Scene绑定了一个类,也就是左侧的两个Controller。 这里学会了不用代码,直接用storyboard直接实现控制器的跳转。

可以参考下这篇文章:Xcode新建View Controller Scene并实现界面间跳转的方法

2.3 启动页

class SplasViewController: UIViewController {
    
    @IBOutlet weak var signupButton: UIButton!
    @IBOutlet weak var loginButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        signupButton.layer.cornerRadius = 5
        loginButton.layer.cornerRadius = 5 
    }
    
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return UIStatusBarStyle.lightContent
    }
}
这里设置了按钮圆角。

2.4 登录页

UI定义:

@IBOutlet weak var uesernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!

@IBOutlet weak var centerAlignUsername: NSLayoutConstraint!
@IBOutlet weak var centerAlignPassword: NSLayoutConstraint!

@IBOutlet weak var loginButton: UIButton!

初始化:

override func viewDidLoad() {
    super.viewDidLoad()
    
    uesernameTextField.layer.cornerRadius = 5
    passwordTextField.layer.cornerRadius = 5
    uesernameTextField.delegate = self
    passwordTextField.delegate = self
    loginButton.layer.cornerRadius = 5
}

这里设置编辑框代理,设置按钮圆角。

将要显示:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    centerAlignUsername.constant -= view.bounds.width
    centerAlignPassword.constant -= view.bounds.width
    loginButton.alpha = 0
}

这里设置刚进页面x轴减去控件宽度,实现效果就是从左边滑出来。

已经显示了:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    UIView.animate(withDuration: 0.5, delay: 0.00, options: .curveEaseOut, animations: {
        
        self.centerAlignUsername.constant += self.view.bounds.width
        self.view.layoutIfNeeded()
    
        }, completion: nil)
    
    UIView.animate(withDuration: 0.5, delay: 0.10, options: .curveEaseOut, animations: {
        
        self.centerAlignPassword.constant += self.view.bounds.width
        self.view.layoutIfNeeded()
        
        }, completion: nil)
    
    UIView.animate(withDuration: 0.5, delay: 0.20, options: .curveEaseOut, animations: {
        
        self.loginButton.alpha = 1
        
        }, completion: nil)

}

这是设置3个动画,将三个视图从左边滑出。

3 列表动画

3.1 效果

3.2 第一个列表

初始化:

override func viewDidLoad() {
    super.viewDidLoad()    
    self.view.backgroundColor = UIColor.black
    self.tableView.separatorStyle = UITableViewCellSeparatorStyle.none
    self.tableView.tableFooterView = UIView(frame: CGRect.zero)
    self.tableView.register(FirstTableCell.self, forCellReuseIdentifier: "tableCell")
}

这里绑定了第一个Table的Cell:

class FirstTableCell: UITableViewCell {

    let gradientLayer = CAGradientLayer()
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
    
    
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        gradientLayer.frame = self.bounds
        let color1 = UIColor(white: 1.0, alpha: 0.2).cgColor as CGColor
        let color2 = UIColor(white: 1.0, alpha: 0.1).cgColor as CGColor
        let color3 = UIColor.clear.cgColor as CGColor
        let color4 = UIColor(white: 0.0, alpha: 0.05).cgColor as CGColor
        
        gradientLayer.colors = [color1, color2, color3, color4]
        gradientLayer.locations = [0.0, 0.04, 0.95, 1.0]
        layer.insertSublayer(gradientLayer, at: 0)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = self.bounds
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

这里跟前面类似,设置了渐变背景。通过layer层insertSublayer设置了渐变层实现。

如何实现动画呢?
继续看第一个控制器。

将要的生命周期:

override func viewWillAppear(_ animated: Bool) {
    animateTable()
}

func animateTable() {
        self.tableView.reloadData()
        let cells = tableView.visibleCells
        let tableHeight: CGFloat = tableView.bounds.size.height
        
        for i in cells {
            let cell: UITableViewCell = i as UITableViewCell
            cell.transform = CGAffineTransform(translationX: 0, y: tableHeight)
        }
        
        var index = 0
        for a in cells {
            
            let cell: UITableViewCell = a as UITableViewCell
            
            UIView.animate(withDuration: 1.5, delay: 0.05 * Double(index), usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: [], animations: {
                
                cell.transform = CGAffineTransform(translationX: 0, y: 0);
                
            }, completion: nil)
            
            index += 1
        }
    }

这里一个设置了Cell的transform,第一个是从底部初始位置,第二个循环是恢复到目标位置。

第一个table绑定的代理和数据源:

override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tableData.count
}

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 60
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
    
    cell.textLabel?.text = tableData[indexPath.row]
    cell.textLabel?.textColor = UIColor.white
    cell.textLabel?.backgroundColor = UIColor.clear
    cell.textLabel?.font = UIFont(name: "Avenir Next", size: 18)
    cell.selectionStyle = UITableViewCellSelectionStyle.none
    return cell
}

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cell.backgroundColor =  colorforIndex(indexPath.row)
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    performSegue(withIdentifier: "ShowAnimateTableViewController", sender: nil)
}

func colorforIndex(_ index: Int) -> UIColor {
    let itemCount = tableData.count - 1
    let color = (CGFloat(index) / CGFloat(itemCount)) * 0.6
    return UIColor(red: color, green: 0.0, blue: 1.0, alpha: 1.0)
}

这里设置了渐变色,和前面一样。

3.3 第2个列表

继承UITableViewController:

class AnimateTableViewController: UITableViewController {

初始化:

 override func viewDidLoad() {
    super.viewDidLoad()
    
    self.view.backgroundColor = UIColor.black
    self.tableView.separatorStyle = UITableViewCellSeparatorStyle.none
    self.tableView.tableFooterView = UIView(frame: CGRect.zero)
    self.tableView.register(SecondTableCell.self, forCellReuseIdentifier: "SecondTableCell")
}

将要显示:

override func viewWillAppear(_ animated: Bool) {
    animateTable()
}
    
func animateTable() {
    
    self.tableView.reloadData()
    
    let cells = tableView.visibleCells
    let tableHeight: CGFloat = tableView.bounds.size.height
    
    for (index, cell) in cells.enumerated() {
        cell.transform = CGAffineTransform(translationX: 0, y: tableHeight)
        UIView.animate(withDuration: 1.0, delay: 0.05 * Double(index), usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: [], animations: {
                cell.transform = CGAffineTransform(translationX: 0, y: 0);
            }, completion: nil)
    }
}

这里设置了显示动画,从底部弹出效果。

override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tableData.count
}

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 60
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "SecondTableCell", for: indexPath)
    
    cell.textLabel?.text = tableData[indexPath.row]
    cell.textLabel?.textColor = UIColor.white
    cell.textLabel?.backgroundColor = UIColor.clear
    cell.textLabel?.font = UIFont(name: "Avenir Next", size: 18)
    cell.selectionStyle = UITableViewCellSelectionStyle.none
    return cell
    
}

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cell.backgroundColor =  colorforIndex(indexPath.row)
}

func colorforIndex(_ index: Int) -> UIColor {
    let itemCount = tableData.count - 1
    let color = (CGFloat(index) / CGFloat(itemCount)) * 0.6
    return UIColor(red: 1.0, green: color, blue: 0.0, alpha: 1.0)
}

这里数据源绑定。还需要一个Cell支持下。
SecondTableCell:

class SecondTableCell: UITableViewCell {
    
    let gradientLayer = CAGradientLayer()
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        gradientLayer.frame = self.bounds
        let color1 = UIColor(white: 1.0, alpha: 0.2).cgColor as CGColor
        let color2 = UIColor(white: 1.0, alpha: 0.1).cgColor as CGColor
        let color3 = UIColor.clear.cgColor as CGColor
        let color4 = UIColor(white: 0.0, alpha: 0.05).cgColor as CGColor
        
        gradientLayer.colors = [color1, color2, color3, color4]
        gradientLayer.locations = [0.0, 0.04, 0.95, 1.0]
        layer.insertSublayer(gradientLayer, at: 0)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = self.bounds
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

这里和第一个Cell类似。

4 游戏抽奖滚动图案

4.1 效果

4.2 UI

这里一个背景图片
go按钮
中间是UIPickerView,有点像时间滚动条
底部模式,结果文案。

直接在故事版里面拖好了。

然后拖动3个View到控制器:

class ViewController: UIViewController {

    @IBOutlet weak var emojiPickerView: UIPickerView!
    @IBOutlet weak var goButton: UIButton!
    @IBOutlet weak var resultLabel: UILabel!

4.3 Data

 var imageArray = [String]()
    var dataArray1 = [Int]()
    var dataArray2 = [Int]()
    var dataArray3 = [Int]()
    var amazingFlag = false
    var bounds: CGRect = CGRect.zero

变量声明,等下作为UIPickerView的数据。

4.3 生命周期之viewDidLoad

override func viewDidLoad() {
    super.viewDidLoad()
    
    bounds = goButton.bounds
    imageArray = ["👻","👸","💩","😘","🍔","🤖","🍟","🐼","🚖","🐷"]
    
    for _ in 0...100 {
        self.dataArray1.append((Int)(arc4random() % 10 ))
        self.dataArray2.append((Int)(arc4random() % 10 ))
        self.dataArray3.append((Int)(arc4random() % 10 ))
    }
    
    resultLabel.text = ""
    
    emojiPickerView.delegate = self
    emojiPickerView.dataSource = self
    
    goButton.layer.cornerRadius = 6
    goButton.layer.masksToBounds = true
}

这里生成长度为100的3个数组。本质上存放的0到9个数字。

4.4 将要可见

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    goButton.alpha = 0   
}

4.5 已经可见

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    UIView.animate(withDuration: 0.5, delay: 0.3, options: .curveEaseOut, animations: {
        
        self.goButton.alpha = 1
        
        }, completion: nil)
}

底部按钮逐渐显示。

4.6 点击事件

@IBAction func amazingButtonDidTouch(_ sender: UIButton) {
    amazingFlag = !amazingFlag;
    sender.setTitle(amazingFlag ? "开挂模式":"常规模式", for: .normal)
}

@IBAction func goButtoDidTouch(_ sender: AnyObject) {
    let index1: Int
    let index2: Int
    let index3: Int
    if amazingFlag {
        index1 = Int(arc4random()) % 90 + 3
        index2 = dataArray2.firstIndex(of: dataArray1[index1])!
        index3 = dataArray3.lastIndex(of: dataArray1[index1])!
    } else {
        index1 = Int(arc4random()) % 90 + 3
        index2 = Int(arc4random()) % 90 + 3
        index3 = Int(arc4random()) % 90 + 3
    }
    
    /// 下次多少行,开启动画效果
    emojiPickerView.selectRow(index1, inComponent: 0, animated: true)
    emojiPickerView.selectRow(index2, inComponent: 1, animated: true)
    emojiPickerView.selectRow(index3, inComponent: 2, animated: true)
    
    /// 结果显示
    if(dataArray1[emojiPickerView.selectedRow(inComponent: 0)] == dataArray2[emojiPickerView.selectedRow(inComponent: 1)] && dataArray2[emojiPickerView.selectedRow(inComponent: 1)] == dataArray3[emojiPickerView.selectedRow(inComponent: 2)]) {
        
        resultLabel.text = "Bingo!"
        
    } else {
        resultLabel.text = "💔"
    }
    
    /// 底部Go抖动效果
    UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.1, initialSpringVelocity: 5, options: .curveLinear, animations: {
        
        self.goButton.bounds = CGRect(x: self.bounds.origin.x, y: self.bounds.origin.y, width: self.bounds.size.width - 20, height: self.bounds.size.height)
        
    }, completion: { (compelete: Bool) in
        
        UIView.animate(withDuration: 0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: {
            
            self.goButton.bounds = CGRect(x: self.bounds.origin.x, y: self.bounds.origin.y, width: self.bounds.size.width, height: self.bounds.size.height)
            
        }, completion: nil)
        
    })
}

5 启动动画

5.1 效果

5.2 启动页

启动页需要再info.plist中配置
如下:

这里我们在LaunchScreen.storyboard中设置了一个背景图片。
这里面设置了一张白色的🕊没有效果哦。但是之前有,不知道为啥。

5.3 首页

首页其实加了一个同启动页的蓝色背景,中间减掉一个白鸽,然后白鸽再扩展的动画。
这个背景是在故事版的View右侧属性里面的Background中设置的。

设置蒙层:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    self.mask = CALayer()
    self.mask!.contents = UIImage(named: "twitter")?.cgImage
    self.mask!.contentsGravity = kCAGravityResizeAspect
    self.mask!.bounds = CGRect(x: 0, y: 0, width: 100, height: 81)
    self.mask!.anchorPoint = CGPoint(x: 0.5, y: 0.5)
    self.imageView.setNeedsLayout()
    self.imageView.layoutIfNeeded()
    self.mask!.position = CGPoint(x: self.imageView.frame.size.width / 2, y: self.imageView.frame.size.height / 2)
    self.imageView.layer.mask = mask
    
    animateMask()
}

这里给到imageView的layer的mask,这样就相当于在蓝色背景中镂空可以看到里面的东西了。

func animateMask() {
    let keyFrameAnimation = CAKeyframeAnimation(keyPath: "bounds")
    keyFrameAnimation.delegate = self
    keyFrameAnimation.duration = 0.6
    keyFrameAnimation.beginTime = CACurrentMediaTime() + 0.5
    keyFrameAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)]
    do {
        // 动画需要加上这段代码,否则会造成页面闪一下
        keyFrameAnimation.fillMode = kCAFillModeForwards
        keyFrameAnimation.isRemovedOnCompletion = false
    }
    let initalBounds = NSValue(cgRect: mask!.bounds)
    let secondBounds = NSValue(cgRect: CGRect(x: 0, y: 0, width: 90*0.9, height: 73*0.9))
    let finalBounds = NSValue(cgRect: CGRect(x: 0, y: 0, width: 1600, height: 1300))
    keyFrameAnimation.values = [initalBounds, secondBounds, finalBounds]
    keyFrameAnimation.keyTimes = [0, 0.3, 1]
    self.mask!.add(keyFrameAnimation, forKey: "bounds")
}

这个动画的作用是将镂空效果爆炸,显示出主页面。

动画停止后需要去除蒙层哦:

extension ViewController : CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        self.imageView.layer.mask = nil
    }
}

动画结束,需要将蒙层去掉,不然屏幕上方会多出一块多余的遮挡视图。

6 滑动菜单

6.1 效果

6.2 首页

class NewsTableViewController: BaseTableViewController {
    let menuTransitionManager = MenuTransitionManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Everyday Moments"
        self.view.backgroundColor = UIColor(red:0.062, green:0.062, blue:0.07, alpha:1)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let menuTableViewController = segue.destination as! MenuTableViewController
        menuTableViewController.currentItem = self.title!
        menuTableViewController.transitioningDelegate = menuTransitionManager
        menuTransitionManager.delegate = self
    }
}

这里有一个prepare方法,实际上是跳转到其它页面前会执行。
我们在故事版里面定义了跳转关系,这里就可以通过prepare方法拿到跳转的目标控制器了。

这是一个新闻的列表页。
这里首先继承了BaseTableViewController:

class BaseTableViewController: UITableViewController {
    
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return UIStatusBarStyle.lightContent
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.separatorStyle = UITableViewCellSeparatorStyle.none
    }
}

然后这里有一个MenuTransitionManager,是我们自定义的一个类:
个人猜测这个Transition相关的应该是为了实现一个转场动画,实现菜单能够丝滑地顶下来吧。
里面放了一个自己定义的协议MenuTransitionManagerDelegate:

@objc protocol MenuTransitionManagerDelegate {
    func dismiss()
}

class MenuTransitionManager: NSObject {
    var duration = 0.5
    var isPresenting = false
    var delegate:MenuTransitionManagerDelegate?
    var snapshot:UIView? {
        didSet {
            if let _delegate = delegate {
                let tapGestureRecognizer = UITapGestureRecognizer(target: _delegate, action: #selector(MenuTransitionManagerDelegate.dismiss))
                snapshot?.addGestureRecognizer(tapGestureRecognizer)
            }
        }
    }
}

然后扩展了一个UIViewControllerAnimatedTransitioning,
这个应该是系统的协议:

extension MenuTransitionManager : UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let fromView = transitionContext.view(forKey: .from)!
        let toView = transitionContext.view(forKey: .to)!
        let container = transitionContext.containerView
        let moveLeft = CGAffineTransform(translationX: 250, y: 0)
        let moveRight = CGAffineTransform(translationX: 0, y: 0)
        
        if isPresenting {
            snapshot = fromView.snapshotView(afterScreenUpdates: true)
            container.addSubview(toView)
            container.addSubview(snapshot!)
        }
        
        UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.3, options: .curveEaseInOut, animations: {
            if self.isPresenting {
                self.snapshot?.transform = moveLeft
                toView.transform = .identity
            } else {
                self.snapshot?.transform = .identity
                fromView.transform = moveRight
            }
            
        }, completion: { finished in
            transitionContext.completeTransition(true)
            if !self.isPresenting {
                self.snapshot?.removeFromSuperview()
            }
        })
    }
}

同时也扩展了UIViewControllerTransitioningDelegate,也是系统的:

extension MenuTransitionManager : UIViewControllerTransitioningDelegate {
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresenting = false
        return self
    }
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresenting = true
        return self
    }
}

再看下首页新闻展示的代理和数据源:

extension NewsTableViewController {
    // MARK: UITableViewDataSource
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 3
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 4
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! NewsTableViewCell
        cell.backgroundColor = UIColor.clear
        
        if indexPath.row == 0 {
            cell.postImageView.image = UIImage(named: "1")
            cell.postTitle.text = "Love mountain."
            cell.postAuthor.text = "Allen Wang"
            cell.authorImageView.image = UIImage(named: "a")
            
        } else if indexPath.row == 1 {
            cell.postImageView.image = UIImage(named: "2")
            cell.postTitle.text = "New graphic design - LIVE FREE"
            cell.postAuthor.text = "Cole"
            cell.authorImageView.image = UIImage(named: "b")
            
        } else if indexPath.row == 2 {
            cell.postImageView.image = UIImage(named: "3")
            cell.postTitle.text = "Summer sand"
            cell.postAuthor.text = "Daniel Hooper"
            cell.authorImageView.image = UIImage(named: "c")
            
        } else {
            cell.postImageView.image = UIImage(named: "4")
            cell.postTitle.text = "Seeking for signal"
            cell.postAuthor.text = "Noby-Wan Kenobi"
            cell.authorImageView.image = UIImage(named: "d")
            
        }
        
        return cell
    }
}

这里每行能够正常展示了。Cell用了自定义的NewsTableViewCell:

class NewsTableViewCell: UITableViewCell {
    @IBOutlet weak var postImageView:UIImageView!
    @IBOutlet weak var postTitle:UILabel!
    @IBOutlet weak var postAuthor:UILabel!
    @IBOutlet weak var authorImageView:UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
        authorImageView.layer.cornerRadius = authorImageView.frame.width / 2
        authorImageView.layer.masksToBounds = true
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
}

具体布局还是故事版里面处理的布局。

然后让首页也实现我们自定义的MenuTransitionManagerDelegate 转场代理吧:

extension NewsTableViewController : MenuTransitionManagerDelegate {
    func dismiss() {
        dismiss(animated: true, completion: nil)
    }
}

这里猜测是点击空白区域,菜单dismiss吧。

6.3 菜单控制器

class MenuTableViewController: BaseTableViewController {
    var menuItems = ["Everyday Moments", "Popular", "Editors", "Upcoming", "Fresh", "Stock-photos", "Trending"]
    var currentItem = "Everyday Moments"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor(red:0.109, green:0.114, blue:0.128, alpha:1)
    }
    
    // 这里prepare去掉效果一样
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let menuTableViewController = segue.source as! MenuTableViewController
        
        if let selectedRow = menuTableViewController.tableView.indexPathForSelectedRow?.row {
            currentItem = menuItems[selectedRow]
        }
    }
}

extension MenuTableViewController {
    // MARK: UITableViewDataSource
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return menuItems.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MenuTableViewCell
        
        cell.titleLabel.text = menuItems[indexPath.row]
        cell.titleLabel.textColor = (menuItems[indexPath.row] == currentItem) ? UIColor.white : UIColor.gray
        cell.backgroundColor = UIColor.clear
        return cell
    }
}

这里实现了菜单列表显示。

菜单Cell如下:

class MenuTableViewCell: UITableViewCell {

    @IBOutlet weak var titleLabel:UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

7 酷炫左右缩放菜单

7.1 效果

7.2 首页

class MainViewController: BaseViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationController?.isNavigationBarHidden = true
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    @IBAction func unwindToMainViewController (_ sender: UIStoryboardSegue){
        self.dismiss(animated: true, completion: nil)
    }
}

这里的unwindToMainViewController 是我们自行在故事版拖进来的,这样子在这个控制器里面的点击事件也可以处理的。
这里就是dismiss弹框。
其它ui是我们自己拖到故事版里面的。

7.3 菜单页

菜单页是由一个UIVisualEffectView的父布局包裹的。
这个故事版的Scene绑定的class为MenuViewController。

class MenuViewController: BaseViewController {
    
    let transitionManager = MenuTransitionManager()
    
    @IBOutlet weak var textButton: UIButton!
    @IBOutlet weak var textLabel: UILabel!
    
    @IBOutlet weak var photoButton: UIButton!
    @IBOutlet weak var photoLabel: UILabel!
    
    @IBOutlet weak var quoteButton: UIButton!
    @IBOutlet weak var quoteLabel: UILabel!
    
    @IBOutlet weak var linkButton: UIButton!
    @IBOutlet weak var linkLabel: UILabel!
    
    @IBOutlet weak var chatButton: UIButton!
    @IBOutlet weak var chatLabel: UILabel!
    
    @IBOutlet weak var audioButton: UIButton!
    @IBOutlet weak var audioLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.transitioningDelegate = self.transitionManager
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

这里声明了,布局效果在故事版里面拖动的。

注意了这里self的transitiningDelegate设置了转场动画。

也是我们自己定义的一个类:


class MenuTransitionManager: NSObject {

    private var presenting = false

    func offstage(_ amount: CGFloat) -> CGAffineTransform {
        return CGAffineTransform(translationX: amount, y: 0)
    }
    
    func offStageMenuController(_ menuViewController: MenuViewController) {
        if !presenting{
            menuViewController.view.alpha = 0
        }
        let topRowOffset  : CGFloat = 300
        let middleRowOffset : CGFloat = 150
        let bottomRowOffset  : CGFloat = 50
        
        menuViewController.textButton.transform = self.offstage(-topRowOffset)
        menuViewController.textLabel.transform = self.offstage(-topRowOffset)
        
        menuViewController.quoteButton.transform = self.offstage(-middleRowOffset)
        menuViewController.quoteLabel.transform = self.offstage(-middleRowOffset)
        
        menuViewController.chatButton.transform = self.offstage(-bottomRowOffset)
        menuViewController.chatLabel.transform = self.offstage(-bottomRowOffset)
        
        menuViewController.photoButton.transform = self.offstage(topRowOffset)
        menuViewController.photoLabel.transform = self.offstage(topRowOffset)
        
        menuViewController.linkButton.transform = self.offstage(middleRowOffset)
        menuViewController.linkLabel.transform = self.offstage(middleRowOffset)
        
        menuViewController.audioButton.transform = self.offstage(bottomRowOffset)
        menuViewController.audioLabel.transform = self.offstage(bottomRowOffset)
        
    }
    
    func onStageMenuController(_ menuViewController: MenuViewController) {
        menuViewController.view.alpha = 1
        
        menuViewController.textButton.transform = .identity
        menuViewController.textLabel.transform = .identity
        menuViewController.quoteButton.transform = .identity
        menuViewController.quoteLabel.transform = .identity
        menuViewController.chatButton.transform = .identity
        menuViewController.chatLabel.transform = .identity
        menuViewController.photoButton.transform = .identity
        menuViewController.photoLabel.transform = .identity
        menuViewController.linkButton.transform = .identity
        menuViewController.linkLabel.transform = .identity
        menuViewController.audioButton.transform = .identity
        menuViewController.audioLabel.transform = .identity
        
    }
}

可以看到这里主要是针对里面控制器的ui的transform做了一些配置。

然后这个管理员实现了UIViewControllerTransitioningDelegate代理:

extension MenuTransitionManager : UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        self.presenting = true
        return self
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        self.presenting = false
        return self
    }
}

同样也实现了UIViewControllerAnimatedTransitioning这个协议:

extension MenuTransitionManager : UIViewControllerAnimatedTransitioning {
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.5
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let container = transitionContext.containerView
        let screens: (from:UIViewController, to:UIViewController) = (transitionContext.viewController(forKey: .from)!, transitionContext.viewController(forKey: .to)!)
        
        let menuViewController = !self.presenting ? screens.from as! MenuViewController : screens.to as! MenuViewController
        let bottomViewController = !self.presenting ? screens.to as UIViewController : screens.from as UIViewController
        
        let menuView: UIView! = menuViewController.view
        let bottomView: UIView! = bottomViewController.view
        
        if (self.presenting) {
             self.offStageMenuController(menuViewController)
        }
        container.addSubview(bottomView)
        container.addSubview(menuView)
        
        let duration = self.transitionDuration(using: transitionContext)
        
        UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: [], animations: {
            if (self.presenting) {
                 self.onStageMenuController(menuViewController)
            } else {
                self.offStageMenuController(menuViewController)
            }
        }, completion: { finished in
            transitionContext.completeTransition(true)
            UIApplication.shared.keyWindow!.addSubview(screens.to.view)
        })
    }
}

主要是动画执行的时候,走了self.onStageMenuController方法或者self.offStageMenuController这个方法实现动画效果。

8 限制字符串

8.1 效果

8.2 UI

这里也是先构造一个故事版。
左上角:Close按钮。
右上角:Tweet按钮。
头像:Avatar Image VIew。
底部View: 4个Button
编辑框:UITextView

控制器要用的:

class ViewController: UIViewController {

    @IBOutlet weak var tweetTextView: UITextView!
    @IBOutlet weak var bottomUIView: UIView!
    @IBOutlet weak var avatarImageView: UIImageView!
    @IBOutlet weak var characterCountLabel: UILabel!

初始化生命周期:

    override func viewDidLoad() {
        super.viewDidLoad()
        tweetTextView.delegate = self
        avatarImageView.layer.cornerRadius = avatarImageView.frame.width / 2
        tweetTextView.backgroundColor = UIColor.clear
        
        NotificationCenter.default.addObserver(self, selector:#selector(ViewController.keyBoardWillShow(_:)), name:UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector:#selector(ViewController.keyBoardWillHide(_:)), name:UIResponder.keyboardWillHideNotification, object: nil)
    }

这里配置了UITextView的代理为自己,后面一定会实现相关方法的。

然后这里监听了键盘显示和隐藏哦。
主要是将底部的控制栏跟随键盘上下顶起来。

@objc func keyBoardWillShow(_ note:NSNotification) {
    let userInfo  = note.userInfo
    let keyBoardBounds = (userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
    let duration = (userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    let deltaY = keyBoardBounds.size.height
    let animations = {
        self.bottomUIView.transform = CGAffineTransform(translationX: 0, y: -deltaY)
    }
    
    if duration > 0 {
        // 莫名其妙的一段代码, 左移16位能看出来是个啥值吗
        // let options = UIView.AnimationOptions(rawValue: UInt((userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).intValue << 16))
        UIView.animate(withDuration: duration, delay: 0, options:[.beginFromCurrentState, .curveLinear], animations: animations, completion: nil)
    }else {
        animations()
    }
}

上面的代码是为了在键盘弹出的时候,底部bottomUIView跟随键盘顶起来。

@objc func keyBoardWillHide(_ note:NSNotification) {
    let userInfo  = note.userInfo
    let duration = (userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
    
    let animations = {
        self.bottomUIView.transform = .identity
    }
    
    if duration > 0 {
        // let options = UIView.AnimationOptions(rawValue: UInt((userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).intValue << 16))
        UIView.animate(withDuration: duration, delay: 0, options:[.beginFromCurrentState, .curveLinear], animations: animations, completion: nil)
    }else{
        animations()
    }
    
}

上面的代码是为了在键盘隐藏的时候,底部bottomUIView也随之落下去。

然后编辑框的代理如下:

extension ViewController : UITextViewDelegate {
    // MARK:UITextViewDelegate
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        let inputTextLength = text.count - range.length + tweetTextView.text.count
        if inputTextLength > 140 {
            return false
        }
        characterCountLabel.text = "\(140 - inputTextLength)"
        return true
    }
}

这里是为了统计当前剩余字符,如果不足,则无法继续输入。

9 自定义下拉刷新

9.1 效果

9.2 变量定义

    var refreshController: UIRefreshControl!
    var customView: UIView!
    var labelsArray: Array<UILabel> = []
    var isAnimating = false
    var currentColorIndex = 0
    var currentLabelIndex = 0
    var timer: Timer!
    var dataArray: Array<String> = ["😂", "🤗", "😳", "😌", "😊"]

这里第一个就是刷新控制器了,应该主要就是往这里面加逻辑。

9.3 初始化

override func viewDidLoad() {
    super.viewDidLoad()
    
    tblDemo.delegate = self
    tblDemo.dataSource = self
    refreshController = UIRefreshControl()
    refreshController.backgroundColor = UIColor.clear
    refreshController.tintColor = UIColor.clear
    tblDemo.addSubview(refreshController)
    
    loadCustomRefreshContents()
}

这里设置了代理和数据源,new了一个刷新控制器,清空背景,给tableView添加了一个子View。

下面加载自定义xib文件:

 func loadCustomRefreshContents() {
        
    let refreshContents = Bundle.main.loadNibNamed("RefreshContents", owner: self, options: nil)
    
    customView = refreshContents![0] as? UIView
    customView.frame = refreshController.bounds
    
    for i in 0..<customView.subviews.count {
        labelsArray.append(customView.viewWithTag(i + 1) as! UILabel)
    }
    
    refreshController.addSubview(customView)
}

这个布局里面主要是存放了我们自定义头部的View。

9.4 数据源和代理

extension ViewController : UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }
}

extension ViewController : UITableViewDataSource {
    
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "idCell", for: indexPath)
        
        cell.textLabel!.text = dataArray[indexPath.row]
        cell.textLabel!.font = UIFont(name: "Apple Color Emoji", size: 40)
        cell.textLabel!.textAlignment = .center
        
        return cell
    }
}

这里cell用默认的。

9.5 下拉动画实现

首先实现UIScrollViewDelegate代理:

extension ViewController : UIScrollViewDelegate {
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if refreshController.isRefreshing {
            if !isAnimating {
                doSomething()
                animateRefreshStep1()
            }
        }
    }
}

如果没有在刷新中,没有在动画中,就做点失去,然后走动画第一步。

func doSomething() {
    timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(ViewController.endedOfWork), userInfo: nil, repeats: true)
}

这里开启定时器,5s后才走endedofWork。

@objc func endedOfWork() {
    refreshController.endRefreshing()
    timer.invalidate()
    timer = nil
}

这里停止刷新,定时器结束。
说明上面的doSomething只是开启一个定时器,模拟进行网络请求,然后加载动画而已。

动画第一步:

func animateRefreshStep1() {
    isAnimating = true
    
    UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveLinear, animations: {
        self.labelsArray[self.currentLabelIndex].transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi/4))
        self.labelsArray[self.currentLabelIndex].textColor = self.getNextColor()
        }, completion: { _ in
            UIView.animate(withDuration: 0.05, delay: 0.0, options: .curveLinear, animations: {
                self.labelsArray[self.currentLabelIndex].transform = .identity
                self.labelsArray[self.currentLabelIndex].textColor = UIColor.black
                }, completion: { _ in
                    self.currentLabelIndex += 1
                    if self.currentLabelIndex < self.labelsArray.count {
                        self.animateRefreshStep1()
                    }else {
                        self.animateRefreshStep2()
                    }
            })
    })
}

func getNextColor() -> UIColor {
    var colorsArray: Array<UIColor> = [.magenta, .brown, .yellow,
                                        .red, .green, .blue, .orange]
    if currentColorIndex == colorsArray.count {
        currentColorIndex = 0
    }
    let returnColor = colorsArray[currentColorIndex]
    currentColorIndex += 1
    return returnColor
}

这里开启动画了,给子View配置transform和textColor。
递归走每一个子View动画,走完后走动画第二步:

func animateRefreshStep2() {
    UIView.animate(withDuration: 0.40, delay: 0.0, options: .curveLinear, animations: {
        let scale = CGAffineTransform(scaleX: 1.5, y: 1.5)
        self.labelsArray[1].transform = scale
        self.labelsArray[2].transform = scale
        self.labelsArray[3].transform = scale
        self.labelsArray[4].transform = scale
        self.labelsArray[5].transform = scale
        self.labelsArray[6].transform = scale
        self.labelsArray[7].transform = scale
        self.labelsArray[8].transform = scale
        self.labelsArray[9].transform = scale
        self.labelsArray[10].transform = scale
        self.labelsArray[11].transform = scale
        
        }, completion: { _ in
            UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveLinear, animations: {
                self.labelsArray[0].transform = .identity
                self.labelsArray[1].transform = .identity
                self.labelsArray[2].transform = .identity
                self.labelsArray[3].transform = .identity
                self.labelsArray[4].transform = .identity
                self.labelsArray[5].transform = .identity
                self.labelsArray[6].transform = .identity
                self.labelsArray[7].transform = .identity
                self.labelsArray[8].transform = .identity
                self.labelsArray[9].transform = .identity
                self.labelsArray[10].transform = .identity
                self.labelsArray[11].transform = .identity
                
                }, completion: { _ in
                    if self.refreshController.isRefreshing {
                        self.currentLabelIndex = 0
                        self.animateRefreshStep1()
                    } else {
                        self.isAnimating = false
                        self.currentLabelIndex = 0
                        for i in 0 ..< self.labelsArray.count {
                            self.labelsArray[i].textColor = UIColor.black
                            self.labelsArray[i].transform = .identity
                        }
                    }
            })
    })
}

10 CollectionView动画

10.1 效果

10.2 首页

Main.storyboard里面放置了一个UICollectionView。

里面放置了2个结构体:

private struct Storyboard {
    static let CellIdentifier = "AnimationCollectionViewCell"
    static let NibName = "AnimationCollectionViewCell"
}
    
private struct Constants {
    static let AnimationDuration: Double = 0.5
    static let AnimationDelay: Double = 0.0
    static let AnimationSpringDamping: CGFloat = 1.0
    static let AnimationInitialSpringVelocity: CGFloat = 1.0
}

成员定义:

@IBOutlet var testCollectionView: UICollectionView!

var imageCollection: AnimationImageCollection!

第一个是拖过来的,第二个是自定义的:

struct AnimationImageCollection {
    private let imagePaths = ["1", "2", "3", "4", "5"]
    var images: [AnimationCellModel]
    
    init() {
        images = imagePaths.map { AnimationCellModel(imagePath: $0) }
    }
}

这个是一个结构体,里面的实体是这个:

struct AnimationCellModel {
    let imagePath: String
    
    init(imagePath: String?) {
        self.imagePath = imagePath ?? ""
    }
}

很简单,放置了一个图片路径而已。

回到第一个控制器:

override func viewDidLoad() {
    super.viewDidLoad()
    
    imageCollection = AnimationImageCollection()
    testCollectionView.register(UINib(nibName: Storyboard.NibName, bundle: nil), forCellWithReuseIdentifier: Storyboard.CellIdentifier)
}

这里集合View注册了一个Cell,这个Cell是我们建立的xib文件,名称叫做AnimationCollectionViewCell.

10.3 子item

应该就是这个itemCell了,还是看下吧:

class AnimationCollectionViewCell: UICollectionViewCell {
    
    @IBOutlet weak var backButton: UIButton!
    @IBOutlet weak var animationImageView: UIImageView!
    @IBOutlet weak var animationTextView: UITextView!
    
    var backButtonTapped: (() -> Void)?
    
    func prepareCell(_ viewModel: AnimationCellModel) {
        animationImageView.image = UIImage(named: viewModel.imagePath)
        animationTextView.isScrollEnabled = false
        backButton.isHidden = true
        addTapEventHandler()
    }
    
    func handleCellSelected() {
        animationTextView.isScrollEnabled = false
        backButton.isHidden = false
        self.superview?.bringSubview(toFront: self)
    }
    
    private func addTapEventHandler() {
        backButton.addTarget(self, action: #selector(backButtonDidTouch(_:)), for: .touchUpInside)
    }
    
    @objc func backButtonDidTouch(_ sender: UIGestureRecognizer) {
        backButtonTapped?()
    }
}

item布局就是返回按钮,图片,和文案。
这个返回按钮默认应该是隐藏的,选中才给它显示。选中的时候走handleCellSelected方法,
这里走了一个

  self.superview?.bringSubview(toFront: self)

这个作用应该就是把item移动到最前面了。

 private func addTapEventHandler() {
    backButton.addTarget(self, action: #selector(backButtonDidTouch(_:)), for: .touchUpInside)
}

@objc func backButtonDidTouch(_ sender: UIGestureRecognizer) {
    backButtonTapped?()
}

这里将返回的点击事件暴露出去了。

回调给首页里面了:

// MARK: 按钮事件
func backButtonDidTouch() {
    guard let indexPaths = self.collectionView!.indexPathsForSelectedItems else {
        return
    }

    collectionView!.isScrollEnabled = true
    collectionView!.reloadItems(at: indexPaths)
}

10.4 代理设置和数据绑定

 // MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Storyboard.CellIdentifier, for: indexPath) as? AnimationCollectionViewCell,
        let viewModel = imageCollection.images.safeIndex(indexPath.item) else {
        return UICollectionViewCell()
    }
    // 这里是自己定义的方法哦
    cell.prepareCell(viewModel)
    return cell
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return imageCollection.images.count
}

// MARK: UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard let cell = collectionView.cellForItem(at: indexPath) as? AnimationCollectionViewCell else {
        return
    }
    
    self.handleAnimationCellSelected(collectionView, cell: cell)
}

这里选中的时候走了handleAnimationCellSelected这个方法,才实现动画效果的:

private func handleAnimationCellSelected(_ collectionView: UICollectionView, cell: AnimationCollectionViewCell) {
        
    cell.handleCellSelected()
    cell.backButtonTapped = self.backButtonDidTouch
    
    let animations = {
        cell.frame = self.view.bounds
    }

    let completion: (_ finished: Bool) -> () = { _ in
        collectionView.isScrollEnabled = false
    }

    UIView.animate(withDuration: Constants.AnimationDuration, delay: Constants.AnimationDelay, usingSpringWithDamping: Constants.AnimationSpringDamping, initialSpringVelocity: Constants.AnimationInitialSpringVelocity, options: [], animations: animations, completion: completion)
}

动画主要是设置了cell的frame吧。

11 总结

    1. 渐变TableView主要实现的是每个cell颜色不同,实现渐变效果,主要是在Cell里面定义了一个CAGradientlayer,在tableView的代理方法为willDisplay中给cell设置了背景色。背景色+渐变层实现了这种效果。
    1. 登录动画的实现方案,就是在视图将要可见,将左侧约束减掉视图宽度,然后已经可见再走UIView的动画函数,再恢复约束,实现动画效果。
    1. 列表动画,主要也是在将要可见的生命周期,遍历cell,修改cell的transform为CGAffineTransform,改变y值初始值为整个tableView的高度,然后动画设置恢复,从而实现进入动画效果。
    1. 游戏抽奖滚动动画,主要用了UIPickerView来实现。点击后设置pickerView的selectRow,开启动画效果,即可实现改效果。
    1. 启动动画,这里主要是加了一个蒙层,通过控制器的mask,设置未CALayer,然后将png镂空图标设置给mask的conents,然后可以通过CAKeyframeAnimation给mask设置动画,这样可以实现启动展开效果。
    1. 滑动菜单,主要是在prepare方法中,定义了场景的transitioningDelegate为自定义效果,通过扩展UIViewControllerAnimatedTransitioning的系统协议,在animateTransition方法里面加入我们自己定义好的动画,可以实现滑动菜单效果。
    1. 酷炫左右缩放菜单效果,跳转的逻辑可以在故事版里面写,这里只是菜单里面配置了一个自定义的transitioningDelegate得以实现,具体动画在UIViewControllerAnimatedTransitioning这个协议里面的animateTransition方法中处理,这里面可以拿到跳转的控制器,控制器可以再拿里面的ui。
    1. 限制字符串,这个比较简单,就是UITextView的用法,一个是监听键盘收起和弹出,给底部栏加动画,另一个是实现UITextViewDelegate,可以拿到当前长度。
    1. 自定义下拉刷新,主要是对UIRefreshControl做处理,这个控制器可以添加子View,子View可以通过Bundle.main.loadNibNamed加载xib文件。这样可以实现自定义下拉刷新效果。
    1. 列表转场动画,这个主要就是走didSelectItemAt的协议方法中,实现动画,关键逻辑就是cell.frame赋予self.view.bounds,相当于放大了cell,达成目标效果。

   转载规则


《iOS swift 3天30个swift项目之第二天》 Jason 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
iOS swift 3天30个swift项目之第三天 iOS swift 3天30个swift项目之第三天
1 滑动删除1.1 效果 1.2 列表实现class ViewController: UITableViewController { 数据定义: var data = [ pattern(image: "1&
2023-02-05
下一篇 
iOS swift 3天30个Swift项目之第一天 iOS swift 3天30个Swift项目之第一天
参考项目:https://github.com/soapyigu/Swift-30-Projects 1 计时器首先看下效果吧。 1.1 主要功能点击播放按钮,数字开始增加;点击暂停按钮,数字停止增加;点击“Reset”,数值置为0;
2023-02-03
  目录