Skip to content

Commit 1113eaa

Browse files
committed
Added UIKit .onHover + minor consistency improvement | fixed minor Naming fixes in AppKitBackend
- tapGestureRecognizer and longPressGestureRecognizer now get removed if their corresponding handler is set to nil. - Added Hoverable Widget - Added hovertarget creation & update functions - added macCalatalyst as compatible for UISlider since it supports it
1 parent 0556998 commit 1113eaa

File tree

3 files changed

+60
-11
lines changed

3 files changed

+60
-11
lines changed

Examples/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.9
1+
// swift-tools-version: 5.10
22

33
import Foundation
44
import PackageDescription

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,17 +1435,17 @@ public final class AppKitBackend: AppBackend {
14351435
.isActive = true
14361436
child.translatesAutoresizingMaskIntoConstraints = false
14371437

1438-
let tapGestureTarget = NSCustomHoverTarget()
1439-
container.addSubview(tapGestureTarget)
1440-
tapGestureTarget.leadingAnchor.constraint(equalTo: container.leadingAnchor)
1438+
let hoverGestureTarget = NSCustomHoverTarget()
1439+
container.addSubview(hoverGestureTarget)
1440+
hoverGestureTarget.leadingAnchor.constraint(equalTo: container.leadingAnchor)
14411441
.isActive = true
1442-
tapGestureTarget.topAnchor.constraint(equalTo: container.topAnchor)
1442+
hoverGestureTarget.topAnchor.constraint(equalTo: container.topAnchor)
14431443
.isActive = true
1444-
tapGestureTarget.trailingAnchor.constraint(equalTo: container.trailingAnchor)
1444+
hoverGestureTarget.trailingAnchor.constraint(equalTo: container.trailingAnchor)
14451445
.isActive = true
1446-
tapGestureTarget.bottomAnchor.constraint(equalTo: container.bottomAnchor)
1446+
hoverGestureTarget.bottomAnchor.constraint(equalTo: container.bottomAnchor)
14471447
.isActive = true
1448-
tapGestureTarget.translatesAutoresizingMaskIntoConstraints = false
1448+
hoverGestureTarget.translatesAutoresizingMaskIntoConstraints = false
14491449

14501450
return container
14511451
}
@@ -1455,8 +1455,8 @@ public final class AppKitBackend: AppBackend {
14551455
environment: EnvironmentValues,
14561456
action: @escaping (Bool) -> Void
14571457
) {
1458-
let tapGestureTarget = container.subviews[1] as! NSCustomHoverTarget
1459-
tapGestureTarget.hoverChangesHandler = action
1458+
let hoverGestureTarget = container.subviews[1] as! NSCustomHoverTarget
1459+
hoverGestureTarget.hoverChangesHandler = action
14601460
}
14611461

14621462
final class NSBezierPathView: NSView {
@@ -1773,6 +1773,8 @@ final class NSCustomHoverTarget: NSView {
17731773
addTrackingArea(area)
17741774
trackingArea = area
17751775
} else if hoverChangesHandler == nil, let trackingArea {
1776+
// should be impossible at the moment of implementation
1777+
// keeping it to be save in case of later changes
17761778
removeTrackingArea(trackingArea)
17771779
self.trackingArea = nil
17781780
}

Sources/UIKitBackend/UIKitBackend+Control.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ final class TappableWidget: ContainerWidget {
133133
target: self, action: #selector(viewTouched))
134134
child.view.addGestureRecognizer(gestureRecognizer)
135135
self.tapGestureRecognizer = gestureRecognizer
136+
} else if onTap == nil, let tapGestureRecognizer {
137+
child.view.removeGestureRecognizer(tapGestureRecognizer)
138+
self.tapGestureRecognizer = nil
136139
}
137140
}
138141
}
@@ -144,6 +147,9 @@ final class TappableWidget: ContainerWidget {
144147
target: self, action: #selector(viewLongPressed(sender:)))
145148
child.view.addGestureRecognizer(gestureRecognizer)
146149
self.longPressGestureRecognizer = gestureRecognizer
150+
} else if onLongPress == nil, let longPressGestureRecognizer {
151+
child.view.removeGestureRecognizer(longPressGestureRecognizer)
152+
self.onLongPress = nil
147153
}
148154
}
149155
}
@@ -164,6 +170,36 @@ final class TappableWidget: ContainerWidget {
164170
}
165171
}
166172

173+
174+
@available(tvOS, unavailable)
175+
final class HoverableWidget: ContainerWidget {
176+
private var hoverGestureRecognizer: UIHoverGestureRecognizer?
177+
178+
var hoverChangesHandler: ((Bool) -> Void)? {
179+
didSet {
180+
if hoverChangesHandler != nil && hoverGestureRecognizer == nil {
181+
let gestureRecognizer = UIHoverGestureRecognizer(target: self,
182+
action: #selector(hoveringChanged(_:)))
183+
child.view.addGestureRecognizer(gestureRecognizer)
184+
self.hoverGestureRecognizer = gestureRecognizer
185+
} else if hoverChangesHandler == nil, let hoverGestureRecognizer {
186+
// should be impossible at the moment of implementation
187+
// keeping it to be save in case of later changes
188+
child.view.removeGestureRecognizer(hoverGestureRecognizer)
189+
self.hoverGestureRecognizer = nil
190+
}
191+
}
192+
}
193+
194+
@objc
195+
func hoveringChanged(_ recognizer: UIHoverGestureRecognizer) {
196+
switch recognizer.state {
197+
case .began: hoverChangesHandler?(true)
198+
case .ended: hoverChangesHandler?(false)
199+
default: break
200+
}
201+
}
202+
}
167203
@available(tvOS, unavailable)
168204
final class SliderWidget: WrapperWidget<UISlider> {
169205
var onChange: ((Double) -> Void)?
@@ -413,8 +449,19 @@ extension UIKitBackend {
413449
wrapper.onLongPress = environment.isEnabled ? action : {}
414450
}
415451
}
452+
453+
public func createHoverTarget(wrapping child: Widget) -> Widget {
454+
HoverableWidget(child: child)
455+
}
456+
457+
public func updateHoverTarget(_ hoverTarget: any WidgetProtocol,
458+
environment: EnvironmentValues,
459+
action: @escaping (Bool) -> Void) {
460+
let wrapper = hoverTarget as! HoverableWidget
461+
wrapper.hoverChangesHandler = action
462+
}
416463

417-
#if os(iOS) || os(visionOS)
464+
#if os(iOS) || os(visionOS) || targetEnvironment(macCatalyst)
418465
public func createSlider() -> Widget {
419466
SliderWidget()
420467
}

0 commit comments

Comments
 (0)