Skip to content

Instantly share code, notes, and snippets.

@ryohey
Created August 4, 2023 05:29
Show Gist options
  • Save ryohey/12bd5fa92d41d3920d5be67626495ad0 to your computer and use it in GitHub Desktop.
Save ryohey/12bd5fa92d41d3920d5be67626495ad0 to your computer and use it in GitHub Desktop.
import SwiftUI
import UIKit
/// UIKit でジェスチャを取得するための透明な View
///
/// overlay で利用する
/// 参考 https://stackoverflow.com/a/57943387/1567777
struct ClearLongPressGestureView: UIViewRepresentable {
let onChanged: (Bool) -> Void
final class Coordinator: NSObject, UIGestureRecognizerDelegate {
let onChanged: (Bool) -> Void
init(onChanged: @escaping (Bool) -> Void) {
self.onChanged = onChanged
}
public func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer) -> Bool {
true
}
@objc func didLongPress(_ gesture: UILongPressGestureRecognizer) {
print("didLongPress\(gesture.state.rawValue)")
switch gesture.state {
case .possible:
break
case .began:
onChanged(true)
case .ended, .cancelled, .failed:
onChanged(false)
default:
break
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(onChanged: onChanged)
}
func makeUIView(context: UIViewRepresentableContext<ClearLongPressGestureView>) -> UIView {
let view = UIView()
view.backgroundColor = .clear
let longPress = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.didLongPress))
longPress.minimumPressDuration = 0
longPress.delaysTouchesBegan = false
longPress.delegate = context.coordinator
view.addGestureRecognizer(longPress)
return view
}
func updateUIView(_: UIView, context _: UIViewRepresentableContext<ClearLongPressGestureView>) {}
}
import SwiftUI
/// 厳密に frame を当たり判定とした ButtonStyle
///
/// タップ時に少し透明にする
struct ExactTapButtonStyle: PrimitiveButtonStyle {
enum Position {
case overlay
case background
}
/// タップ判定の位置
/// ボタンを入れ子にするときには親に .background, 子に .overlay を指定すること
let position: Position
@State private var isPressing: Bool = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.opacity(isPressing ? 0.7 : 1)
.background {
if position == .background {
TransparentTapArea(isPressing: $isPressing) { _ in
configuration.trigger()
}
}
}
.overlay {
if position == .overlay {
TransparentTapArea(isPressing: $isPressing) { _ in
configuration.trigger()
}
}
}
}
}
/// タップ判定を行うための透明な View
private struct TransparentTapArea: View {
@Binding var isPressing: Bool
let action: (CGPoint) -> Void
var body: some View {
GeometryReader { proxy in
Color.clear
.contentShape(Rectangle())
.overlay {
// onLongPressGesture を使うと ScrollView のスクロールを阻害するため
// UIKit でタップ判定を行う
ClearLongPressGestureView { value in
isPressing = value
}
}
.onTapGesture { location in
if proxy.size.contains(location) {
action(location)
}
}
}
}
}
private extension CGSize {
func contains(_ point: CGPoint) -> Bool {
point.x >= 0
&& point.y >= 0
&& point.x < width
&& point.y < height
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment