관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 인스타그램 썸네일 프로필 UI 구현 방법 본문

UI 컴포넌트 (swift)

[iOS - swift] 인스타그램 썸네일 프로필 UI 구현 방법

jake-kim 2022. 6. 28. 22:19

 

구현한 인스타 썸네일 UI

사용한 프레임워크

  • UI 레이아웃 구현에 편의를 위해 사용

구현 아이디어

  • 동그란 뷰가 여기저기서 많이 사용되고 있으므로, RoundView, RoundImageView를 새로 만들어서 사용
  • 동그란 뷰를 서브클래싱하여 구현
  • 핵심은 온라인 상태를 암시해주는 초록색 뷰의 위치
    • 가장 아래에 있는 UIView에 이미지가 들어갈 containerView와 온라인 상태를 암시하는 greenDotContainerView를 삽입
    • containerView에는 이미지가 들어가는 형태로 구현
  • 바깥 테두리의 그라데이션 효과는, layoutSubviews()에서 CAShapeLayer를 통해 테두리의 radius, width값을 구해서 사용

구현

  • 동그란 뷰 RoundView, RoundImageView
// RoundView.swift

class RoundView: UIView {
  override func layoutSubviews() {
    super.layoutSubviews()
    
    self.clipsToBounds = true
    self.layer.cornerRadius = self.bounds.height / 2.0
  }
}
//  RoundImageView.swift

class RoundImageView: UIImageView {
  override func layoutSubviews() {
    super.layoutSubviews()
    
    self.clipsToBounds = true
    self.layer.cornerRadius = self.bounds.height / 2.0
  }
}
  • 썸네일 뷰 - RoundView를 서브클래싱
import UIKit
import SnapKit
import Then

class ThumbnailView: RoundView {

}
  • Constants 선언
    • imageOuterSpacing: 이미지 뷰와 이 뷰를 감싸는 컨테이너 사이의 간격이며, gradient를 사용하면 안쪽부터 채워지므로 borderWidth만큼 gradient 너비를 정할 것이므로 이 값까지 더함
    • greenDotOuterSpacing: 그린 닷과 이 뷰를 감싸고 있는 컨테이너 사이의 간격
    • greenDotViewInset: 그린 닷의 위치를 지정할때 사용할 값
    • greenDotViewSize: 그릿 닷의 크기
    • gradationWidth: 그라데이션의 크기
  // MARK: Constants
  private enum Const {
    // view
    static let imageOuterSpacing = Self.borderWidth + 4.0 // borderWidth만큼 안쪽으로 바깥 라인이 채워지므로 borderWidth값을 더함
    static let greenDotOuterSpacing = 4.0
    static let greenDotViewInset = UIEdgeInsets(top: 0, left: 0, bottom: 11, right: 11)
    static let greenDotViewSize = CGSize(width: 16, height: 16)
    
    // shapeLayer
    static let gradationWidth = 2.0
  }
  • UI 선언
    • 썸네일 이미지 UI = 이미지 컨테이너 + 이미지 뷰
    • 그린 닷 이미지 UI = 그린닷 컨테이너 + 그린닷
    • 바깥 테두리의 그라데이션 = shapeLayer로 바깥 테두리의 path를 구할 수 있기 때문에 gradationLayer와 같이 선언 (layoutSubviews에서 접근)
  private let containerView = RoundView().then {
    $0.backgroundColor = .clear
    $0.isUserInteractionEnabled = false
    $0.clipsToBounds = true
  }
  private let thumbnailImageView = RoundImageView().then {
    $0.clipsToBounds = true
    $0.contentMode = .scaleAspectFill
  }
  private let greenDotContainerView = RoundView().then {
    $0.isUserInteractionEnabled = false
    $0.backgroundColor = .white
    $0.clipsToBounds = true
    
    let greenDotView = RoundView().then {
      $0.backgroundColor = .green
    }
    $0.addSubview(greenDotView)
    greenDotView.snp.makeConstraints {
      $0.edges.equalToSuperview().inset(Const.greenDotOuterSpacing)
    }
  }
  private var gradientLayer = CAGradientLayer().then {
    $0.colors = [UIColor.red, .systemPink, .yellow].map(\.cgColor)
  }
  private var shapeLayer = CAShapeLayer().then {
    $0.lineWidth = Const.gradationWidth
    $0.strokeColor = UIColor.black.cgColor
    $0.fillColor = UIColor.clear.cgColor
  }
  • 초기화 (레이아웃)
  required init?(coder: NSCoder) {
    fatalError()
  }
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.containerView.layer.addSublayer(self.gradientLayer)
    self.gradientLayer.mask = self.shapeLayer
    
    self.addSubview(self.containerView)
    self.addSubview(self.greenDotContainerView)
    self.containerView.addSubview(self.thumbnailImageView)
    
    self.containerView.snp.makeConstraints {
      $0.edges.equalToSuperview()
    }
    self.thumbnailImageView.snp.makeConstraints {
      $0.edges.equalToSuperview().inset(Const.imageOuterSpacing)
    }
    self.greenDotContainerView.snp.makeConstraints {
      $0.right.bottom.equalToSuperview().inset(Const.greenDotViewInset)
      $0.size.equalTo(Const.greenDotViewSize)
    }
  }
  • 그라데이션 효과 - layoutSubviews()에서 구현
  override func layoutSubviews() {
    super.layoutSubviews()
    guard self.containerView.bounds != .zero else { return }
    
    self.gradientLayer.frame = self.containerView.bounds
    self.shapeLayer.path = UIBezierPath(
      roundedRect: self.containerView.bounds.insetBy(dx: Const.gradationWidth, dy: Const.gradationWidth),
      cornerRadius: self.bounds.height / 2.0
    ).cgPath
  }

* 전체 코드: https://github.com/JK0369/ExInstagramProfile

Comments