커스텀 액티비티 인디케이터
iOS 앱 개발에서 사용자에게 작업이 진행중임을 알리는 것은 중요합니다.
기본 제공되는 액티비티 인디케이터가 존재하지만 때론 앱의 디자인과 더 잘 어울리는 커스텀 액티비티 인디케이터를 만들어야 할 경우가 많습니다.
네트워크 통신을 진행할 때 통신중 또는 로딩중인 경우 또는 어떤 작업(오래 걸리는 작업)을 진행중일 경우 사용자에게 알려줘야합니다.
그렇지 않으면 사용자는 앱이 멈춘건지, 로딩중인건지, 기다려야하는지 판단하기 힘듭니다.
이럴 때 ActivityIndicator는 사용자에게 프로그램이 정확히 작업중이라는것을 알 수 있게 해줍니다.
이런 커스텀 액티비티 인디케이터를 어떻게 만들었고 네트워킹과 어떻게 연결하여 사용하였는지 기록해보겠습니다.
앞서 해당 프로젝트에서는 Alamofire와 Rxswift를 활용하였습니다.
1. 우선 액티비티 인디케이터를 관리할 클래스를 생성하였습니다.
해당 클래스의 이름은 Loading입니다.
네트워크 모듈과 Loading 클래스가 속해있는 Core모듈은 서로 분리되어 있습니다.
backgroundColor는 액티비티 인디케이터가 회전중일 경우 배경 색을 약간 흐려지게 만들어 UI처리를 위한 컬러입니다.
2. 액티비티 인디케이터를 정의합니다.
이는 Loading 클래스 내부에 존재합니다. 저는 이미지를 사용하였습니다.
우선 UIView객체를 생성하고, 화면의 크기를 가져옵니다.
이후 인디케이터의 가로와 세로 크기를 화면 넓이의 4/1로 설정하고, 인디케이터가 화면의 중앙에 위치하도록 합니다.
이는 side, x, y 변수에 저장됩니다.
view의 배경색을 설정하고 이미지를 가져와 이미지뷰에 설정하고, 생성한 view에 추가합니다.
3. 액티비티 인디케이터를 화면에 띄웁니다.
public static func start(isFullScreen: Bool = true, stopTouch: Bool = true) {
DispatchQueue.main.async {
if let window: UIWindow = UIApplication.shared.windows.filter({$0.isKeyWindow}).first {
var found: Bool = false
for subViews in window.subviews {
if subViews.tag == 1 {
found = true
}
}
if !found {
if isFullScreen {
let screen: CGRect = UIScreen.main.bounds
indicator.frame = CGRect(x: 0, y: 0, width: screen.width, height: screen.height)
for subview in indicator.subviews {
let x = (screen.width / 2) - (subview.frame.width / 2)
let y = (screen.height / 2) - (subview.frame.height / 2)
subview.frame = CGRect(x: x, y: y, width: subview.frame.width, height: subview.frame.height)
}
} else {
let screen: CGRect = UIScreen.main.bounds
let side = screen.width / 4
let x = (screen.width / 2) - (side / 2)
let y = (screen.height / 2) - (side / 2)
indicator.frame = CGRect(x: x, y: y, width: side, height: side)
for subview in indicator.subviews {
subview.frame = CGRect(x: side / 4, y: side / 4, width: side / 2, height: side / 2)
}
}
if(stopTouch) {
indicator.isUserInteractionEnabled = true
}
for subView in indicator.subviews {
subView.layer.add(animation, forKey: nil)
}
window.addSubview(indicator)
UIView.animate(withDuration: fade, delay: 0.5, animations: {
self.indicator.alpha = 1.0
})
}
}
}
}
액티비티 인디케이터를 UIWindow에 추가합니다. 특정 ViewController에 추가하는 것이 아닌 Window에 추가합니다.
또 하나 파라미터로 stopTouch라는 변수를 받게 됩니다.
이 파라미터가 true일 경우 indicator.isUserInteractionEnabled = true로 설정해 인디케이터를 가진 뷰가 터치 이벤트를 받도록 함으로써 통신중일 경우엔 유저의 터치 이벤트가 작동하지 않도록 설정할 수 있습니다.
3. 액티비티 인디케이터를 정지합니다.
간단한 코드이니 설명은 넘어가겠습니다.
4. 네트워크 통신과 연결
인디케이터 작동에 필요한 요소를 전부 작성했으니 네트워크 통신과 연결해야합니다.
제 프로젝트에서는 response가 필요한 모든 http 통신을 다음 메서드로 사용합니다.
public func request<T: Decodable>(_ urlConvertible: URLRequestConvertible) -> Single<T>
커스텀한 네트워크 레이어입니다.
RxSwift의 Single을 사용합니다.
실제 네트워크 통신은 Single.create 내부에서 진행됩니다.
public func request<T: Decodable>(_ urlConvertible: URLRequestConvertible) -> Single<T> {
return Single.create { single in
Loading.start()
let request = self.session
.request(urlConvertible,
interceptor: self.authInterceptor)
.validate(statusCode: 200 ..< 300)
.responseDecodable(of: T.self) { response in
/.......
Loading.stop()
}
return Disposables.create {
request.cancel()
}
}
}
Single.create 내부의 구문을 보시면 네트워크 통신을 시작하기 전에 Loading.start()로 액티비티 인디케이터를 작동시킵니다.
그 후 모든 네트워크 통신이 끝난 후 성공, 실패 구분 없이 Loading.stop()으로 인디케이터의 작동을 중지시킵니다.
이렇게 되면 어떤 화면에서 어떤 객체가 네트워크 통신이 진행중이더라도 액티비티 인디케이터가 작동하게 됩니다.
만약 어떠한 API통신을 할 때 액티비티 인디케이터가 작동하지 않아야 한다면 requestNoActi 처럼 메서드를 하나 더 만들어 주면 될 것입니다.
여기까지 커스텀 ActivityIndicator를 제작해본 경험이였습니다.
|