Skip to content

Commit

Permalink
feat: BBToolTip Action 처리 및 마이그레이션 작업 해요 (#693)
Browse files Browse the repository at this point in the history
* feat: BBBaseToolTipView, BBThumbnailToolTipView 추가
- BBAnimatable 로직 수정
- BBToolTipType xPosition, yPosition 분리
- BBToolTipAction Nested Type 추가

* feat: BBTextToolTipView, BBThumbnailToolTipView 모듈 분리
- BBBaseToolTIpView 내부 drawable Method 추가
- BBToolTip Class 추가
- BBDrawable protocol, extension 제거

* fix: BBToolTip createToolTip Method parameter completionHandler 추가

* feat: BBToolTip 관련 에러처리 로직 수정
- ToolTip 관련 주석 추가

* feat: BBToolTipConfiguration maxWidth, maxHeight Properties 추가
- BBToolTip Layout을 frame 기반으로 로직 수정

* feat: BBTextToolTipView, BBThumbnailToolTipView Width, height 동적으로 정의하기 위해 intrinsicContentSize 재정의
- BBTooltip contentView ContentView intrinsicContentSize 기반으로 로직 수정
- BBToolTipConfiguration maxWidth, maxHeight Properties 제거

* fix: BBDrawable Protocol 제거
- BBToolTip property, Intializer 수정

* fix: MemoriesCalendarPageTitleView 기존 TooltipView 로직 수정
- BBTextToolTipView TouchControl 클릭시 Tooltip 사라지는 애니메이션 로직 제거

* fix: BBAnimatable, BBTextToolTipView, BBThumbnailToolTipView, BBToolTIpView 코드리뷰 반영
- ManagementTableHeaderView ToolTipView 추가
  • Loading branch information
Do-hyun-Kim authored Dec 20, 2024
1 parent 6c4b282 commit 4c8e7e1
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 218 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final public class MemoriesCalendarPageTitleView: BaseView<MemoriesCalendarTitle
private let memoryCountLabel: BBLabel = BBLabel(.body1Regular, textColor: .gray200)
private let tipButton: UIButton = UIButton(type: .system)

private let toolTipView: BBToolTipView = BBToolTipView()
private let toolTipView: BBToolTip = BBToolTip(.monthlyCalendar)

// MARK: - Properties

Expand All @@ -47,18 +47,15 @@ final public class MemoriesCalendarPageTitleView: BaseView<MemoriesCalendarTitle

private func bindOutput(reactor: Reactor) {
reactor.pulse(\.$hiddenTooltipView)
.bind(with: self) {
$1 ? $0.toolTipView.hidePopover()
: $0.toolTipView.showPopover()
}
.bind(to: toolTipView.rx.isHidden)
.disposed(by: disposeBag)
}


public override func setupUI() {
super.setupUI()

self.addSubviews(labelStack, memoryCountLabel, toolTipView)
self.addSubviews(labelStack, memoryCountLabel)
labelStack.addArrangedSubviews(titleLabel, tipButton)
}

Expand All @@ -79,10 +76,6 @@ final public class MemoriesCalendarPageTitleView: BaseView<MemoriesCalendarTitle
$0.size.equalTo(20)
}

toolTipView.snp.makeConstraints {
$0.top.equalTo(tipButton.snp.bottom).offset(4)
$0.leading.equalToSuperview().offset(57.5)
}
}

public override func setupAttributes() {
Expand All @@ -102,9 +95,9 @@ final public class MemoriesCalendarPageTitleView: BaseView<MemoriesCalendarTitle
$0.distribution = .fill
}

toolTipView.hidePopover()
toolTipView.toolTipType = .monthlyCalendar
// toolTipView.anchorPoint = CGPoint(x: 0.3, y: 0)
toolTipView.do {
$0.superview = tipButton
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,23 @@ public final class ManagementTableHeaderReactor {

// MARK: - Action

public enum Action { }
public enum Action {
case didTappedToolTipButton
}


// MARK: - Mutation

public enum Mutation { }
public enum Mutation {
case setToolTipHidden(Bool)
}


// MARK: - State

public struct State { }
public struct State {
@Pulse var isHidden: Bool = false
}


// MARK: - Properties
Expand All @@ -39,4 +45,19 @@ public final class ManagementTableHeaderReactor {
self.initialState = State()
}

public func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .didTappedToolTipButton:
return .just(.setToolTipHidden(!currentState.isHidden))
}
}

public func reduce(state: State, mutation: Mutation) -> State {
var newState = state
switch mutation {
case let .setToolTipHidden(isHidden):
newState.isHidden = isHidden
}
return newState
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public final class ManagementTableHeaderView: BaseView<ManagementTableHeaderReac
private let familyNameLabel: BBLabel = BBLabel(.head1, textColor: .gray200)
private let memberCountLabel: BBLabel = BBLabel(.body1Regular, textColor: .gray400)
private let familyNameEditButton: BBButton = BBButton()

private let familyNameToolTipeView: BBToolTip = BBToolTip(.familyNameEdit)

// MARK: - Properties

Expand Down Expand Up @@ -58,6 +58,9 @@ public final class ManagementTableHeaderView: BaseView<ManagementTableHeaderReac
$0.setImage(DesignSystemAsset.edit.image, for: .normal)
$0.addTarget(self, action: #selector(didTapFamilyNameEditButton(_:event:)), for: .touchUpInside)
}
familyNameToolTipeView.do {
$0.superview = familyNameEditButton
}
}

public override func setupAutoLayout() {
Expand All @@ -75,6 +78,18 @@ public final class ManagementTableHeaderView: BaseView<ManagementTableHeaderReac
}
}

public override func bind(reactor: Reactor) {
familyNameEditButton.rx.tap
.throttle(RxInterval._300milliseconds, scheduler: RxScheduler.main)
.map { Reactor.Action.didTappedToolTipButton }
.bind(to: reactor.action)
.disposed(by: disposeBag)

reactor.pulse(\.$isHidden)
.bind(to: familyNameToolTipeView.rx.isHidden)
.disposed(by: disposeBag)
}

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,52 @@ public typealias BBComponentPresentable = BBComponentShowable & BBComponentClosa
/// **Animate**, **CGAffineTransform**, **CABasicAnimation** 을 활용한 Animation 메서드를 정의하는 Protocol 입니다.
/// 해당 **BBComponentShowable** 프로토콜은 Component 객체를 보여주는 애니메이션을 정의하는 프로토콜입니다.
public protocol BBComponentShowable {
func showPopover(duration: TimeInterval, options: UIView.AnimationOptions, transform: CGAffineTransform, alpha: CGFloat)
func show(duration: TimeInterval, options: UIView.AnimationOptions, transform: CGAffineTransform, alpha: CGFloat)
}

/// **Animate**, **CGAffineTransform**, **CABasicAnimation** 을 활용한 Animation 메서드를 정의하는 Protocol 입니다.
/// 해당 **BBComponentClosable** 프로토콜은 Component 객체를 숨기는 애니메이션을 정의하는 프로토콜입니다.
public protocol BBComponentClosable {
func hidePopover(duration: TimeInterval, options: UIView.AnimationOptions, transform: CGAffineTransform, alpha: CGFloat)
func hide(duration: TimeInterval, options: UIView.AnimationOptions, transform: CGAffineTransform, alpha: CGFloat)
}


//MARK: - Extensions

public extension BBComponentShowable where Self: UIView {
/// showPopover 메서드 호출 시 Popover 애니메이션 효과를 실행합니다.
func showPopover(duration: TimeInterval = 0.3, options: UIView.AnimationOptions = [.curveEaseInOut], transform: CGAffineTransform = CGAffineTransform(scaleX: 0.1, y: 0.1), alpha: CGFloat = 1) {
self.transform = transform
self.alpha = alpha
public extension BBComponentShowable where Self: BBToolTip {
func show(duration: TimeInterval = 0.3, options: UIView.AnimationOptions = [.curveEaseInOut], transform: CGAffineTransform = CGAffineTransform(scaleX: 0.1, y: 0.1), alpha: CGFloat = 1) {

UIView.animate(withDuration: duration, delay: 0, options: options) { [weak self] in
guard let self else { return }
self.transform = CGAffineTransform.identity
self.alpha = 1
guard let contentView else {
assertionFailure("No contentView assigned to BBToolTip")
return
}

superview?.addSubview(contentView)
updateLayout()

UIView.animate(withDuration: duration, delay: 0, options: options) {
self.contentView?.transform = CGAffineTransform.identity
self.contentView?.alpha = 1
}
}
}

public extension BBComponentClosable where Self: UIView {
/// hidePopover 메서드 호출 시 Popover 애니메이션 효과를 제거합니다.
func hidePopover(duration: TimeInterval = 0.3, options: UIView.AnimationOptions = [.curveEaseInOut], transform: CGAffineTransform = CGAffineTransform(scaleX: 0.1, y: 0.1), alpha: CGFloat = 0) {
public extension BBComponentClosable where Self: BBToolTip {
func hide(
duration: TimeInterval = 0.3,
options: UIView.AnimationOptions = [.curveEaseInOut],
transform: CGAffineTransform = CGAffineTransform(scaleX: 0.1, y: 0.1),
alpha: CGFloat = 0
) {

UIView.animate(withDuration: duration, delay: 0, options: options) { [weak self] in
guard let self else { return }
self.transform = transform
self.alpha = alpha
} completion: { _ in
self.transform = CGAffineTransform.identity
}
UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
self.contentView?.transform = transform
self.contentView?.alpha = 0
}, completion: { _ in
self.contentView?.removeFromSuperview()
self.contentView?.transform = .identity
})

self.contentView?.layoutIfNeeded()
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
//
// BBDrawable.swift
// BBBaseToolTipView.swift
// Core
//
// Created by Kim dohyun on 9/19/24.
// Created by 김도현 on 10/27/24.
//

import UIKit

/// **UIBezierPath**, ** CALayer**, **CGMutablePath**을 활용한 draw 메서드를 정의하는 Protocol입니다.
protocol BBDrawable {
func drawToolTip(_ frame: CGRect, type: BBToolTipType, context: CGContext)
func drawToolTipArrowShape(_ frame: CGRect, type: BBToolTipType, path: CGMutablePath)
func drawToolTipBottomShape(_ frame: CGRect, toolTipType: BBToolTipType, cornerRadius: CGFloat, path: CGMutablePath)
func drawToolTipTopShape(_ frame: CGRect, toolTipType: BBToolTipType, cornerRadius: CGFloat, path: CGMutablePath)
}

import SnapKit
import Then

extension BBDrawable {
public class BBBaseToolTipView: UIView {
// MARK: - Properties
public var toolTipType: BBToolTipType

// MARK: - Intializer
public init(toolTipType: BBToolTipType) {
self.toolTipType = toolTipType
super.init(frame: .zero)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

public override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
drawToolTip(rect, type: toolTipType, context: context)
context.restoreGState()
}

/// drawToolTip 메서드 호출 시 **BBToolTipType** 에 해당하는 ToolTip Layout을 **CGContext** 내에서 드로잉 하는 메서드입니다.
///
/// drawToolTip에 frame은 UIView의 **draw(_: )** 메서드에서 호출되고 있습니다.
/// ToolTip Layout을 변경할 경우 **setNeedsDisplay** 메서드를 호출하시면 됩니다.
func drawToolTip(_ frame: CGRect, type: BBToolTipType, context: CGContext) {
// MARK: - Configure
private func drawToolTip(_ frame: CGRect, type: BBToolTipType, context: CGContext) {
let toolTipPath = CGMutablePath()

switch type {
Expand All @@ -40,13 +50,10 @@ extension BBDrawable {
context.setFillColor(type.configure.backgroundColor.cgColor)
context.fillPath()
}

/// drawToolTipArrowShape 메서드 호출 시 ToolTip에 Arrow 모양을 드로잉 하도록 실행합니다.
///
/// **BBToolTipType** 에 따라 Arrow의 위치가 배치됩니다.
func drawToolTipArrowShape(_ frame: CGRect, type: BBToolTipType, path: CGMutablePath) {

private func drawToolTipArrowShape(_ frame: CGRect, type: BBToolTipType, path: CGMutablePath) {
let margin: CGFloat = 16
let arrowTipXPosition = type.xPosition.rawValue * frame.width
let arrowTipXPosition = type.configure.xPosition.rawValue * frame.width
let adjustedArrowTipXPosition = min(max(arrowTipXPosition, margin + type.configure.arrowWidth / 2), frame.width - margin - type.configure.arrowWidth / 2)
let arrowLeft = adjustedArrowTipXPosition - type.configure.arrowWidth / 2
let arrowRight = adjustedArrowTipXPosition + type.configure.arrowWidth / 2
Expand All @@ -62,25 +69,20 @@ extension BBDrawable {
path.addLine(to: CGPoint(x: arrowRight, y: frame.height - type.configure.arrowHeight))
}
}

/// drawToolTipTopShape 메서드 실행 시 ToolTip의 **ContentShape** 영역들을 드로잉 하도록 실행합니다.
///
/// Note: - 해당 메서드는 **BBToolTipVerticalPosition** 이 Top일 경우 실행합니다.
func drawToolTipTopShape(_ frame: CGRect, toolTipType: BBToolTipType, cornerRadius: CGFloat, path: CGMutablePath) {

private func drawToolTipTopShape(_ frame: CGRect, toolTipType: BBToolTipType, cornerRadius: CGFloat, path: CGMutablePath) {
path.addArc(tangent1End: CGPoint(x: frame.maxX, y: toolTipType.configure.arrowHeight), tangent2End: CGPoint(x: frame.maxX, y: frame.maxY + frame.height), radius: cornerRadius)
path.addArc(tangent1End: CGPoint(x: frame.maxX, y: frame.maxY), tangent2End: CGPoint(x: frame.minX, y: frame.maxY), radius: cornerRadius)

path.addArc(tangent1End: CGPoint(x: frame.minX, y: frame.maxY), tangent2End: CGPoint(x: frame.minX, y: toolTipType.configure.arrowHeight), radius: cornerRadius)
path.addArc(tangent1End: CGPoint(x: frame.minX, y: toolTipType.configure.arrowHeight), tangent2End: CGPoint(x: frame.maxX, y: toolTipType.configure.arrowHeight), radius: cornerRadius)
}

/// drawToolTipBottomShape 메서드 실행 시 ToolTip의 **ContentShape** 영역들을 드로잉 하도록 실행합니다.
///
/// Note: - 해당 메서드는 **BBToolTipVerticalPosition** 이 Bottom일 경우 실행합니다.
func drawToolTipBottomShape(_ frame: CGRect, toolTipType: BBToolTipType, cornerRadius: CGFloat, path: CGMutablePath) {
private func drawToolTipBottomShape(_ frame: CGRect, toolTipType: BBToolTipType, cornerRadius: CGFloat, path: CGMutablePath) {
path.addArc(tangent1End: CGPoint(x: frame.maxX, y: frame.height - toolTipType.configure.arrowHeight), tangent2End: CGPoint(x: frame.maxX, y: 0), radius: cornerRadius)
path.addArc(tangent1End: CGPoint(x: frame.maxX, y: 0), tangent2End: CGPoint(x: frame.minX, y: 0), radius: cornerRadius)
path.addArc(tangent1End: CGPoint(x: frame.minX, y: 0), tangent2End: CGPoint(x: frame.minX, y: frame.height - toolTipType.configure.arrowHeight), radius: cornerRadius)
path.addArc(tangent1End: CGPoint(x: frame.minX, y: frame.height - toolTipType.configure.arrowHeight), tangent2End: CGPoint(x: frame.maxX, y: frame.height - toolTipType.configure.arrowHeight), radius: cornerRadius)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// BBTextToolTipView.swift
// Core
//
// Created by 김도현 on 10/27/24.
//

import UIKit

import SnapKit
import Then


public class BBTextToolTipView: BBBaseToolTipView {
// MARK: - Properties
private var contentLabel: BBLabel = BBLabel()

// MARK: - Intializer
public override var intrinsicContentSize: CGSize {
let contentWidth = contentLabel.intrinsicContentSize.width + 32
let contentHeight = contentLabel.intrinsicContentSize.height + toolTipType.configure.arrowHeight + 20
return CGSize(width: contentWidth, height: contentHeight)
}


public override init(toolTipType: BBToolTipType) {
super.init(toolTipType: toolTipType)
setupToolTipUI()
setupToolTipContent()
setupAutoLayout()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Configure
private func setupToolTipUI() {
addSubview(contentLabel)
}

private func setupToolTipContent() {
contentLabel.do {
$0.text = toolTipType.configure.contentText
$0.fontStyle = toolTipType.configure.font
$0.textAlignment = .center
$0.numberOfLines = 0
$0.textColor = toolTipType.configure.foregroundColor
$0.sizeToFit()
}

self.do {
$0.backgroundColor = .clear
}
}

private func setupAutoLayout() {
let position = toolTipType.configure.yPosition
let arrowHeight: CGFloat = toolTipType.configure.arrowHeight
let textPadding: CGFloat = 10

switch position {
case .bottom:
contentLabel.snp.makeConstraints {
$0.horizontalEdges.equalToSuperview().inset(16)
$0.bottom.equalToSuperview().inset((arrowHeight + textPadding))
$0.top.equalToSuperview().inset(textPadding)
}
case .top:
contentLabel.snp.makeConstraints {
$0.horizontalEdges.equalToSuperview().inset(16)
$0.top.equalToSuperview().inset((arrowHeight + textPadding))
$0.bottom.equalToSuperview().inset(textPadding)
}
}
}
}
Loading

0 comments on commit 4c8e7e1

Please sign in to comment.