툴팁 기능
- Tip의 위치 조정 가능할 것 (위, 아래, 왼쪽, 오른쪽, 중간)
- Label의 Padding 변경 가능할 것
툴팁 구현 아이디어
구현
1. TriangleView
TriangleView가 다시 그려지는 것과 같은 업데이트되는 시점에 BezierPath로 삼각형을 그려주었습니다.
그리고 Tip의 위치가 변경됨에 따라 다시 그려지도록 했습니다.
// Tip의 Y축 위치 조정
enum TipYPosition {
case top
case bottom
}
// Tip의 X축 위치 조정
enum TipXPosition {
case left(constant: CGFloat)
case right(constant: CGFloat)
case center
}
final class TriangleView: UIView {
var color: UIColor = .white
// 팁의 Y축이 변경되면 다시 그려지도록 함
var tipYPosition: TipYPosition = .bottom {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false // 뷰 전체 또는 일부가 투명한 경우 false로 설정해야한다고 합니다. (false로 설정 안하니까 TriangleView의 색상이 정상적으로 안나왔습니다.)
// 애플문서 참고 - https://developer.apple.com/documentation/uikit/uiview/1622622-isopaque
}
init(frame: CGRect, tipYPosition: TipYPosition = .bottom) {
self.tipYPosition = tipYPosition
super.init(frame: frame)
self.isOpaque = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
if tipYPosition == .top {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: rect.height))
path.addLine(to: CGPoint(x: rect.width / 2, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: rect.height))
path.close()
color.set()
path.fill()
} else {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width / 2, y: rect.height))
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.close()
color.set()
path.fill()
}
}
}
2. PaddingLabel
Label의 Padding을 조정 기능을 추가한 Custom PaddingLabel을 만들겠습니다.
@IBDesignable
final class PaddingLabel: UILabel {
@IBInspectable var topInset: CGFloat = 8.0
@IBInspectable var bottomInset: CGFloat = 8.0
@IBInspectable var leftInset: CGFloat = 16.0
@IBInspectable var rightInset: CGFloat = 16.0
// 지정된 사각형에 레이블의 텍스트나 그림자를 그립니다.
override func drawText(in rect: CGRect) {
let padding = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: rect.inset(by: padding))
}
// PaddingLabel의 본질적인 크기 설정합니다.
override var intrinsicContentSize: CGSize {
let padding = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
var contentSize = super.intrinsicContentSize
contentSize.width += padding.left + padding.right
contentSize.height += padding.top + padding.bottom
return contentSize
}
}
- drawText에서 PaddingLabel의 텍스트의 크기를 재정의
- 텍스트의 크기가 패딩을 포함하도록 변경되었기 때문에 기존 UILabel 패딩까지 포함하는 크기로 intrinsicContentSize도 재정의
3. ContainerView
Tip의 위치가 변경됨에 따라 다시 그려지도록 했습니다.
그리고 backgroundColor 변경 시 툴팁의 색상이 변경되도록 backgroundColor를 재정의 했습니다.
class ToolTipView: UIView {
var paddingLabel: PaddingLabel = {
let view = PaddingLabel()
view.text = "안녕하세요?"
view.numberOfLines = 0
view.textColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private var trianleView: TriangleView = {
var view = TriangleView(frame: .zero, tipYPosition: .bottom)
return view
}()
/*
ToolTipView의 backgroundColor 변경 시 containerView의 전체 배경이 아닌
툴팁 모양의 색깔이 변경되도록 함
*/
override var backgroundColor: UIColor? {
get {
return paddingLabel.backgroundColor
}
set {
paddingLabel.backgroundColor = newValue
trianleView.color = newValue ?? .white
}
}
// 팁의 Y축 위치 조정
var tipYPosition: TipYPosition = .bottom {
didSet {
updateAutoLayout()
trianleView.tipYPosition = tipYPosition
}
}
// 팁의 X축 위치 조정
var tipXPosition: TipXPosition = .center {
didSet {
updateAutoLayout()
}
}
override init(frame: CGRect) {
self.trianleView = TriangleView(frame: frame, tipYPosition: self.tipYPosition)
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
self.trianleView = TriangleView(frame: .zero, tipYPosition: self.tipYPosition)
super.init(coder: coder)
setup()
}
private func setup() {
self.trianleView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(paddingLabel)
self.addSubview(trianleView)
updateAutoLayout()
}
private func layoutTopTip() {
switch tipXPosition {
case .center:
trianleView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
case .left(let constant):
trianleView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: constant).isActive = true
case .right(constant: let constant):
trianleView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: constant).isActive = true
}
NSLayoutConstraint.activate([
trianleView.widthAnchor.constraint(equalToConstant: 20),
trianleView.heightAnchor.constraint(equalToConstant: 20),
trianleView.topAnchor.constraint(equalTo: self.topAnchor),
paddingLabel.topAnchor.constraint(equalTo: trianleView.bottomAnchor),
paddingLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
paddingLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
paddingLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
private func layoutBottomTip() {
switch tipXPosition {
case .center:
trianleView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
case .left(let constant):
trianleView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: constant).isActive = true
case .right(constant: let constant):
trianleView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: constant).isActive = true
}
NSLayoutConstraint.activate([
paddingLabel.topAnchor.constraint(equalTo: self.topAnchor),
paddingLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
paddingLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
paddingLabel.bottomAnchor.constraint(equalTo: trianleView.topAnchor),
trianleView.widthAnchor.constraint(equalToConstant: 20),
trianleView.heightAnchor.constraint(equalToConstant: 20),
trianleView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
private func removeAutoLayout() {
self.removeConstraints(self.constraints)
}
private func updateAutoLayout() {
removeAutoLayout()
if tipYPosition == .top {
layoutTopTip()
} else {
layoutBottomTip()
}
}
func update(text: String) {
paddingLabel.text = text
}
}
참고
반응형
'IOS Swift' 카테고리의 다른 글
[iOS - Swift] Deprecated 표시 하는 법 (0) | 2024.08.25 |
---|---|
What's new in Screen Time API (2) - WWDC2022 (0) | 2024.04.12 |
Screen Time API (1) - WWDC2021 (0) | 2024.04.11 |
로그인 정보 자동완성으로 사용자 편의성 높이기(feat. Swift Password AutoFill) (4) | 2024.02.20 |
빌드 환경에 따라 분기처리하기(feat: xcconfig) (0) | 2024.02.05 |