LNPopupView.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. //
  2. // LNPopupView.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/11/14.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. enum LNPopupViewHeight {
  11. case auto
  12. case percent(CGFloat)
  13. case height(CGFloat)
  14. }
  15. class LNPopupView: UIView {
  16. let container = UIView()
  17. var containerHeight: LNPopupViewHeight = .auto
  18. var touchOutsideToCancel = true
  19. override init(frame: CGRect) {
  20. super.init(frame: frame)
  21. setupViews()
  22. }
  23. func showIn(_ targetView: UIView? = nil) {
  24. if let window = targetView as? UIWindow {
  25. window.addSubview(self)
  26. } else if let view = targetView?.viewController?.view {
  27. view.addSubview(self)
  28. } else if let window = UIView.appKeyWindow {
  29. window.addSubview(self)
  30. } else {
  31. return
  32. }
  33. snp.makeConstraints { make in
  34. make.directionalEdges.equalToSuperview()
  35. }
  36. moveToHiddenPosition()
  37. layoutIfNeeded()
  38. moveToShowupPosition()
  39. backgroundColor = .clear
  40. UIView.animate(withDuration: 0.2) {
  41. self.backgroundColor = .black.withAlphaComponent(0.4)
  42. self.layoutIfNeeded()
  43. }
  44. }
  45. func dismiss() {
  46. endEditing(true)
  47. moveToHiddenPosition()
  48. UIView.animate(withDuration: 0.2) {
  49. self.backgroundColor = .clear
  50. self.layoutIfNeeded()
  51. } completion: { [weak self] _ in
  52. guard let self else { return }
  53. self.removeFromSuperview()
  54. }
  55. }
  56. required init?(coder: NSCoder) {
  57. fatalError("init(coder:) has not been implemented")
  58. }
  59. }
  60. extension LNPopupView {
  61. private func setupViews() {
  62. let bg = UIView()
  63. bg.onTap { [weak self] in
  64. guard let self else { return }
  65. if touchOutsideToCancel {
  66. self.dismiss()
  67. } else {
  68. endEditing(true)
  69. }
  70. }
  71. insertSubview(bg, at: 0)
  72. bg.snp.makeConstraints { make in
  73. make.directionalEdges.equalToSuperview()
  74. }
  75. container.backgroundColor = .white
  76. container.layer.cornerRadius = 20
  77. container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
  78. addSubview(container)
  79. let dismissView = UIView()
  80. container.addSubview(dismissView)
  81. dismissView.snp.makeConstraints { make in
  82. make.directionalEdges.equalToSuperview()
  83. }
  84. dismissView.onTap { [weak self] in
  85. guard let self else { return }
  86. endEditing(true)
  87. }
  88. LNEventDeliver.addObserver(self)
  89. }
  90. private func moveToHiddenPosition() {
  91. container.snp.remakeConstraints { make in
  92. make.leading.trailing.equalToSuperview()
  93. make.top.equalTo(self.snp.bottom)
  94. switch containerHeight {
  95. case .percent(let percent):
  96. make.height.equalToSuperview().multipliedBy(percent)
  97. case .height(let height):
  98. make.height.equalTo(height)
  99. case .auto: break
  100. }
  101. }
  102. }
  103. private func moveToShowupPosition() {
  104. container.snp.remakeConstraints { make in
  105. make.leading.trailing.bottom.equalToSuperview()
  106. switch containerHeight {
  107. case .percent(let percent):
  108. make.height.equalToSuperview().multipliedBy(percent)
  109. case .height(let height):
  110. make.height.equalTo(height)
  111. case .auto: break
  112. }
  113. }
  114. }
  115. private func onKeyboardShowup(_ keyboardHeight: CGFloat) {
  116. container.snp.remakeConstraints { make in
  117. make.leading.trailing.equalToSuperview()
  118. switch containerHeight {
  119. case .percent(let percent):
  120. make.height.equalToSuperview().multipliedBy(percent)
  121. case .height(let height):
  122. make.height.equalTo(height)
  123. case .auto: break
  124. }
  125. make.bottom.equalToSuperview().offset(-keyboardHeight)
  126. }
  127. }
  128. }
  129. extension LNPopupView: LNKeyboardNotify {
  130. func onKeybaordWillShow(curInput: UIView?, keyboardHeight: CGFloat) {
  131. guard let curInput, curInput.isDescendant(of: self) == true else { return }
  132. let view = (curInput as? UITextInput)?.visiableView ?? curInput
  133. let frame = view.convert(view.bounds, to: self)
  134. let offset = bounds.height - frame.maxY - keyboardHeight - 16
  135. if offset < 0 {
  136. onKeyboardShowup(-offset)
  137. }
  138. }
  139. func onKeybaordShow(curInput: UIView?, keyboardHeight: CGFloat) {
  140. guard curInput?.isDescendant(of: self) == true else { return }
  141. layoutIfNeeded()
  142. }
  143. func onKeybaordWillHide(curInput: UIView?) {
  144. guard curInput?.isDescendant(of: self) == true else { return }
  145. moveToShowupPosition()
  146. }
  147. func onKeybaordHide(curInput: UIView?) {
  148. guard curInput?.isDescendant(of: self) == true else { return }
  149. layoutIfNeeded()
  150. }
  151. }