관리 메뉴

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

[iOS - swift] 3. CALayer 마스킹 활용 - 뷰 합치는 방법 본문

iOS 응용 (swift)

[iOS - swift] 3. CALayer 마스킹 활용 - 뷰 합치는 방법

jake-kim 2023. 2. 14. 22:52

1. CALayer 마스킹 활용 - 마스킹하는 기본 방법 (mask, .evenOdd)

2. CALayer 마스킹 활용 - 특정 뷰 음영 효과 주는 방법

3. CALayer 마스킹 활용 - 뷰 합치는 방법 <

뷰 합치기 아이디어

  • 이미지 뷰 두 개를 준비
  • 이미지 뷰 두 개를 width와 위치를 동일하게 constraint하고난 후 각각 path, mask를 사용하여 자르기

구현

* 예제에 나온 코드는 코드로 UI 작성의 편의를 위해 SnapKit 사용

  • 구현은 사용하는쪽에서 이미지 두 개와, ratio를 주입하여 뷰를 생성하면 자동으로 이미지 두 개가 나누어지도록 구현
    • 아래에서 SlantView 구현
      * slant: 경사진
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let slantView = SlantView(firstImage: .init(named: "1"), secondImage: .init(named: "2"), ratio: 0.9)
        view.addSubview(slantView)
        slantView.snp.makeConstraints {
            $0.size.equalTo(350)
            $0.center.equalToSuperview()
        }
    }
}
  • ratio 값의 의미

ratio = 0.9
ratio = 0.5

  • SlantView 선언
final class SlantView: UIView {
    private let ratio: Double
    private let firstImageView: UIImageView
    private let secondImageView: UIImageView
    
}
  • 생성자
    • setUp()은 아래에서 계속 구현
init(firstImage: UIImage?, secondImage: UIImage?, ratio: Double) {
    self.ratio = ratio
    self.firstImageView = UIImageView(image: firstImage)
    self.secondImageView = UIImageView(image: secondImage)

    super.init(frame: .zero)

    setUp()
}
required init?(coder: NSCoder) {
    fatalError()
}
  • 이미지뷰의 contentMode과 layout 설정
private func setUp() {
    backgroundColor = .clear

    firstImageView.contentMode = .scaleAspectFill
    secondImageView.contentMode = .scaleAspectFill

    addSubview(firstImageView)
    addSubview(secondImageView)

    firstImageView.snp.makeConstraints {
        $0.top.leading.bottom.equalToSuperview()
        $0.width.equalToSuperview()
    }
    secondImageView.snp.makeConstraints {
        $0.top.trailing.bottom.equalToSuperview()
        $0.width.equalToSuperview()
    }
}
  • 이미지뷰 마스킹
    • 마스킹은 이미지뷰의 bounds가 정해졌을때 이 값을 사용하여 마스킹할 수 있으므로 layoutSubviews에서 호출
override func layoutSubviews() {
    super.layoutSubviews()
    mask()
}
  • mask() 구현
    • mask의 기본 3단계 (path로 남길 부분을 그리기 -> shapeLayer에 path 대입 -> layer.mask에 대입)
    • 핵심 부분은 path를 그리는 것이고 이 부분의 코드가 길기 때문에 따로 메소드로 빼서 구현
private func mask() {
    // 1. path 인스턴스로 경로 정보 획득
    let firstPath = getFirstImagePath()
    let secondPath = getSecondImagePath()
    
    // 2. shapeLayer.path에 위 path 인스턴스 대입
    let firstShapeLayer = CAShapeLayer()
    firstShapeLayer.path = firstPath.cgPath
    
    let secondShapeLayer = CAShapeLayer()
    secondShapeLayer.path = secondPath.cgPath
    
    // 3. layer.mask에 shapeLayer를 대입
    firstImageView.layer.mask = firstShapeLayer
    secondImageView.layer.mask = secondShapeLayer
}
  • path 그리기
    • 좌측 상단에서부터 시계방향으로 그리기
private func getFirstImagePath() -> UIBezierPath {
    let path = UIBezierPath()
    let bs = firstImageView.bounds
    path.move(to: bs.origin)
    path.addLine(to: .init(x: bs.maxX * ratio, y: bs.minY))
    path.addLine(to: .init(x: bs.minX + bs.width * (1 - ratio), y: bs.maxY))
    path.addLine(to: .init(x: bs.minX, y: bs.maxY))
    path.close()
    return path
}

private func getSecondImagePath() -> UIBezierPath {
    let path = UIBezierPath()
    let bs = secondImageView.bounds
    path.move(to: .init(x: bs.minX + bs.width * ratio, y: bs.minY))
    path.addLine(to: .init(x: bs.maxX, y: bs.minY))
    path.addLine(to: .init(x: bs.maxX, y: bs.maxY))
    path.addLine(to: .init(x: bs.minX + bs.width * (1 - ratio), y: bs.maxY))
    path.close()
    return path
}

(완료)

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

Comments