9.
The Mouse
Written by Andy Pereira
Just like a keyboard, the mouse is a toolset that you may not have encountered if you’ve focused solely on iOS development. Catalyst makes working with the mouse easy since it provides a familiar pattern, and it gives you a great amount of control in the process.
In this chapter, you’ll learn to implement PointerStyleProvider and UIHoverGestureRecognizer to show a shadow effect or to change the default mouse pointer, when hovering over a diary entry in the sample app. You’ll also learn to accessorize your mouse pointers using UIPointAccessory that’s new in iOS 15. You’ll look at the differences between iOS/iPadOS and touch targets in macOS.
Getting Started
Open the starter project for this chapter. Build and run for iPadOS. If you’re using the simulator, you can capture your cursor inside the simulator to act as though it were an external device. Do this by selecting Capture Pointer in the simulator toolbar:
Add a few entries and then move your mouse around the app. Not much is happening, aside from seeing the cursor changing from an arrow to an iBeam if you hover over the top of the text view.
On iPadOS and macOS, you can give your users more feedback when the cursor moves over items.
Pointer Style Providers
On iPadOS, your cursor behaves a bit different from that of macOS. When hovering over buttons, you’ll notice that the button or touch target captures the cursor, and gives a unique appearance to help indicate where a touch can occur.
Open EntryTableViewController.swift and the following block of code inside supplementaryDataSource, just before the line that returns reusableView:
if let button = reusableView.viewWithTag(1) as? UIButton {
button.pointerStyleProvider = { button, effect, _ in
var rect = button.bounds
rect = button.convert(
rect, to: effect.preview.target.container
)
let style = UIPointerStyle(
effect: effect, shape: .roundedRect(rect)
)
return style
}
}
Here, you’ve added a very simple UIButton.PointerStyleProvider. Added in iOS 13.4, it allows you to define custom styles and shapes for your cursor on touch areas. Here, you simply take the button’s shape, have the pointer style take the entire shape of it.
Build and run, and capture the mouse. Then hover your mouse over the Camera button to see the effect take place. You see the button fill with a color, like below:
It’s a subtle effect, but things like this make a difference to your users, even if they aren’t thinking about it.
Adding Effects With Hover Gesture Recognizer
Next, you’ll use UIHoverGestureRecognizer to add some more effects for iPadOS, as well as macOS.
To start, open EntryTableViewCell.swift. Add the following to the end of awakeFromNib.
addHoverGesture()
Then add the following to the class:
private func addHoverGesture() {
let hoverGesture = UIHoverGestureRecognizer(
target: self,
action: #selector(hovering(_:))
)
contentView.addGestureRecognizer(hoverGesture)
}
This method does the following:
- It creates a hover gesture recognizer, setting
selfas the target, and an action that you’ll set up in the next step. - It adds the hover gesture to the content view of the cell.
Next, add the following to the same class:
@objc private func hovering(
_ recognizer: UIHoverGestureRecognizer
) {
// 1
guard !isSelected else { return }
// 2
switch recognizer.state {
// 3
case .began, .changed:
backgroundColor = .secondarySystemBackground
// 4
case .ended:
backgroundColor = .none
default:
break
}
}
Here, you can see how you respond to the hover events:
- This ensures that nothing happens if you’ve already selected the cell.
- The gesture recognizer passes itself as a parameter to this method. You check the state of the gesture recognizer.
- If the hover is starting (
.began) or moving around (.changed), you change the background color of the cell. - Once the mouse is no longer hovering over the view (
.ended), you remove the color you set.
Build and run. Then add a few entries and hover your mouse over the table view. You now see that all the cells, except for the cell you selected, change background color while the mouse is over it. Once the mouse leaves the view, it changes back to having no background color.
There’s one more place where you can add a hover gesture to give your app more finesse.
Open EntryTableViewController.swift and add the following to supplementaryDataSource, just before it returns reusableView:
let hoverGesture = UIHoverGestureRecognizer(
target: self,
action: #selector(self.hovering(_:))
)
reusableView.addGestureRecognizer(hoverGesture)
Once again, this code creates a hover gesture and adds it to the reusable view that the collection view’s header returns.
Next, add the following to the class:
@objc private func hovering(
_ recognizer: UIHoverGestureRecognizer
) {
#if targetEnvironment(macCatalyst)
switch recognizer.state {
case .began, .changed:
NSCursor.pointingHand.set()
case .ended:
NSCursor.arrow.set()
default:
break
}
#endif
}
Here, you respond to the event over the Camera button and change the cursor to the pointing hand when above it. It goes back to the default arrow when you mouse away from it. While this code you added in the first section was only for iPadOS, this new code complements your UI on macOS, all without having to write too much code.
Build and run on macOS. Then hover over the Camera button in the Entry view. It now changes cursor shapes as you mouse over it.
A Few Notes on Elements and Haptics
Keep in mind that the interface guidelines for iOS state that you should keep touch targets for interactive elements to a minimum of 44pt × 44pt. The cursor gives you a lot more flexibility when your app is running on macOS. If you’re creating macOS-specific UI elements, you can use smaller elements if it makes sense for your app.
You also have access to haptic feedback on both platforms. You could easily set up haptic feedback when mousing over an area if your app requires it. Keep Apple’s guidelines in mind, and remember not to overstimulate your users.
Accessorize
New to iOS 15, UIPointAccessory allows you to add custom views to the cursor, providing additional context for users. You can use predefined shapes, or custom bezier paths.
Open EntryTableViewController.swift. In supplementaryDataSource, replace the existing if let block with:
if let button = reusableView.viewWithTag(1) as? UIButton {
button.pointerStyleProvider = { button, effect, _ in
var rect = button.bounds
rect = button.convert(
rect, to: effect.preview.target.container
)
let style = UIPointerStyle(
effect: effect, shape: .roundedRect(rect)
)
style.accessories = [
.init(.path(.plusPath), position: .bottomRight)
]
return style
}
}
Here, you add a new accessory to the UIPointerStyle. In this case, it is a custom bezier path, a plus symbol, which shows at the bottom right of the cursor.
Build and run on iPadOS. Then hover over the Camera button to see the new accessory.
Key Points
- You can use
PointerStyleProviderto respond to cursor events on iPadOS. - Add hovering to views by adding a hover gesture recognizer to give your user more visual feedback.
- Hover gesture recognizers work similarly to other gesture recognizers.
- You can access
NSCursoron macOS with Catalyst. - Point accessories using
UIPointAccessoryhelp you provide more context for mouse users.
Where to Go From Here?
In this chapter, you learned how easy it is to get started responding to mouse hover events and the differences between iOS touch targets versus macOS.
You can find Apple’s Human Interface Guidelines for the mouse and trackpad, here.
You can read more about UIHoverGestureRecognizer from Apple’s website.
To learn more about the possible states for a gesture recognizer, refer to the Apple documentation.