The Maps app on macOS always zooms the same way (two fingers up to zoom in, two fingers down to zoom out) when the shift key is held down, regardless of whether natural scrolling is turned off. I'd like to replicate this same behavior using UIPanGestureRecognizer, but the translation is always inverted whenever I disable natural scrolling. How can I detect if natural scrolling is enabled or disabled when the pan gesture is detected?
UIKit
RSS for tagConstruct and manage graphical, event-driven user interfaces for iOS or tvOS apps using UIKit.
Posts under UIKit tag
179 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
For a UIKit app based on scenes (UIScene), is it safe to reference UserDefaults in code that is executed from UIApplicationDelegate/application(_: didFinishLaunchingWithOptions:) ?
I've read that in iOS 15, there were undocumented scenarios involving app prewarming that would cause UserDefaults reads to fail within a window of time after device reboots, as described at https://christianselig.com/2024/10/beware-userdefaults/
The failure mode is that an app would be released, and months later, a small fraction of users would report failures consistent with UserDefaults reads unexpectedly returning nil, causing a loss of data. The user experience is bad, and debugging this behavior is then challenging because of how rarely it occurs.
Apple's https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app/about_the_app_launch_sequence#3894431 seems to suggest that prewarming only executes an app "up until, but not including when main() calls UIApplicationMain(_:_:_:_:), but https://stackoverflow.com/questions/71025205/ios-15-prewarming-causing-appwilllaunch-method-when-prewarm-is-done documents that UIApplicationDelegate/application(_: didFinishLaunchingWithOptions:) has in fact been observed executing during app prewarming in scene-based apps.
So, my question: In an app based on scenes, if I'd like to reference UserDefaults within UIApplicationDelegate/application(_: didFinishLaunchingWithOptions:), when is it safe to do this?
I'm guessing the answer is one of these:
Never.
Only in apps that don't support scenes.
Only in iOS 16 or later.
Only in IOS 17 or later.
Is it guaranteed safe to reference UserDefaults in UIWindowSceneDelegate/scene(_:willConnectTo:options:) or later?
Is there documentation from Apple regarding this issue?
Thank you.
I've got a UIKit app with a collapsible trailing-edge child view controller, implemented sort of like UISplitViewController but it's got a bunch of custom behavior - moving to the bottom edge in portrait orientation, etc. It exposes a couple of different app functions via a UITabBar on the bottom edge on iOS.
When I run the app on visionOS, that tab bar transforms to a leading-edge ornament. This would be great, but that means it tries to overlap the trailing-edge content of its parent view controller, which isn't ideal.
Is there a way to get the tab bar to lay out on the trailling edge of the child view controller? Or can I create a custom ornament that has the same auto-expand behavior as the tab bar, where it shows a vertical column of icons that expands to show titles when you're gazing at it?
I have a record button that either starts or stops a recording using the default action. When the user is recording, I want to add a custom action to discard the recording instead of saving it. That all works fine with the following code:
if isRecording {
recordButton.accessibilityCustomActions = [
.init(name: String(localized: "discard recording"), actionHandler: { [weak delegate] _ in
delegate?.discardRecording()
return true
})
]
recordButton.accessibilityLabel = String(localized: "stop recording", comment: "accessibility label")
} else {
recordButton.accessibilityCustomActions = []
recordButton.accessibilityLabel = String(localized: "start recording", comment: "accessibility label")
}
The problem I have is that when a user chose "discard recording", it becomes the default selected action again the next time the user records, and instead of stopping and saving the recording, the user might accidentally discard the next one as well.
How can I programmatically reset the selected action on this recordButton to the default action?
I've got a UIKit app and I want to add some buttons in a top-edge window ornament. I'm looking at the WWDC23 talk Meet UIKit for Spatial Computing, and it does exactly what I think I want to do:
extension EditorViewController {
func showEditingControlsOrnament() {
let ornament = UIHostingOrnament(sceneAlignment: .bottom, contentAlignment: .center) {
EditingControlsView(model: controlsViewModel)
.glassBackgroundEffect()
}
self.ornaments = [ornament]
editorView.style = .edgeToEdge
}
}
But the thing I really want to know is what is in EditingControlsView. Is it a toolbar? How do you make a toolbar in SwiftUI without something to attach the .toolbar modifier to?
New in iOS 17.5 is UIImpactFeedbackGenerator/impactOccurred(at:), which generates haptic feedback at a specific screen location.
https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator/4403143-impactoccurred
Which devices support this?
How does this work if the Taptic Engine has a fixed physical location?
I am trying to build my app with Xcode16 on iOS18, once app is going background PIP is enable but whenever app is coming back to foreground by tapping on PIP, black screen appears(video is playing in background). It only happening with build created by Xcode16 and running on iOS18.
It working fine with build created by Xcode15.4.
My first thought on it that somehow PIP is not stoping.
I have a project that currently has data saved locally and I'm trying to get it to sync over multiple devices.
Currently basic data is syncing perfectly fine, but I'm having issues getting the images to convert to data. From what I've researched it because I'm using a UIImage to convert and this caches the image
It works fine when there's only a few images, but if there's several its a pain
The associated code
func updateLocalImages() {
autoreleasepool {
let fetchRequest: NSFetchRequest<Project> = Project.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "converted = %d", false)
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Project.statusOrder?.sortOrder, ascending: true), NSSortDescriptor(keyPath: \Project.name, ascending: true)]
do {
let projects = try viewContext.fetch(fetchRequest)
for project in projects {
currentPicNumber = 0
currentProjectName = project.name ?? "Error loading project"
if let pictures = project.pictures {
projectPicNumber = pictures.count
for pic in pictures {
currentPicNumber = currentPicNumber + 1
let picture : Picture = pic as! Picture
if let imgData = convertImage(picture: picture) {
picture.pictureData = imgData
}
}
project.converted = true
saveContext()
}
}
} catch {
print("Fetch Failed")
}
}
}
func convertImage(picture : Picture)-> Data? {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let path = paths[0]
if let picName = picture.pictureName {
let imagePath = path.appendingPathComponent(picName)
if let uiImage = UIImage(contentsOfFile: imagePath.path) {
if let imageData = uiImage.jpegData(compressionQuality: 0.5) {
return imageData
}
}
}
return nil
}```
Hello everyone,
Recently, I have encountered an issue in my tvOS app where a specific property of UITextField, isSecureTextEntry, set to true, was preventing another property, keyboardType, from functioning correctly. In my case, keyboardType is set to numberPad option.
The problem is that during the first tap on the text field, the default keyboard with numbers, letters, and some special characters opens. However, after the second tap, the correct keyboard type with only numbers appears as I want. Removing isSecureTextEntry or setting to false solves the problem.
import UIKit
class ViewController: UIViewController {
private let textField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
textField.keyboardType = .numberPad
textField.isSecureTextEntry = true
view.addSubview(textField)
setupConstraints()
}
}
I'd like to share an app's screen in two modes. First in a standard mirroring mode and second in an "additional content" mode (very likely with a session role windowExternalDisplayNonInteractive).
I found that the Keynote app on iOS does a very nice example of what I want to achieve when sharing an iPhone using AirPlay to an AppleTV.
sharing a screen results in mirroring the screen on the TV
tapping the play button in Keynote switches to "additional content" where iPhone and TV show different content
leaving the additional content mode returns to "mirroring" where TV and iPhone show the same content
Is there an example for implementing such a feature?
I am able to successfully use the external display (windowExternalDisplayNonInteractive) and show additional content there.
How can I programmatically "detach" the additional content from the external display and activate mirroring mode?
Searching the Developer Forums for windowExternalDisplayNonInteractive reveals some discussions, which include valuable information, however, returning to mirror mode does not seem to be covered.
When I create a modal segue to a navigation controller in a storyboard, the navigation bar buttons appear correctly. But when trying to recreate this programmatically, no buttons appear:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .infoLight, primaryAction: UIAction(handler: { _ in
self.present(UINavigationController(rootViewController: ModalViewController()), animated: true)
}))
button.frame.origin = CGPoint(x: 100, y: 100)
view.addSubview(button)
}
}
class ModalViewController: UIViewController {
override func loadView() {
let button = UIBarButtonItem(title: "button")
button.primaryAction = UIAction(handler: { action in
})
button.style = .done
navigationItem.title = "title"
navigationItem.rightBarButtonItem = button
view = UITableView()
}
}
What am I doing wrong?
I got a UIControl, and I want to make it behavior like a custom UIAccessibilityElement.
UIControl *control = [[UIControl alloc] init];
control.isAccessibilityElement = NO;
CustomAccessibilityElement *elem = [[CustomAccessibilityElement alloc] initWithAccessibilityContainer:control];
elem.isAccessibilityElement = YES;
// some custom setting here
control.accessibilityElements = @[elem];
It worked well with an iPhone 13, iOS 15.5.1, but crashed with an iPhone SE, iOS 15.4.1 and the crash msg is :
"-[UIAccessibilityElement _addAccessibilityElementsAndOrderedContainersWithOptions:toCollection:]: unrecognized selector sent to instance 0x283b7c680"
Can you tell me the reason ?
Thanks a lot.
Hello!
Since iOS 18.0.1 I receive crash reports for iPadOS only, "Fatal error: <UpdateCoalescingCollectionView 0x30148e7c0> is stuck in a recursive layout loop. The error is not reproducible for me.
However, the screen where the user did the last touch event has no CollectionView but just a ScrollView. Can it be possible that this error can happen on a View that is not visible but is on another tab of the active UITabViewController(Representable) but has a UICollectionView? Or can you see somehow from the crash report on which SwiftIUView/UIKItView this error happened?
crash_report.crash
I have an iOS app that relies on dynamic text size such that all fonts in the app respect the user's setting of Text Size in the iOS Settings app.
This app also runs on macOS via Mac Catalyst. But until macOS 14 Sonoma, there was no Text Size setting in the macOS Settings app. But even as of Sonoma, the Text Size setting isn't usable by 3rd party apps. And Sequoia doesn't seem to change that.
As a work around, my Mac Catalyst app provides its own Text Size setting. I was able to make it work by providing my own UIApplication subclass and overriding preferredContentSizeCategory. Under macOS 12 to macOS 14, this workaround works just fine and all fonts in the app created with code such as UIFont.preferredFont(forTextStyle:) gives appropriately sized fonts based on the overridden content size category.
However, this workaround stopped working with macOS 15 Sequoia. I've also tried code such as:
self.window.traitOverrides.preferredContentSizeCategory = myCustomSizeCategoryValue
and
self.window.maximumContentSizeCategory = myCustomSizeCategoryValue
self.window.minimumContentSizeCategory = myCustomSizeCategoryValue
in the scene delegate but that made no difference.
Is there any way to get code such as UIFont.preferredFont(forTextStyle:) to return an appropriately sized font based on some app provided content size category in a Mac Catalyst app running under macOS 15?
It sure would be nice if Mac Catalyst apps automatically responded to the macOS Text Size setting under Settings -> Accessibility -> Display -> Text Size just like a native iOS app.
Hey, I have an app that user selects wallpaper for iPhone. I want a feature that user can set wallpaper direct from app itself for lock screen and home screen not download the image and manually set the wallpaper. As my research there was a PhotoLibrary api that contains PLWallpaperImageViewController.h which allows you to set wallpaper directly.
Thank You!
I'm trying to create a brush by drawing in CGContext using a UIImage for a brush. However, I when I try drawing, the stroke is antialiased when I don't want it to be. I tried
context.interpolationQuality = .none
context.setShouldAntialias(false)
but it doesn't seem to work. Is it a problem with my brush or resizing the brush maybe?? (Also this problem doesn't occur when I draw a regular stroke without the brush.)
Any help or advice would be greatly appreciated!
Here is my draw function
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
context.interpolationQuality = .none
context.setShouldAntialias(false)
for stroke in strokes {
let brush = UIColor.blue.circle(size: CGSize(width: CGFloat(stroke.width * 10), height: CGFloat(stroke.width * 10))).mask(color: UIColor(cgColor: stroke.color))
var first = true
for point in stroke.points {
if first {
first = false
context.move(to: point)
continue
}
context.addLine(to: point, using: brush)
}
}
}
Circle brush
extension UIColor {
func circle(size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
return UIGraphicsImageRenderer(size: size).image { rendererContext in
self.setFill()
UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)).fill()
}
}
}
CGConext extension where I overloaded addLine
extension CGContext {
func addLine(to point: CGPoint, using brush: UIImage, density: CGFloat = 1.0) {
var frame: CGRect = .zero
frame.size = brush.size
let lastPoint = self.currentPointOfPath
let distanceX = point.x - lastPoint.x
let distanceY = point.y - lastPoint.y
let distanceR = sqrt(pow(distanceX, 2) + pow(distanceY, 2))
let deltaR = (1.0 / density)
let numOfSteps = ceil(distanceR / deltaR)
var renders : CGFloat = 0.0
let deltaX = distanceX / numOfSteps
let deltaY = distanceY / numOfSteps
var currentCenter = lastPoint
repeat {
frame.origin.x = currentCenter.x - frame.width / 2.0
frame.origin.y = currentCenter.y - frame.height / 2.0
brush.draw(in: frame)
currentCenter.x += deltaX
currentCenter.y += deltaY
renders += 1.0
} while (renders <= numOfSteps)
self.move(to: point)
}
}
Summary
In iOS 18, the UICollectionViewDelegate method
collectionView(_:targetIndexPathForMoveOfItemFromOriginalIndexPath:atCurrentIndexPath:toProposedIndexPath:)
is not being called when moving items in a UICollectionView. This method works as expected in iOS 17.5 and earlier versions.
Steps to Reproduce
Create a UICollectionView with drag and drop enabled.
Implement the UICollectionViewDelegate method:
func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveOfItemFromOriginalIndexPath originalIndexPath: IndexPath, atCurrentIndexPath currentIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
print("🐸 Move")
return proposedIndexPath
}
Run the app on iOS 18.
Attempt to drag and drop items within the collection view.
Expected Behavior
The method should be called during the drag and drop operation, and "🐸 Move" should be printed to the console.
Actual Behavior
The method is not called, and nothing is printed to the console. The drag and drop operation still occurs, but without invoking this delegate method.
Configuration
iOS Version: 18
Xcode Version: Xcode 16.0.0
Use a UITabBarController to load two ViewControllers, A and B, both wrapped in UINavigationController.
In A’s viewDidLoad method, push to C, with C’s hidesBottomBarWhenPushed set to true. When popping back to A, the tab bar flickers and then disappears.
In versions lower than iOS 18, after popping back, the tabBar does not flicker and disappear.
A ViewController
override func viewDidLoad() {
super.viewDidLoad()
let vc = CViewController()
vc.hidesBottomBarWhenPushed = true
navigationController?.pushViewController(vc, animated: true)
// Do any additional setup after loading the view.
}
MACCATALYST
I am able to hide title bar title using below line.
windowScene.titlebar?.titleVisibility = .hidden
but,
windowScene.titlebar?.toolbarStyle = .unifiedCompact
windowScene.titlebar?.toolbar = nil
with above 2 lines, my title bar is still visible with system defined some background color.
I want to make it transparent(clear background color)
Currently, I have achieve shadow and corner effect for UICollectionViewListCell, using the following code.
UICollectionViewListCell
class NoteCell: UICollectionViewListCell {
override func awakeFromNib() {
super.awakeFromNib()
initShadow()
initCorner()
}
private func updateShadowColor() {
// Determine the shadow color based on the current interface style
let shadowUIColor = UIColor.label
self.layer.shadowColor = shadowUIColor.cgColor
}
private func initShadow() {
// https://www.hackingwithswift.com/example-code/uikit/how-to-add-a-shadow-to-a-uiview
self.layer.shadowOpacity = 0.3
self.layer.shadowOffset = CGSize(width: 0.5, height: 0.5)
self.layer.shadowRadius = 2
self.layer.masksToBounds = false
self.updateShadowColor()
// Remove the following two lines if you experience any issues with shadow rendering:
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
private func initCorner() {
var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
backgroundConfig.backgroundColor = .systemBackground
backgroundConfig.cornerRadius = 16
self.backgroundConfiguration = backgroundConfig
}
layout
private func layoutConfig() -> UICollectionViewCompositionalLayout {
let layout = UICollectionViewCompositionalLayout { section, layoutEnvironment in
var config = UICollectionLayoutListConfiguration(appearance: .plain)
config.headerMode = .none
config.footerMode = .none
config.showsSeparators = false
config.headerTopPadding = 0
config.backgroundColor = nil
config.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath in
guard let self = self else { return nil }
// Knowing what we are tapping at.
var snapshot = dataSource.snapshot()
let sectionIdentifier = snapshot.sectionIdentifiers[indexPath.section]
let itemIdentifiers = snapshot.itemIdentifiers(inSection: sectionIdentifier)
let itemIdentifier: NoteWrapper = itemIdentifiers[indexPath.item]
let deleteHandler: UIContextualAction.Handler = { action, view, completion in
completion(true)
// TODO:
//snapshot.reloadItems([itemIdentifier])
}
let deleteAction = UIContextualAction(style: .normal, title: "Trash", handler: deleteHandler)
var swipeActionsConfiguration = UISwipeActionsConfiguration(actions: [
deleteAction,
])
deleteAction.image = UIImage(systemName: "trash")
deleteAction.backgroundColor = UIColor.systemRed
swipeActionsConfiguration.performsFirstActionWithFullSwipe = false
return swipeActionsConfiguration
}
// https://developer.apple.com/forums/thread/759987
let layoutSection = NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
layoutSection.interGroupSpacing = 16 // Distance between item.
layoutSection.contentInsets = NSDirectionalEdgeInsets(
top: 16, // Distance between 1st item and its own header.
leading: 16,
bottom: 16, // Distance of last item and other header/ bottom edge.
trailing: 16
)
return layoutSection
}
return layout
}
This is the outcome.
However, when I perform swipe action, the shadow effect is gone.
Do you have any idea how I can resolve such? Thanks.