프로토콜 지향 프로그래밍을 이용한 프로젝트를 진행해보자 (2)

이번에는 기획자가 간단한 기능을 하나 추가해 달라고 합니다. 기존에는 이미지만 볼 수 있다면, 이번에는 영상도 볼 수 있게 해달라고 하네요. 일단은 영상을 재생시킬수 있는 뷰를 하나 만들어주겠습니다.

final class VideoView: UIView {
    private var url: String?
    private var player = AVPlayer()
    private var playerLayer: AVPlayerLayer?
    
    private lazy var videoBackgroundView: UIView = {
        let view = UIView()
        view.backgroundColor = .systemGray
        view.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(view)
        return view
      }()
    
    init(){
        super.init(frame: .zero)
        NSLayoutConstraint.activate([
            self.videoBackgroundView.leftAnchor.constraint(equalTo: self.leftAnchor),
            self.videoBackgroundView.rightAnchor.constraint(equalTo: self.rightAnchor),
            self.videoBackgroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            self.videoBackgroundView.topAnchor.constraint(equalTo: self.topAnchor),
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        self.playerLayer?.frame = self.videoBackgroundView.bounds
    }
}

extension VideoView {
    func load(url: String) {
        self.url = url
        guard let url = URL(string: self.url ?? "") else { return }
        let item = AVPlayerItem(url: url)
        self.player.replaceCurrentItem(with: item)
        let playerLayer = AVPlayerLayer(player: self.player)
        playerLayer.frame = self.videoBackgroundView.bounds
        playerLayer.videoGravity = .resizeAspectFill
        self.playerLayer = playerLayer
        self.videoBackgroundView.layer.addSublayer(playerLayer)
        self.player.play()
    }
}

그리고 데이터 모델에 비디오/이미지 타입을 넣어줍시다.

struct Content {
    enum MediaType {
        case image
        case video
    }
    
    var type: Content.MediaType
    var url: String
    var description: String
}

이제 각 뷰의 셀이 모델 타입에 따라 비디오/이미지 뷰를 넣어줘야 합니다. 저희는 코드의 중복을 피하기 위해 MediaContainer이라는 프로토콜로 셀에 들어갈 UI들은 만들어둔 상태입니다. 여기서 수정을 하면 해당 프로토콜을 채택하고 객체들에게도 적용이 되죠. 이미지뷰와 비디오 뷰는 둘다 UIView를 상속받았기에 그 둘을 둘 다 사용할 수 있게 media를 UIView타입으로 두고, extension으로 content의 타입에 따라 이미지/비디오 뷰가 리턴되도록 하였습니다.

protocol MediaContainer {
    var content: Content? { get set }
    var media: UIView { get }
    var note: UILabel { get }
    
    var videoView: VideoView { get }
    var mediaImageView: UIImageView { get }
    
    func contentUpdate()
}

extension MediaContainer {
     var media: UIView {
         guard let content = content else { return UIImageView() }
        switch content.type {
        case .video:
            return videoView
        case .image:
            return mediaImageView
        }
    }
    
    func contentUpdate() {
        if let content = content {
            switch content.type {
            case .video:
                videoView.load(url: content.url)
            case .image:
                mediaImageView.load(url: content.url)
            }
            self.note.text = content.description
        }
    }
}

이제 MediaContainer를 채택하고 있는 뷰들은 모두다 동일하게 적용이 된 것을 볼 수 있습니다.

 

 

전체 소스코드: https://github.com/Kim-Junhwan/POP_Gallry

 

GitHub - Kim-Junhwan/POP_Gallry: 프로토콜 지향 프로그래밍을 이용한 사진첩 모드 , 리스트 모드를 지원

프로토콜 지향 프로그래밍을 이용한 사진첩 모드 , 리스트 모드를 지원하는 사진 앱. Contribute to Kim-Junhwan/POP_Gallry development by creating an account on GitHub.

github.com