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?
UIKit
RSS for tagConstruct and manage graphical, event-driven user interfaces for iOS or tvOS apps using UIKit.
Posts under UIKit tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
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.
I'm updating my Photo Editing Extension to support HDR. To do this I set imageView.preferredImageDynamicRange = .high. But you can turn off the option to view HDR photos in the complete dynamic range in Settings > Photos. When you do that, open a photo, and tap the edit button, it does not appear in the full range as expected, but when you select my app from More > Extensions, it does appear in the complete dynamic range unexpectedly. I need to set imageView.preferredImageDynamicRange = .standard when View Full HDR is off, but I don't see any way to get that in my PHContentEditingController.
In a photo editing extension, is it possible to display the photo in HDR? In this context you only have a placeholder UIImage and a PHContentEditingInput which has a displaySizeImage and fullSizeImageURL. The displaySizeImage has isHighDynamicRange false.
I use @Binding to sync data between SwiftUI component state and UITable state.
my observations:
while reordering @State variable of the TestView is updating, it can be checked by taping the button
inside the table the data is not updating. 'log2' is always the same on reorder and which is more important the app crashes when I try to remove item.
What to I missing?
import SwiftUI
import UIKit
struct TestView: View {
@State var items = ["item 1", "item 2", "item 3", "item 4", "item 5"]
var body: some View {
VStack {
Button("Print Items") { print("log1", items) }
ReorderableListView(
items: $items
) { item in
Text(item)
}
}
}
}
private struct ReorderableListView<Content: View>: UIViewControllerRepresentable {
@Binding var items: [String]
let content: (String) -> Content
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
var parent: ReorderableListView
init(parent: ReorderableListView) {
self.parent = parent
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("log2", parent.items)
return parent.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let hostingController = UIHostingController(rootView: parent.content(parent.items[indexPath.row]))
hostingController.view.backgroundColor = .clear
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
cell.contentView.addSubview(hostingController.view)
cell.separatorInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15)
if indexPath.row == self.parent.items.count - 1 {
cell.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: .greatestFiniteMagnitude)
}
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor)
])
return cell
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = parent.items.remove(at: sourceIndexPath.row)
parent.items.insert(movedObject, at: destinationIndexPath.row)
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
parent.items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
func makeUIViewController(context: Context) -> UITableViewController {
let tableViewController = UITableViewController()
tableViewController.tableView.dataSource = context.coordinator
tableViewController.tableView.delegate = context.coordinator
tableViewController.tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "Cell")
tableViewController.tableView.isEditing = true
return tableViewController
}
func updateUIViewController(_ uiViewController: UITableViewController, context: Context) {
uiViewController.tableView.reloadData()
}
}
private class CustomTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func layoutSubviews() {
super.layoutSubviews()
superview?.subviews.filter({ "\(type(of: $0))" == "UIShadowView" }).forEach { (sv: UIView) in
sv.removeFromSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
#Preview {
TestView()
}
Explanation:
I am working on an application and had trouble diagnosing the warnings below. I pulled the search bar code out into a separate project, and the warnings still show up. The entire code for the sample project is shown below. The "AddInstanceForFactory", "LoudnessManager", and "NSBundle" warning all show up immediately after tapping the search bar. The "RTIInputSystemClient" warning arises after tapping, dismissing, and re-tapping the search bar.
Any help with resolving these warnings is appreciated!
Warnings:
"-[RTIInputSystemClient remoteTextInputSessionWithID:performInputOperation:] perform input operation requires a valid sessionID. inputModality = Keyboard, inputOperation = , customInfoType = UIEmojiSearchOperations"
"NSBundle file:///Library/Developer/CoreSimulator/Volumes/iOS_22A3351/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS%2018.0.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/MetalTools.framework/ principal class is nil because all fallbacks have failed"
"AddInstanceForFactory: No factory registered for id <CFUUID --------------> -----------------"
"LoudnessManager.mm:709 unable to open stream for LoudnessManager plist"
Code:
import UIKit
class SearchView: UIView {
private lazy var searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.delegate = self
searchBar.translatesAutoresizingMaskIntoConstraints = false
searchBar.placeholder = "Search"
return searchBar
}()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .yellow
addSubview(searchBar)
NSLayoutConstraint.activate([
searchBar.topAnchor.constraint(equalTo: topAnchor, constant: 12),
searchBar.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
searchBar.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
searchBar.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12),
])
}
}
extension SearchView: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print(searchText)
}
}
class ViewController: UIViewController {
private lazy var searchView = SearchView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
view.addSubview(searchView)
NSLayoutConstraint.activate([
searchView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
searchView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
searchView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
@objc func dismissKeyboard() {
view.endEditing(true)
}
}