순진이의 하루/study정리

8. 고차함수, UIViewPropertyAnimator_2022.08.29

순진이 2022. 8. 29. 09:32

고차함수

var numbers = Array(1...100)

1. ForEach

// 1. ForEach
numbers.forEach { number in
    print(number)
}


// 단축문법
numbers.forEach { print($0) }

-for문으로도 가능

var doubledNumbers = [Int]()

for number in numbers {
    let doubleNumber = number * 2
    doubledNumbers.append(doubleNumber)
}


2. map

// 2. map
numbers.map { number in 
	return number * 2
}

// 단축문법
numbers.map { $0 * 2 }

-for문으로도 가능

 

for number in numbers {
    doubledNumbers.append(number)
}


3. filter

// 3. filter
numbers.filter { number in
    number > 0
}

// 단축 문법
numbers.filter { $0 > 0 }

-for문으로도 가능

var higherThanFifty = [Int]()

for number in numbers where number > 50 {
    higherThanFifty.append(number)
}


4. reduce

// 4. reduce
numbers.reduce(0) { partialResult, number in
    partialResult + number
}

// 단축문법
numbers.reduce(0) { $0 + $1 }
// 0 { 0, 0 in 0 + 1 } => 1
// 1 { 1, 1 in 1 + 1 } => 2

-for문으로도 가능

var sum = 0

for number in numbers {
    sum += number
}

 


Animation

1. UIView.animate

import UIKit

class ViewController: UIViewController {

    let redView = UIView()
    let button = UIButton(type: .system)
    var topAnchor: NSLayoutConstraint?
    var leadingAnchor: NSLayoutConstraint?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    
    @objc func buttonTapped(_ sender: UIButton) {
        animate()
    }
    
    private func animate() {
        UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
            self.leadingAnchor?.constant = self.view.frame.width - 80
            // 레드뷰의 넓이가 60이니까 80만큼 -하면 끝이 20만큼 남을 거임
            self.view.layoutIfNeeded()
        }, completion: { _ in
            UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
                self.topAnchor?.constant = self.view.frame.height - 140
                self.view.layoutIfNeeded()
            }, completion: { _ in
                UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
                    self.leadingAnchor?.constant = 20
                    self.view.layoutIfNeeded()
                }, completion: { _ in
                    UIView.animate(withDuration: 1.0, delay: 0, options: [], animations: {
                        self.topAnchor?.constant = 80
                        self.view.layoutIfNeeded()
                    }, completion: { _ in })
                })
            })
        })
    }
}

//MARK: -UI
extension ViewController {
    final private func configureUI() {
        setAttributes()
        addTarget()
        setConstraints()
    }
    
    final private func setAttributes() {
        redView.backgroundColor = .red
        button.setTitle("button", for: .normal)
    }
    
    final private func addTarget() {
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
    }
    
    final private func setConstraints() {
        [redView, button].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        topAnchor = redView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80)
        topAnchor?.isActive = true
        leadingAnchor = redView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20)
        leadingAnchor?.isActive = true
        
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50),
            
            redView.widthAnchor.constraint(equalToConstant: 60),
            redView.heightAnchor.constraint(equalToConstant: 60)
        ])
    }
}

callback 지옥에 갇힘

 

2. keyFrame

    private func animateKeyFrame() {
        UIView.animateKeyframes(withDuration: 4, delay: 0, animations: {
            
            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.25, animations: {
                self.leadingAnchor?.constant = self.view.frame.width - 80
                // 상대적인(relative) startTime임! : 0.0 ~ 1.0 까지만 존재함
                // relativeDuration : 전체 시간 중에 duration. 여기서는 4초에서 0.25니까 1초임
                self.view.layoutIfNeeded()
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25) {
                self.topAnchor?.constant = self.view.frame.height - 140
                self.view.layoutIfNeeded()
            }
            
            UIView.addKeyframe(withRelativeStartTime: 0.50, relativeDuration: 0.25) {
                self.leadingAnchor?.constant = 20
                self.view.layoutIfNeeded()
            }
            
            UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
                self.topAnchor?.constant = 80
                self.view.layoutIfNeeded()
            }
        })
    }

 

3. UIViewPropertyAnimator

import UIKit

class ViewController: UIViewController {
    
    enum Button: String, CaseIterable {
        case start
        case pause
        case stop
    }
    
    private let redView = UIView()
    private var stackView = UIStackView()
    
    private var animator: UIViewPropertyAnimator?
    
    private var topAnchor: NSLayoutConstraint?
    private var leadingAnchor: NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    @objc func buttonTapped(_ sender: UIButton) {
        switch sender.currentTitle {
        case Button.start.rawValue:
            startAnimation()
        case Button.pause.rawValue:
            pauseAnimation()
        case Button.stop.rawValue:
            stopAnimation()
        default:
            fatalError()
        }
    }
    
    private func startAnimation() {
        if animator == nil {
            animator = UIViewPropertyAnimator(duration: 4, timingParameters: UICubicTimingParameters())
            animator?.addAnimations {
                UIView.animateKeyframes(withDuration: 4, delay: 0, animations: {
                    
                    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/6, animations: {
                        self.leadingAnchor?.constant = self.view.frame.width - 80
                        // 상대적인(relative) startTime임! : 0.0 ~ 1.0 까지만 존재함
                        // relativeDuration : 전체 시간 중에 duration. 여기서는 4초에서 0.25니까 1초임
                        self.view.layoutIfNeeded()
                    })
                    
                    UIView.addKeyframe(withRelativeStartTime: 1/6, relativeDuration: 2/6) {
                        self.topAnchor?.constant = self.view.frame.height - 140
                        self.view.layoutIfNeeded()
                    }
                    
                    UIView.addKeyframe(withRelativeStartTime: 3/6, relativeDuration: 1/6) {
                        self.leadingAnchor?.constant = 20
                        self.view.layoutIfNeeded()
                    }
                    
                    UIView.addKeyframe(withRelativeStartTime: 4/6, relativeDuration: 2/6) {
                        self.topAnchor?.constant = 80
                        self.view.layoutIfNeeded()
                    }
                })
            }
        }
        animator?.startAnimation()
    }
    
    private func pauseAnimation() {
        // 잠시 멈춤. 다시 시작할 수 있음
        animator?.pauseAnimation()
    }
    
    private func stopAnimation() {
        // 애니메이션 완전 끝
        animator?.stopAnimation(false)
        animator?.finishAnimation(at: .current)
        animator = nil
        topAnchor?.constant = 80
        leadingAnchor?.constant = 20
        view.setNeedsLayout()
    }
}
//MARK: -UI
extension ViewController {
    final private func configureUI() {
        setAttributes()
        setConstraints()
    }
    
    final private func setAttributes() {
        redView.backgroundColor = .red
    }

    final private func setConstraints() {
        stackView.alignment = .fill
        stackView.axis = .horizontal
        stackView.distribution = .equalSpacing
        
        [redView, stackView].forEach {
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        Button.allCases.forEach {
            let button = UIButton(type: .system)
            stackView.addArrangedSubview(button)
            button.setTitle($0.rawValue, for: .normal)
            button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
        }
        
        topAnchor = redView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80)
        topAnchor?.isActive = true
        leadingAnchor = redView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20)
        leadingAnchor?.isActive = true
        
        NSLayoutConstraint.activate([
            redView.widthAnchor.constraint(equalToConstant: 60),
            redView.heightAnchor.constraint(equalToConstant: 60),
            
            stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -60),
            stackView.widthAnchor.constraint(equalToConstant: view.frame.width - 40)
        ])
    }
}

UIViewPropertyAnimator class는 UIViewAnimating이라는 프로토콜을 채택하고 있기 때문에 starting, stopping, modifying할 수가 있다.