본문 바로가기
이모저모/UIKit

애플 앱 프로필 버튼 만들기

by ARpple 2023. 9. 15.

구현 설계

  1. UINavigationController에 Extension하여 UINavigationBar에 프로필용 이미지 뷰를 적용할 수 있는 코드를 작성한다.
  2. AppManager 전역 싱글톤 인스턴스를 통해 프로필 용 이미지 뷰를 관리한다.

1. 네비게이션 바 코드 작성

1-1. UINavigationController Extension하기

이미지의 크기를 설정하기

→ Const 구조체 설정

extension UINavigationController{
    /// WARNING: Change these constants according to your project's design
    /// 앱 매니저용 싱글톤 인스턴스에 이 UIImageView 정보를 담아 놓아야 한다..!
    private struct Const {
        static let ImageSizeForLargeState: CGFloat = 40
        static let ImageRightMargin: CGFloat = 16
        static let ImageBottomMarginForLargeState: CGFloat = 12
        static let NavBarHeightLargeState: CGFloat = 96.5
    }
}

특정 뷰 컨트롤러에서 프로필 버튼을 넣기

extension UINavigationController{
...
// 특정 뷰 컨트롤러에 Account 매니저를 추가하려함
    @MainActor func insertAccount(){
        guard self.navigationBar.prefersLargeTitles else {
            AppManager.shared.accountLogoView?.removeFromSuperview()
            fatalError("It's not enabled perfersLargeTitles")
        }
        AppManager.shared.accountLogoView?.removeFromSuperview()
        // 유저 계정 이미지가 있으면 그것을 가져오도록 코드를 수정해야함
                **// accountImage와 accountLogoView와 다른 프로퍼티다!**
        let imageView = UIImageView(image: AppManager.shared.**accountImage** ?? UIImage(systemName: "person.circle") )
                // 내부적으로 구현한 메서드
        setConstraints(imageView: imageView)
        // 계정 이미지를 탭하면 바로 계정 설정에 넘어갈 수 있도록 설정한다.
        imageView.isUserInteractionEnabled = true
        imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(Self.goToAccount)))
        AppManager.shared.accountLogoView = imageView
    }
}

특정 뷰 컨트롤러에서 프로필 버튼을 없애기

extension UINavigationController{
...
// 앱 매니저의 슈퍼 뷰에서 없애주고 View를 비워줌
    @MainActor func deleteAccount(){
        AppManager.shared.accountLogoView?.removeFromSuperview()
        AppManager.shared.accountLogoView = nil
    }
}

스크롤 하며 LargeTitle에서 MediumTitle로 변환 대응하기

extension UINavigationController{
...
// 나중에 다른 아이폰에도 적용 가능하게 만들어야함!!
    // 스크롤에 따라 계정 버튼 숨겨주기 기능
    @MainActor func scrollAccountView(nowY: CGFloat){
        let targetHeight:CGFloat = -103
        let normTarget:CGFloat = -143 + 103
        var norm = (nowY - targetHeight) / normTarget
        norm = max(0,min(1,norm))
                // 내부적으로 구현한 계산 프로퍼티
        self.accountViewOpacity = Float(norm)
        self.accountHidden = nowY > targetHeight
    }
}

1-2. 위에 메서드를 구현을 돕기 위한 내부 메서드

내부 Constraint 및 Appearnce 설정 메서드

fileprivate extension UINavigationController{
@MainActor private func setConstraints(imageView: UIImageView){
        self.navigationBar.addSubview(imageView)
        // setup constraints
        imageView.layer.cornerRadius = Const.ImageSizeForLargeState / 2
        imageView.layer.cornerCurve = .circular
        imageView.clipsToBounds = true
        imageView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            imageView.rightAnchor.constraint(equalTo: navigationBar.rightAnchor, constant: -Const.ImageRightMargin),
            imageView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: -Const.ImageBottomMarginForLargeState),
            imageView.heightAnchor.constraint(equalToConstant: Const.ImageSizeForLargeState),
            imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
        ])
    }
}

내부 뷰 스크롤 대응 설정 계산 프로퍼티

fileprivate extension UINavigationController{
@MainActor private var accountHidden:Bool{
        get{ AppManager.shared.accountLogoView?.isHidden ?? false }
        set{ AppManager.shared.accountLogoView?.isHidden = newValue }
    }
    @MainActor private var accountViewOpacity: Float{
        get{ AppManager.shared.accountLogoView?.layer.opacity ?? -1.0 }
        set{ AppManager.shared.accountLogoView?.layer.opacity = newValue }
    }
}

프로필 이미지 선택시 계정 설정 ViewController로 이동

fileprivate extension UINavigationController{
@objc func goToAccount(){
        let nav = UINavigationController(rootViewController: AccountVC())
        self.present(nav,animated: true)
    }
}

2. AppManager (전역 싱글톤)에 이미지 뷰 적용

💡 프로필 정보를 담은 UIImageView를 앱을 사용하는 동안 계속 저장할 공간이면 어떤 기법을 적용해도 상관 없다…
→ NotificationCenter를 사용해도 문제 없다는 의미..!
final class AppManager: NSObject{
        // 프로필 이미지 뷰를 저장하는 프로퍼티
    var accountLogoView: UIImageView?
        // 프로필 이미지를 UserDefaults에 저장하는 프로퍼티
        // 프로필 이미지 뷰를 처음 만들 때, 이 이미지를 먼저 가져온다.
    @DefaultsState(\.profileImage) var accountImage {
        didSet{ accountLogoView?.image = accountImage }
    }
//    var userName: String?
    static let shared = AppManager()
    private override init(){
        super.init()
    }
}

3. 뷰 컨트롤러에 사용

class ViewController: UIViewController{
...
override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.navigationController?.insertAccount()
    }
...
override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.navigationController?.deleteAccount()
    }
}

스크롤 하는 경우

 // UITableViewDeleate, UICollectionViewDeleate도 가능함
extension ViewController: UIScrollViewDelegate{
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        self.navigationController?.scrollAccountView(nowY: nowY)
    }
}

✅ scrollAccountView 메서드는 내부 스크롤 영역을 감지하는 숫자를 기기에 대응할 필요가 있음..!
✅ 프로필 이미지 크기를 기기 별로 대응할 필요가 있음..!

댓글