iOS Lottie 알아보기
모든 걸 코드로 구현할 수 없어서 적당히 로티를 사용하는 걸로 하자.
변경로그
- 2022년 2월 19일 03시 13분
- 최초 포스팅
- 2022년 4월 29일 15시 05분
- Lottie AnimationView width, height 적용이 되지 않는 문제에 대한 고찰
- 기존 샘플 코드에서 잘못된 레이아웃 수정
- 2022년 4월 30일 16시 39분
- 로티 이미지 색상 변경
- 2024년 11월 15일 03시 01분
- dot-lottie 오픈소스 사용시 주의할 점
로티 오픈소스 사이트
https://github.com/airbnb/lottie-ios/tree/master/Example/iOS/ViewControllers
그냥 여담인데, 어느 글에서 본건데 aribnb에서 large scale이라서 코드 로드하는 시간만 2분씩 걸리고 했다는데, 이걸 개편하는 작업을 진행했다더고 함.
설치는 SPM이나 cocoapod 사용해서 하면 됨.
로티 관련 파일을 다운로드 받아서 사용할 수 있는 사이트
https://lottiefiles.com/featured
json형식 말고 .lottie 파일도 있는데 메모리 사용량이 압도적으로 많아서 json 권장, 오픈소스 사이트에 issue를 올려두기도 했는데 메모리 너무 크게 잡아먹음.
- 2KB(.json) -> 2MB
- 7KB(.lottie) -> 20MB
코드 예제
ViewController 파일
//
// ViewController.swift
// Lottie_iOS
//
// Created by Hamlit Jason on 2022/02/18.
//
import UIKit
import Lottie
import SnapKit
import Then
import RxSwift
import RxCocoa
class ViewController: UIViewController {
fileprivate var bag = DisposeBag()
let animationView: AnimationView = .init(name: "95171-colors")
let stackView = UIStackView().then {
$0.distribution = .fillEqually
$0.layer.borderColor = UIColor.black.cgColor
$0.layer.borderWidth = 2
}
let playBtn = UIButton().then {
$0.setTitle("play", for: .normal)
$0.backgroundColor = .blue
}
let loopSelectBtn = UIButton().then {
$0.setTitle("loopSelect", for: .normal)
}
let stopBtn = UIButton().then {
$0.setTitle("stopBtn", for: .normal)
$0.backgroundColor = .red
}
let speedFrameSelectBtn = UIButton().then {
$0.setTitle("speedFrameSelectBtn", for: .normal)
$0.backgroundColor = .gray
}
let progressSelectBtn = UIButton().then {
$0.setTitle("progressSelectBtn", for: .normal)
$0.backgroundColor = .purple
}
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
bind()
}
fileprivate func loopAlert() {
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let playOnce = UIAlertAction(title: "playOnce", style: .default) { [weak self] _ in self?.animationView.loopMode = .playOnce }
let repeat2 = UIAlertAction(title: "repeat2", style: .default) { [weak self] _ in self?.animationView.loopMode = .repeat(2) }
let loop = UIAlertAction(title: "loop", style: .default) { [weak self] _ in self?.animationView.loopMode = .loop }
let repeatBackwards = UIAlertAction(title: "repeatBackwards", style: .default) { [weak self] _ in self?.animationView.loopMode = .repeatBackwards(1) }
let autoReverse = UIAlertAction(title: "autoReverse", style: .default) { [weak self] _ in self?.animationView.loopMode = .autoReverse }
let acncelBtn = UIAlertAction(title: "나가기😆", style: .cancel)
[playOnce, repeat2, loop, repeatBackwards, autoReverse, acncelBtn].forEach{ alert.addAction($0) }
self.present(alert, animated: true)
}
fileprivate func speedFrameAlert() {
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let speedDouble = UIAlertAction(title: "speed_Double", style: .default) { [weak self] _ in self?.animationView.animationSpeed = 2 }
let speedHalf = UIAlertAction(title: "speed_half", style: .default) { [weak self] _ in self?.animationView.animationSpeed = 0.5 }
let endFrame: String = "\(animationView.animation?.endFrame ?? -1)"
// 유의할 점: frame10To90 이후 frame40To140 실행시 이전 값부터 90-140까지만 수행된다.
let frame10To90 = UIAlertAction(title: "10-90 end:\(endFrame)", style: .default) { [weak self] _ in self?.animationView.play(fromFrame: 10, toFrame: 90) }
let frame40To140 = UIAlertAction(title: "40-140 end:\(endFrame)", style: .default) { [weak self] _ in self?.animationView.play(fromFrame: 40, toFrame: 140) }
let acncelBtn = UIAlertAction(title: "나가기😆", style: .cancel)
[speedDouble, speedHalf, frame10To90 ,frame40To140, acncelBtn].forEach { alert.addAction($0) }
self.present(alert, animated: true)
}
fileprivate func progressAlert() {
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let progress_2_6 = UIAlertAction(title: "0.2-0.6", style: .default) { [weak self] _ in self?.animationView.play(fromProgress: 0.2, toProgress: 0.6) }
let progress_4_9 = UIAlertAction(title: "0.4-0.9", style: .default) { [weak self] _ in self?.animationView.play(fromProgress: 0.4, toProgress: 0.9) }
let progress_3_8 = UIAlertAction(title: "0.3-0.8 루프모드 및 컴플리션핸들러 파라미터", style: .default) { [weak self] _ in self?.animationView.play(fromProgress: 0.4, toProgress: 0.9, loopMode: .loop, completion: nil) }
let acncelBtn = UIAlertAction(title: "나가기😆", style: .cancel)
let progress_5 = UIAlertAction(title: "progress_0.5", style: .default) { [weak self] _ in self?.animationView.currentProgress = 0.5 }
let frame_190 = UIAlertAction(title: "frame_190", style: .default) { [weak self] _ in self?.animationView.currentFrame = 190 }
let time_1 = UIAlertAction(title: "time_1", style: .default) { [weak self] _ in self?.animationView.currentTime = 1 }
[progress_2_6, progress_4_9, progress_3_8, progress_5, frame_190, acncelBtn, time_1 ].forEach { alert.addAction($0) }
self.present(alert, animated: true)
}
fileprivate func bind() {
playBtn.rx.tap
.bind { [weak self] _ in self?.animationView.play() }
.disposed(by: bag)
loopSelectBtn.rx.tap
.bind { [weak self] _ in self?.loopAlert() }
.disposed(by: bag)
stopBtn.rx.tap
.bind { [weak self] _ in self?.animationView.stop() }
.disposed(by: bag)
speedFrameSelectBtn.rx.tap
.bind { [weak self] _ in self?.speedFrameAlert() }
.disposed(by: bag)
progressSelectBtn.rx.tap
.bind { [weak self] _ in self?.progressAlert() }
.disposed(by: bag)
}
fileprivate func setupViews() {
// 레이아웃 잡는 코드와 같다.
// animationView.frame = self.view.bounds
// animationView.center = self.view.center
// animationView.contentMode = .scaleAspectFit
[animationView].forEach{ view.addSubview($0) }
[stackView].forEach{ animationView.addSubview($0) }
[playBtn, loopSelectBtn, stopBtn, speedFrameSelectBtn, progressSelectBtn].forEach{
stackView.addArrangedSubview($0)
$0.setTitleColor(.black, for: .normal)
}
animationView.snp.makeConstraints {
$0.edges.equalTo(view.safeAreaLayoutGuide)
}
stackView.snp.makeConstraints {
$0.top.leading.trailing.equalTo(animationView.safeAreaLayoutGuide)
$0.height.equalTo(48)
}
}
}
위의 코드를 천천히 읽어보면 별 다른 설명 없어도 다 이해할 수 있다.
궁금하면 직접 파일 다운받아서 사용해보자.
(update1) Lottie AnimationView width, height 적용이 되지 않는 문제에 대한 고찰
결론 : AnimationView를 사용하는 경우에는 width와 height가 적용되지 않으므로 inset및 offset을 통해서 사이즈를 조정해야 함.
(가정) lottie Json file에 적용된 값들
어떻게 JSON 파일을 만들어져서 배포되는지 모르겠으나, 여러 파일을 확인한 결과 위 처럼 width와 height 부분에 정해져 있는 것을 확인할 수 있음. stackOverflow를 참고해보면 다른 사람들도 동일한 문제를 다수 겪음.
let animationView: AnimationView = .init(name: {Your lottie JSON file name})
animationView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
animationView.snp.makeConstraints {
$0.center.equalTo(view.safeAreaLayoutGuide)
}
하지만 저는 로티 애니메이션을 갈아 끼우더라도 view에서 동일한 사이즈로 나타나길 원함
let animationView: AnimationView = .init(name: {Your lottie JSON file name})
animationView.snp.makeConstraints {
$0.width.equalTo(100)
$0.height.equalTo(100)
$0.center.equalTo(view.safeAreaLayoutGuide)
}
// or
let animationView: AnimationView = .init(name: {Your lottie JSON file name})
animationView.snp.makeConstraints {
$0.center.equalTo(view.safeAreaLayoutGuide).inset(100)
}
(update2) 로티의 이미지 색을 변경하고 싶을 때
dot-lottie 오픈소스 사용시 주의할 점
메모리 10배도 넘게 많이 먹어서 사용하기 꺼려짐
깃에 문의해서 답변을 받긴 했는데, 해당 부분으로 해결되지 않았음.
'apple > iOS, UIKit, Documentation' 카테고리의 다른 글
[iOS/Swift] init과 super.init에 대해서 알아보자. 🤔 (0) | 2022.02.25 |
---|---|
iOS 오픈소스 라이선스 페이지 (뱅크샐러드) (0) | 2022.02.23 |
Showing All Messages Undefined symbol: __swift_FORCE_LOAD_$_XCTestSwiftSupport (0) | 2022.02.18 |
[iOS] TTGTagCollectionView에 대해서 알아보자. (0) | 2022.02.17 |
Swift Protocol (@objc, extension 기본 구현) (0) | 2022.01.31 |