Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions Koin/Core/Extensions/UIKit/UIViewController+Dropdown.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// UIViewController+Dropdown.swift
// koin
//
// Created by 홍기정 on 11/1/26.
//

import UIKit

// MARK: - UIViewController
extension UIViewController {
var isPresentingDropdown: Bool {
children.contains(where: { $0 is KoinDropdownViewController })
}

@discardableResult
func presentDropdown(
anchor: UIView,
content: UIView & KoinDropdownContentView,
configuration: KoinDropdownConfiguration
) -> KoinDropdownViewController? {
guard !isPresentingDropdown,
anchor.isDescendant(of: view) else { return nil }

view.endEditing(true)
let dropdown = KoinDropdownViewController(
anchor: anchor,
contentView: content,
configuration: configuration
)
addChild(dropdown)
dropdown.view.frame = view.bounds
view.addSubview(dropdown.view)
dropdown.didMove(toParent: self)
guard dropdown.presentDropdown() else {
dropdown.dismissDropdown(animated: false)
return nil
}
return dropdown
}
}

// MARK: - UIView
extension UIView {
@discardableResult
func presentDropdown(
anchor: UIView,
content: UIView & KoinDropdownContentView,
configuration: KoinDropdownConfiguration
) -> KoinDropdownViewController? {
// responder chain을 따라 올라가 이 view를 띄운 가장 가까운 VC를 찾는다.
var responder: UIResponder? = self
while let current = responder {
if let viewController = current as? UIViewController {
return viewController.presentDropdown(
anchor: anchor,
content: content,
configuration: configuration
)
}
responder = current.next
}
return nil
}
}
146 changes: 0 additions & 146 deletions Koin/Core/View/UIKit/KoinDropdown/KoinDropdown.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,30 @@ import UIKit
@MainActor
final class KoinDropdownAnimator {

// MARK: - Metric
enum Metric {
static let presentDuration: TimeInterval = 0.3
static let presentBounce: CGFloat = 0.15
static let dismissDuration: TimeInterval = 0.2
}

private func hiddenTransform(travel: CGFloat) -> CGAffineTransform {
CGAffineTransform(translationX: 0, y: -travel)
// MARK: - Transform
private func hiddenTransform(hiddenOffsetY: CGFloat) -> CGAffineTransform {
CGAffineTransform(
translationX: 0,
y: hiddenOffsetY
)
}

// MARK: - Present
func present(
view: UIView,
travel: CGFloat,
hiddenOffsetY: CGFloat,
completion: (() -> Void)? = nil
) {
view.isHidden = false
view.alpha = 0
view.transform = hiddenTransform(travel: travel)
view.transform = hiddenTransform(hiddenOffsetY: hiddenOffsetY)

UIView.animate(
springDuration: Metric.presentDuration,
Expand All @@ -40,14 +46,15 @@ final class KoinDropdownAnimator {
}
}

// MARK: - Dismiss
func dismiss(
view: UIView,
travel: CGFloat,
hiddenOffsetY: CGFloat,
completion: (() -> Void)? = nil
) {
UIView.animate(springDuration: Metric.dismissDuration) {
view.alpha = 0
view.transform = self.hiddenTransform(travel: travel)
view.transform = self.hiddenTransform(hiddenOffsetY: hiddenOffsetY)
} completion: { _ in
view.isHidden = true
completion?()
Expand Down
44 changes: 40 additions & 4 deletions Koin/Core/View/UIKit/KoinDropdown/KoinDropdownConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,46 @@
import UIKit

struct KoinDropdownConfiguration {
let topPadding: CGFloat
// MARK: - Properties
let direction: Direction
let shadow: Shadow
}

// MARK: - Direction
extension KoinDropdownConfiguration {
/// 각 연관값의 spacing은 anchor와 dropdown 사이 간격이다.
enum Direction {
case down(spacing: CGFloat)
case auto(
downSpacing: CGFloat,
upSpacing: CGFloat
)
}

enum ResolvedDirection {
case down
case up
}

var isAutoDirection: Bool {
if case .auto = direction {
return true
}
return false
}

/// 실제로 연 방향에서 anchor와 dropdown 사이 간격을 반환한다.
func spacing(for resolvedDirection: ResolvedDirection) -> CGFloat {
switch direction {
case let .down(spacing):
return spacing
case let .auto(downSpacing, upSpacing):
return resolvedDirection == .down ? downSpacing : upSpacing
}
}
}

// MARK: - Shadow
extension KoinDropdownConfiguration {
enum Shadow {
case shadow2
Expand Down Expand Up @@ -53,12 +89,12 @@ extension KoinDropdownConfiguration {
}
}

var shadowPadding: UIEdgeInsets {
func shadowPadding(for direction: ResolvedDirection) -> UIEdgeInsets {
let base = max(0, shadow.blur + shadow.spread)
return UIEdgeInsets(
top: 0,
top: direction == .up ? base + max(0, -shadow.offset.y) : 0,
left: base + max(0, -shadow.offset.x),
bottom: base + max(0, shadow.offset.y),
bottom: direction == .down ? base + max(0, shadow.offset.y) : 0,
right: base + max(0, shadow.offset.x)
)
}
Expand Down
15 changes: 15 additions & 0 deletions Koin/Core/View/UIKit/KoinDropdown/KoinDropdownContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// KoinDropdownContentView.swift
// koin
//
// Created by 홍기정 on 11/1/26.
//

import UIKit
import Combine

@MainActor
protocol KoinDropdownContentView: AnyObject {
var dismissTappedPublisher: AnyPublisher<Void, Never> { get }
var height: CGFloat { get }
}
Loading
Loading