I have a question about how UIKit expects us to handle interaction events at scale.
From what I understand so far:
For UIControls (UIButton, UISwitch, UITextField, etc.), we explicitly register with addTarget(_:action:for:).
For gestures, we add UIGestureRecognizer instances to views.
For UIView subclasses, we can override touch methods like touchesBegan/touchesEnded.
All of this must be done on the main thread, since UIKit isn’t thread-safe.
Now here’s my main concern
If I have a complex UI with hundreds or thousands of widgets, am I expected to perform these registrations individually for each widget and each high-level event (tap, long press, editing changed, etc.)?
Or does UIKit provide a more centralized mechanism?
In short: Is per-widget, per-event registration the “normal” UIKit approach, or are there best practices for scaling event handling without writing thousands of addTarget or addGestureRecognizer calls?
Thanks!
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I’m trying to detect a double-tap action on a UIButton. There seem to be two possible approaches:
Using a UITapGestureRecognizer with numberOfTapsRequired = 2.
Using the .touchDownRepeat event of UIControl.Event.
What is the recommended approach for reliably handling double-taps on UIButton? Are there any practical differences in terms of behavior, performance, or best practices between these two methods?
Additionally, I noticed that UIControl.Event defines a large set of events (like .editingChanged, .valueChanged, etc.).
Can all these events be applied to any UIControl subclass such as UIButton, or are they only valid for specific controls like UITextField, UISlider, etc.?
If not all events are supported by all controls, what is the rationale behind exposing them under a shared UIControl.Event enum?
Thanks in advance!
I’m working with UIButton and I’d like to register multiple UIControl.Events (e.g. .touchUpInside, .touchDown, .touchCancel, .primaryActionTriggered) using the same selector.
For example:
button.addTarget(self,
action: #selector(handleButtonEvent(_:forEvent:)),
for: [.touchUpInside, .touchDown, .touchCancel, .primaryActionTriggered])
@objc func handleButtonEvent(_ sender: UIButton, forEvent event: UIEvent) {
// How do I tell which UIControl.Event triggered this?
}
From my understanding:
If I use the single-parameter version (@objc func handleButtonEvent(_ sender: UIButton)), I can’t distinguish which event fired.
If I use the two-parameter version with UIEvent, I can inspect touch.phase or event.type, but that feels indirect.
Questions:
Is there a recommended way to directly know which UIControl.Event caused the selector to fire?
Is sharing a single selector across multiple control events considered a good practice, or is it more common to register separate selectors per event?
Would appreciate guidance on what Apple recommends here.
I’m working with UIButton and finding different examples for event handling. Currently, I have a single action method like this, which receives the sender and the UIEvent:
@objc func buttonHandler(_ sender: UIButton, forEvent event: UIEvent) {
if let touches = event.allTouches, let touch = touches.first {
switch touch.phase {
case .began: print("TouchDown")
case .ended:
if sender.bounds.contains(touch.location(in: sender)) {
print("TouchUpInside")
} else {
print("TouchUpOutside")
}
case .cancelled: print("TouchCancel")
default: break
}
}
if event.type == .presses {
print("PrimaryActionTriggered")
}
}
Is this considered best/recommended practice in UIKit, or should I use separate selector methods for each event type (e.g. .touchDown, .touchUpInside, .touchUpOutside) using addTarget(_:action:for:)?
Are there any advantages or disadvantages to using a single handler with UIEvent versus multiple selectors for UIControlEvents?
Thanks in advance!
I'm working on an iOS app that requires an @mention system in a UITextView, similar to those in apps like Twitter or Slack. Specifically, I need to:
Detect @ Symbol and Show Dropdown: When the user types "@", display a dropdown (UITableView or similar) below the cursor with a list of mentionable users, filtered as the user types.
Handle Selection: Insert the selected username as a styled mention (e.g., blue text).
Smart Backspace Behavior: Ensure backspace deletes an entire mention as a single unit when the cursor is at its end, and cancels the mention process if "@" is deleted.
I've implemented a solution using UITextViewDelegate textViewDidChange(_:) to detect "@", a UITableView for the dropdown, and NSAttributedString for styling mentions. For smart backspace, I track mention ranges and handle deletions accordingly. However, I’d like to know:
What is Apple’s recommended approach for implementing this behavior?
Are there any UIKit APIs that simplify this, for proving this experience like smart backspace or custom text interactions?
I’m using Swift/UIKit. Any insights, sample code, or WWDC sessions you’d recommend would be greatly appreciated!
Edit: I am adding the ViewController file to demonstrate the approach that I m using.
import UIKit
// MARK: - Dummy user model
struct MentionUser {
let id: String
let username: String
}
class ViewController: UIViewController, UITextViewDelegate, UITableViewDelegate, UITableViewDataSource {
// MARK: - UI Elements
private let textView = UITextView()
private let mentionTableView = UITableView()
// MARK: - Data
private var allUsers: [MentionUser] = [...]
private var filteredUsers: [MentionUser] = []
private var currentMentionRange: NSRange?
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupTextView() // to setup the UI
setupDropdown() // to setup the UI
}
// MARK: - UITextViewDelegate
func textViewDidChange(_ textView: UITextView) {
let cursorPosition = textView.selectedRange.location
let text = (textView.text as NSString).substring(to: cursorPosition)
if let atRange = text.range(of: "@[a-zA-Z0-9_]*$", options: .regularExpression) {
let nsRange = NSRange(atRange, in: text)
let query = (text as NSString).substring(with: nsRange).dropFirst()
currentMentionRange = nsRange
filteredUsers = allUsers.filter {
$0.username.lowercased().hasPrefix(query.lowercased())
}
mentionTableView.reloadData()
showMentionDropdown()
} else {
hideMentionDropdown()
currentMentionRange = nil
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text.isEmpty, let attributedText = textView.attributedText {
if range.location == 0 { return true }
let attr = attributedText.attributes(at: range.location - 1, effectiveRange: nil)
if let _ = attr[.mentionUserId] {
let fullRange = (attributedText.string as NSString).rangeOfMentionAt(location: range.location - 1)
let mutable = NSMutableAttributedString(attributedString: attributedText)
mutable.deleteCharacters(in: fullRange)
textView.attributedText = mutable
textView.selectedRange = NSRange(location: fullRange.location, length: 0)
textView.typingAttributes = [
.font: textView.font ?? UIFont.systemFont(ofSize: 16),
.foregroundColor: UIColor.label
]
return false
}
}
return true
}
// MARK: - Dropdown Visibility
private func showMentionDropdown() {
guard let selectedTextRange = textView.selectedTextRange else { return }
mentionTableView.isHidden = false
}
private func hideMentionDropdown() {
mentionTableView.isHidden = true
}
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredUsers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "@\(filteredUsers[indexPath.row].username)"
return cell
}
// MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
insertMention(filteredUsers[indexPath.row])
}
// MARK: - Mention Insertion
private func insertMention(_ user: MentionUser) {
guard let range = currentMentionRange else { return }
let mentionText = "\(user.username)"
let mentionAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.systemBlue,
.mentionUserId: user.id
]
let mentionAttrString = NSAttributedString(string: mentionText, attributes: mentionAttributes)
let mutable = NSMutableAttributedString(attributedString: textView.attributedText)
mutable.replaceCharacters(in: range, with: mentionAttrString)
let spaceAttr = NSAttributedString(string: " ", attributes: textView.typingAttributes)
mutable.insert(spaceAttr, at: range.location + mentionText.count)
textView.attributedText = mutable
textView.selectedRange = NSRange(location: range.location + mentionText.count + 1, length: 0)
textView.typingAttributes = [
.font: textView.font ?? UIFont.systemFont(ofSize: 16),
.foregroundColor: UIColor.label
]
hideMentionDropdown()
}
}
// MARK: - Custom Attributed Key
extension NSAttributedString.Key {
static let mentionUserId = NSAttributedString.Key("mentionUserId")
}
In UIKit, UIButton provides a configuration property which allows us to create and customize a UIButton.Configuration instance independently (on a background thread or elsewhere) and later assign it to a UIButton instance. This separation of configuration and assignment is very useful for clean architecture and performance optimization.
Questions:
Is this configuration-style pattern (creating a configuration object separately and assigning it later) available or planned for other UIKit components such as UILabel, UITextField, UISlider, etc.?
Similarly, in AppKit on macOS, are there any components (e.g. NSButton, NSTextField) that support a comparable configuration object mechanism that can be used the same way — constructed separately and assigned to the view later?
This would help in building consistent configuration-driven UI frameworks across Apple platforms. Any insight or official guidance would be appreciated.
I'm working on a macOS application that needs to query the list of available printers using NSPrinter.printerNames. For performance reasons, I'd like to perform this operation on a background thread.
However, since NSPrinter is part of AppKit, and AppKit is generally not thread-safe unless explicitly stated, I want to confirm:
Is it safe to call NSPrinter.printerNames from a background thread?
I couldn’t find explicit guidance in the documentation regarding the thread-safety of printerNames, so any clarification or best practices would be appreciated.
Thanks in advance!
Note: I tested this api on a background thread in code and it did not give any error.
I have a UITextField in my application, and I want to detect all the keys uniquely to perform all relevant task. However, there is some problem in cleanly identifying some of the keys.
I m not able to identify the backspace key press in the textField(_:shouldChangeCharactersIn:replacementString:) method.
Also I don't know how to detect the Caps Lock key.
I am intending to so this because I want to perform some custom handling for some keys. Can someone help me with what is the way of detecting it under the recommendation from apple. Thanks in advance.
Note: checking for replacementString parameter in shouldChangeCharactersIn method for empty does not help for backspace detection as it overlaps with other cases.
In a UIKit application, removing a view from the hierarchy is straightforward—we simply call myView.removeFromSuperview(). This not only removes myView from the UI but also deallocates any associated memory.
Now that I'm transitioning to SwiftUI, I'm struggling to understand the recommended way to remove a view from the hierarchy, given SwiftUI's declarative nature.
I understand that in SwiftUI, we declare everything that should be displayed. However, once a view is rendered, what is the correct way to remove it? Should all UI elements be conditionally controlled to determine whether they appear or not?
Below is an example of how I’m currently handling this, but it doesn’t feel like the right approach for dynamically removing a view at runtime.
Can someone guide me on the best way to remove views in SwiftUI?
struct ContentView: View {
@State private var isVisible = true
var body: some View {
VStack {
if isVisible { // set this to false to remove TextView?
Text("Hello, SwiftUI!")
.padding()
}
Button("Toggle View") {
...
}
}
}
}
In UIKit, certain events like a button tap can be simulated using:
button.sendActions(for: .touchUpInside)
This allows us to trigger the button’s action programmatically.
However, in SwiftUI, there is no direct equivalent of sendActions(for:) for views like Button. What is the recommended approach to programmatically simulate a SwiftUI button tap and trigger its action?
Is there an alternative mechanism to achieve this(and for other events under UIControl.event) , especially in scenarios where we want to test interactions or trigger actions without direct user input?
I wanted to perform simulation in my application as a self tour guide for my user. For this I want to programatically simulate various user interaction events like button click, keypress event in the UITextField or moving the cursor around in the textField. These are only few examples to state, it can be any user interaction event or other events.
I wanted to know what is the apple recommendation on how should these simulations be performed? Is there something that apple offers like creating an event which can be directly executed for simulations. Is there some library available for this purpose?
I have a UIKit application and I have some swiftUI views(like button widget etc) that I m using in this application which are added as a subview using UIHostingController.
I wanted to understand what is the right way as per the apple recommendation on how to perform some updates on these views, since the UIKit and SwiftUI have a different way of operating.
In a pure swiftUI application we use the @State variables which when modified the view are re-rendered. However, in an UIKit application we can directly modify the widget property like color or font from the object.
So, my question is should I get the hostingController object from the swiftUI view and then perform any update on that UIKit view. Is this the right way?
If not, what is the correct way? can someone provide a detailed explanation?
I understand two key concepts from desktop platforms:
Screen Mirroring – The same content is displayed on both the primary and external screens.
Screen Extension – The external display shows different content that complements what's on the main screen.
My question pertains to the second point: Is it possible to extend the display on iOS and iPadOS devices?
I'm referring to this Apple documentation, which explains how to extend content from an iOS/iPadOS device to an external display.
I tested this in a sample iOS Xcode project. In the iOS Simulator, I was able to detect an "external display" and present a separate UIWindow on it. However, when I tried the same on a real device (iPhone 15 connected to a MacBook Pro via cable), the external display connection was not detected.
I’d like to confirm whether screen extension is possible on a real iOS device. From my research, it appears that extension is only supported on iPadOS via Stage Manager, but I want to verify if there’s any way to achieve this on an iPhone. If so, are there any known apps that currently utilize extended display functionality on iOS?
If extension is not possible on iOS, what does the documentation mentions iOS?
I am working on a SwiftUI project where I need to dynamically update the UI by adding or removing components based on some event. The challenge is handling complex UI structures efficiently while ensuring smooth animations and state management.
Example Scenario:
I have a screen displaying a list of items.
When a user taps an item, additional details (like a subview or expanded section) should appear dynamically.
If the user taps again, the additional content should disappear.
The UI should animate these changes smoothly without causing unnecessary re-renders.
My Current Approach:
I have tried using @State and if conditions to toggle views, like this:
struct ContentView: View {
@State private var showDetails = false
var body: some View {
VStack {
Button("Toggle Details") {
showDetails.toggle()
}
if showDetails {
Text("Additional Information")
.transition(.slide) // Using animation
}
}
.animation(.easeInOut, value: showDetails)
}
}
However, in complex UI scenarios where multiple components need to be shown/hidden dynamically, this approach is not maintainable and could cause performance issues. I need help with the below questions.
Questions:
State Management: Should I use @State, @Binding, or @ObservedObject for handling dynamic UI updates efficiently?
Best Practices: What are the best practices for structuring SwiftUI views to handle dynamic updates without excessive re-renders?
Performance Optimization: How can I prevent unnecessary recomputations when updating only specific UI sections?
Animations & Transitions: What is the best way to apply animations smoothly while toggling visibility of multiple components?
Advanced Approaches: Are there better techniques using @EnvironmentObject, ViewBuilder, or even GeometryReader for dynamically adjusting UI layouts?
Any insights, code examples, or resources would be greatly appreciated.
I have an xcode project which has both cpp and swift code. In one of my usecase I am passing primitive type variables from swift to cpp by reference( primitives types list here as per the new cpp-swift interop documentation)
swift code:
// primitive check code:Bool
var x : Bool = true
// When we are passing a variable as a Reference, we need to use explicitly use'&'
student.PassBoolAsReferenceType (&x) // interop call to cpp code
print (x)
Cpp code:
void
Student::PassBoolAsReferenceType(bool &pValue) noexcept
{
std::cout << pValue << std::endl;
pValue = false;
}
The above code fails during compilation with no clear error message "Command SwiftCompile failed with a nonzero exit code"
However, all the other primitive types that I tested worked for the above code like Int, Float, Double etc. Only the Bool interop fails. Can someone explain why is it not possible for bool? I m using the new interop introduced in swift 5.9.