Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 클린 코드
- ribs
- Refactoring
- 스위프트
- 리팩토링
- map
- swift documentation
- Xcode
- swiftUI
- RxCocoa
- HIG
- Human interface guide
- Clean Code
- uitableview
- Protocol
- clean architecture
- combine
- 리펙토링
- 리펙터링
- uiscrollview
- UITextView
- UICollectionView
- ios
- rxswift
- tableView
- collectionview
- MVVM
- 애니메이션
- Observable
- SWIFT
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 동그란 썸네일 이미지 구현 방법, 그라데이션 테두리 (UIBezierPath, CAGradientLayer, CAShapeLayer) 본문
UI 컴포넌트 (swift)
[iOS - swift] 동그란 썸네일 이미지 구현 방법, 그라데이션 테두리 (UIBezierPath, CAGradientLayer, CAShapeLayer)
jake-kim 2022. 5. 14. 22:41* 이 글보다 더 깔끔하게 UI를 구현하고, 온라인 상태를 암시해주는 썸네일을 구현한 포스팅 글은 이 링크 참고
구현 아이디어
- 원에 강아지 넣기
- UIView안에 UIImageView를 넣고, UIImageView의 autolayout을 통해 superview와 일정 간격 떨어뜨려서 흰색 여백을 생성
- 테두리 그라데이션
- UIImageView의 superview인 containerView.layer에 CAGradientLayer()를 넣어서 그라데이션 입력
- 그라데이션 색상은 CAGradientLayer()에서 설정하고, 테두리 윤곽선을 따라서 그려지는 레이아웃은 CAShapeLayer()를 통해 레이아웃을 구해서 사용
원에 강아지 넣기
- VC 준비
import UIKit
class ViewController: UIViewController {
}
- Contant 준비
- thumbnailSize: 썸네일의 width, height 값 (테두리까지 합한 너비 길이)
- borderWidth: 테두리의 굵기
- spacing: 강아지 이미지와 containerView사이의 간격
private enum Constant {
static let thumbnailSize = 100.0
static let thumbnailCGSize = CGSize(width: Constant.thumbnailSize, height: Constant.thumbnailSize)
static let borderWidth = 2.0
static let spacing = 4.0
}
- containerView와 imageView 준비
- cornerRadius값은 길이의 반이되면 정확히 원이 되므로, 길이의 반이 되도록 구현
- imageView의 길이는 spacing에 의하여 줄어들기 때문에 전체 길이에서 spacing의 2재만큼뺀 길이라고 생각
private let containerView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = Constant.thumbnailSize / 2.0
view.layer.borderWidth = Constant.borderWidth
view.layer.borderColor = UIColor.green.cgColor
return view
}()
private let imageView: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "dog")
view.layer.cornerRadius = (Constant.thumbnailSize - Constant.spacing * 2) / 2.0
view.clipsToBounds = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
- 레이아웃
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.containerView)
self.containerView.addSubview(self.imageView)
NSLayoutConstraint.activate([
self.containerView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
self.containerView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
self.containerView.heightAnchor.constraint(equalToConstant: Constant.thumbnailSize),
self.containerView.widthAnchor.constraint(equalToConstant: Constant.thumbnailSize),
])
NSLayoutConstraint.activate([
self.imageView.leftAnchor.constraint(equalTo: self.containerView.leftAnchor, constant: Constant.spacing),
self.imageView.rightAnchor.constraint(equalTo: self.containerView.rightAnchor, constant: -Constant.spacing),
self.imageView.bottomAnchor.constraint(equalTo: self.containerView.bottomAnchor, constant: -Constant.spacing),
self.imageView.topAnchor.constraint(equalTo: self.containerView.topAnchor, constant: Constant.spacing),
])
}
그라데이션 넣기
- 구현하기에 앞서 주의할 점
- gradient의 영역은 containerView의 외부로 뻗어나가지 않고, 내부로 뻗어나가므로 내부 spacing 처리에 주의
- 그라데이션 layer를 containerView에 넣을것이기 때문에, containerView에 존재하던 layer관련 설정 삭제
private let containerView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
// layer 관련 코드 삭제 (3줄)
view.layer.cornerRadius = Constant.thumbnailSize / 2.0
view.layer.borderWidth = Constant.borderWidth
view.layer.borderColor = UIColor.green.cgColor
return view
}()
- 그라데이션의 종류는 linear를 사용하여 구현
* 그라데이션의 개념 (linear, radial, conic)은 이전 포스팅 글 참고
- 그라데이션을 넣을때 테두리 윤곽선을 알기위해서 뷰가 런타임에 표출된 상태여야 구할 수 있기 때문에 viewDidLayoutSubviews()메소드에서 구현
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
- 이미 CAGreadientLayer가 속해 있을때는 다시 그라데이션을 넣지 않도록 설정
guard
self.containerView.bounds != .zero,
self.containerView.layer.sublayers?.contains(where: { $0 is CAGradientLayer }) == false
else { return }
- gradientLayer 생성
let gradient = CAGradientLayer()
gradient.frame = CGRect(origin: CGPoint.zero, size: Constant.thumbnailCGSize)
gradient.colors = [UIColor.blue, UIColor.green].map(\.cgColor)
- containerView의 테두리를 알기 위해서 CAShapeLayer와 UIBVezierPath 사용
- UIBezierPath에서는 insetBy를 통해 containerView
let shape = CAShapeLayer()
shape.lineWidth = Constant.borderWidth
shape.path = UIBezierPath(
roundedRect: self.containerView.bounds.insetBy(dx: Constant.borderWidth, dy: Constant.borderWidth),
cornerRadius: Constant.thumbnailSize / 2.0
).cgPath
shape.strokeColor = UIColor.black.cgColor
shape.fillColor = UIColor.clear.cgColor
gradient.mask = shape
- containerView에 삽입
self.containerView.layer.addSublayer(gradient)
* Tip)
- CAGradientLayer과 CAShapeLayer를 전역에 선언해놓고, viewDidLayoutSubviews()에서 frame값만 바꾸어주면 더욱 단순화가 가능
// 전역에 선언
private var gradientLayer: CAGradientLayer = {
let layer = CAGradientLayer()
layer.colors = Color.gradationColors.map(\.cgColor)
return layer
}()
private var shapeLayer: CAShapeLayer() = {
let layer = CAShapeLayer()
layer.lineWidth = Metric.borderWidth
layer.strokeColor = Color.black.cgColor
layer.fillColor = Color.clear.cgColor
return layer
}()
// 한번만 실행
self.imageContainerView.layer.addSublayer(self.gradientLayer)
self.gradientLayer.mask = self.shapeLayer
// viewDidLayoutSubviews 메소드와 cell에서 shouldShowGradientLayer값이 변경될때 didSet에서 아래 메소드 호출 - frame값만 업데이트
private func updateGradationLayerIfNeeded() {
guard
self.shouldShowGradientLayer,
self.containerView.bounds != .zero
else { return }
self.gradientLayer.frame = self.containerView.bounds
self.shapeLayer.path = UIBezierPath(
roundedRect: self.containerView.bounds.insetBy(dx: Constant.borderWidth, dy: Constant.borderWidth),
cornerRadius: Constant.containerViewCornerRadius
).cgPath
}
- UITableViewCell과 같은 곳은 셀이 화면에 보였다가 안보이는 경우, 셀의 레이아웃이 다시 그려지는데 셀이 저 위 커스텀뷰 자체를 가지고 있을때 CALayer의 frame값이 사라지므로 gradientLayer가 사라지는 버그가 존재
- layoutSubviews에서만이 아닌 셀이 다시 그려질때도 updateGradationLayerIfNeeded()메소드를 호출할것
'UI 컴포넌트 (swift)' 카테고리의 다른 글
Comments