I have an iOS App which looks great on iPhone, portrait only, which makes a lot of use of UITableViews.
On iPad those tables look stretched out in Landscape.
On MacOS with Apple Silicon the app can be resized to any size and the table views look very stretched. There are views in the App which users want to resize so limiting app size not an option.
I've been modifying the app's table views to limit their width and centre them using constraints.
This isn't easy, it's a lot of work as UITableViewController doesn't allow for constraining the table width. Or does it?
So I've changed them to UIViewControllers with UITableView imbedded in the root UIView with constraints. Looks really nice.
Now I've just run into the limitation that static tables, which I have a number of, aren't allowed to be embedded. So how can I limit the width of them?
I really don't want to add a lot of dynamic code.
Please tell me there's an simpler, more elegant method to what really makes a much more aesthetically pleasing UI on iOS App running on iPad and MacOS?
TIA!
UIKit
RSS for tagConstruct and manage graphical, event-driven user interfaces for iOS or tvOS apps using UIKit.
Post
Replies
Boosts
Views
Activity
I have a swiftui view with Button(intent: ) and using UIHostingViewcontroller to use it in UIKit. The problem is that button not works in uikit but normal button(without intent works)
Following instructions from ChatGPT, I'm trying to rearrange the order of rows in a UITableView.
- (void)tableView:(UITableView *)tableView performDropWithCoordinator:(id<UITableViewDropCoordinator>)coordinator {
NSIndexPath *destinationIndexPath = coordinator.destinationIndexPath ?: [NSIndexPath indexPathForRow:self.items.count inSection:0];
[tableView performBatchUpdates:^{
for (UITableViewDropItem *dropItem in coordinator.items) {
NSString *movedItem = dropItem.dragItem.localObject;
if (movedItem) {
NSIndexPath *sourceIndexPath = dropItem.sourceIndexPath;
if (sourceIndexPath) {
[self.items removeObjectAtIndex:sourceIndexPath.row];
[self.items insertObject:movedItem atIndex:destinationIndexPath.row];
[tableView moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath];
}
}
}
} completion:nil];
}
Xcode is complaining that UITableViewDropItem does not exist.
The class is in all the documentation, it is just not showing up when needed!
Suggestions?
Hi,
I'm trying to create a UICollectionView where the cell high is automatic. Cells contains a UILabel with all anchors to the contentView of the cell.
It seems to work but I have a strange behavior with longer text, on reload data and on device rotation: Cells do not display the whole text or they change row, both randomly.
To create my collection view I first create the collection view with a custom flow layout setting the automatic size on viewWillAppear:
let collectionViewFlowLayout = CustomFlowLayout()
collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)
and I have also overridden:
override func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
collectionView.setNeedsLayout()
self.collectionView.layoutIfNeeded()
self.collectionView.collectionViewLayout.invalidateLayout()
//self.collectionView.reloadData()
}
Then, I created the custom layout as follow:
import UIKit
final class CustomFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
layoutAttributesObjects?.forEach({ layoutAttributes in
if layoutAttributes.representedElementCategory == .cell {
if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else {
fatalError()
}
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}
layoutAttributes.frame.origin.x = sectionInset.left
if(indexPath.section == 0){
layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
} else if (indexPath.section == collectionView.numberOfSections - 1){
let width = ScreenUtility.getCollectionCellWidthForElement(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing, multiplier: 3)
layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElement(in: collectionView, at: indexPath, forElementHavingWidth: width, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing, multiplier: 3)
layoutAttributes.frame.size.width = width
} else if (indexPath.section == collectionView.numberOfSections - 3) || (indexPath.section == collectionView.numberOfSections - 4){
let width = ScreenUtility.getCollectionCellWidthForElement(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing)
layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElement(in: collectionView, at: indexPath, forElementHavingWidth: width, sectionLeft: sectionInset.left, sectionRight: sectionInset.right, minimumInteritemSpacing: minimumInteritemSpacing)
layoutAttributes.frame.size.width = width
} else {
let width = ScreenUtility.getCollectionCellSizeForElementFullRow(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right)
layoutAttributes.frame.origin.x = ScreenUtility.getCollectionCellOriginForElementFullRow(in: collectionView, sectionLeft: sectionInset.left, sectionRight: sectionInset.right)
layoutAttributes.frame.size.width = width
}
return layoutAttributes
}
}
And finally on collection view cells:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
override func prepareForReuse() {
self.nameLabel.text = ""
self.idLabel.text = ""
self.contentView.setNeedsLayout()
self.contentView.layoutIfNeeded()
}
Let me show you an example on the iPad that is the worst.
First Time I open the collection view I have cells on wrong rows and not sized properly
Then I rotate the device portrait and the cells are fine
On landscape again it changes behavior:
This is just an example, things happens apparently randomly, and also sometimes cells disappear (I think the height is set to 0).
I really do not understand why, cells width seems to be computed correctly, and cell label is set via setter:
open var step: String = "" {
didSet {
nameLabel.text = step
nameLabel.sizeToFit()
self.contentView.setNeedsLayout()
self.contentView.layoutIfNeeded()
}
}
I'm working on integrating the new format panel shown in the WWDC24 session "What's New in UIKit" under the Text Improvements section. So far, I've implemented long-press functionality on a text passage, allowing the editing options to appear. From there, you can go to Format > More..., which successfully opens the new format panel.
However, I would also like to add a button to programmatically display this format panel—similar to how the Apple Notes app has a button in the keyboard toolbar to open it.
Does anyone know how to achieve this?
Here's my current code for the text editor (I've enabled text formatting by setting allowsEditingTextAttributes to true):
struct TextEditorView: UIViewRepresentable {
@Binding var text: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let textEditorView = UITextView()
textEditorView.delegate = context.coordinator
textEditorView.allowsEditingTextAttributes = true
return textEditorView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: TextEditorView
init(_ uiTextView: TextEditorView) {
self.parent = uiTextView
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
}
}
Thanks in advance for any guidance!
In a UITabBarController, its controllers are set to be UINavigationControllers.
When programmatically setting the selectedViewController to a desired controller which is not currently displayed, the selected icon is correct however the actual view controller is still the previously selected.
Pseudo code:
tabController.controllers = [viewcontroller1, viewcontroller2, viewcontroller3].map{ UINavigationController(rootViewController: $0) }
....
// let's say at some point tab bar is set to e.g. showing index 1
tabController.selectedController = tabController.controllers[0]
// after this the icon of the 1st tab is correctly displayed, but the controller is still the one at index 1
I have noticed that if the controllers are simple UIViewController (not UINavigationController) upon setting the selectedViewController the TabController sets both icon and content correctly.
But this is not the wanted setup and different UINavigationControllers are needed.
Is this a new bug in iOS18?
Any idea how to fix this (mis)behaviour?
code:
let action1 = UIAction(title: "Restore".localized, image: UIImage(resource: .listRestore)) { [weak self] action in
self?.trashRestoreTapped(id: button.tag)
}
let action2 = UIAction(title: "Delete".localized, image: UIImage(resource: .listDelete), attributes: [.destructive]) { [weak self] action in
self?.trashDeleteTapped(id: button.tag)
}
button.menu = UIMenu(options: .displayInline, preferredElementSize: .large, children: [action1, action2])
button.showsMenuAsPrimaryAction = true
MacOS 15.1:
iPad:
We recently converted an existing app to adopt scenes (for CarPlay). The app has one main view controller that presents a WKWebView to show our content. Everything works fine in both landscape and portait mode when swiping on the screen to scroll. But with an iPad using an external Magic Keyboard, once you rotate to landscape mode the scrolling gestures are reversed. Swiping vertically on the trackpad is scrolling the page horizontally and vice versa. When this happens an error like below is logged (this error also shows up when in portait mode, but scrolling works as expected):
Unexpected window orientation: <UIWindow: 0x10370d8f0; orientation: landscapeLeft (4)> {
hidden = NO;
frame = {{0, 0}, {1180, 820}};
bounds = {{0, 0}, {1180, 820}};
ownsOrientation = NO;
ownsOrientationTransform = NO;
autorotationDisabled = NO;
windowInterfaceOrientation = unknown (0);
rootTransformOrientation = landscapeLeft (4);
viewTransformOrientation = unknown (0);
autorotationDisabled = NO;
orientationVC = ... {
providedSupportedOrientations = ( Pu Ll Lr Pd );
resolvedSupportedOrientations = ( Pu Ll Lr Pd );
canPreferOrientation = NO;
};
}, event type: 6
It seems to suggest that while the view controller orientation is set correctly. It doesn't know what the orientation is for the window. I have been unable to figure out what would change with adopting scenes that would explain this behavior. I assume it has to do with the potential multi-window nature of it but haven't found any docs that describe how to ensure the window is setup to use the same orientation as the device. Any suggestions on things to check?
Hello bro, I create a custom button like this way: UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.titleLabel.font = [UIFont fontWithName:fontName size:fontSize];
Then I receive any issue from the third crash collection platform:
0 libobjc.A.dylib 0x000000018fd1e694 _objc_moveWeak + 196
1 libobjc.A.dylib 0x000000018fd1e694 _objc_moveWeak + 196
2 CoreFoundation 0x0000000197e7da94 __CFXNotificationRegistrarAddObserver + 392
3 CoreFoundation 0x0000000197e7c864 _CFXNotificationRegistrarAdd + 580
4 CoreFoundation 0x0000000197e7c040 __CFXNotificationRegisterObserver + 248
5 UIKitCore 0x000000019a1b6c98 _UILabelCommonInit + 188
6 UIKitCore 0x000000019a1b69fc -[UILabel _commonInit] + 520
7 UIKitCore 0x000000019a1cdf88 -[UILabel initWithFrame:] + 56
8 UIKitCore 0x000000019a24824c -[UIButtonLabel _initWithFrame:button:] + 100
9 UIKitCore 0x000000019a247c14 -[UIButtonLegacyVisualProvider _newLabelWithFrame:] + 84
10 UIKitCore 0x000000019a15ee80 -[UIButtonLegacyVisualProvider _setupTitleViewRequestingLayout:] + 84
11 UIKitCore 0x000000019a15d81c -[UIButtonLegacyVisualProvider titleViewCreateIfNeeded:] + 44
12 UIKitCore 0x000000019a1cfa78 -[UIButton titleLabel] + 36
So would you please tell me how to avoid it?
we are migrating our app to support Arabic language support but the requirement is we want the calendar/date object to display as missed. That is all the numbers in english digits and rest all the words like days, months should be in Arabic.
I tried few options but at the end its resulting everything is in Arabic or in English but not the mixed as expected. Attaching the expected behavior.
在webview全屏打开Video视频前后获取设备的宽高结果不同 [UIScreen mainScreen].bounds对应的设备逻辑分辨率发生变化,导致根据分辨率显示的页面显示错误
I'm currently migrating a midsize (20k LOC) project to Swift structured concurrency. With complete checking turned on, it currently builds with only two warnings, both of which are related to the QLPreviewControllerDelegate protocol:
"Main actor-isolated instance method 'previewControllerDidDismiss' cannot be used to satisfy nonisolated protocol requirement; this is an error in the Swift 6 language mode" as well as the same warning but substituting 'previewController(_:transitionViewFor:)' for the method name.
I'm confused as to how to make these nonisolated, as they use UIKit classes/subclasses as arguments and/or return types.
Only IOS 18+ bug.
After tap share on UIPrintInteractionController - crash.
So:
#0 0x00000001bc38edf4 in _realizeSettingsExtension.cold.5 ()
#1 0x00000001bc310ed0 in _realizeSettingsExtension ()
#2 0x00000001bc33d100 in _ingestPropertiesFromSettingsSubclass ()
#3 0x00000001bc33be50 in __FBSIngestSubclassProperties_block_invoke ()
#4 0x00000001bc33bd7c in FBSIngestSubclassProperties ()
#5 0x00000001bc33d814 in FBSSettingForLegacySelector ()
#6 0x00000001bc30ecf8 in FBSSettingForSelector ()
#7 0x00000001bc350d98 in -[FBSMutableSceneSettings addPropagatedProperty:] ()
#8 0x00000001a64bae88 in __58-[_UISceneHostingController createSceneWithConfiguration:]_block_invoke_3 ()
#9 0x00000001c5bc16ec in -[FBScene _joinUpdate:block:completion:] ()
#10 0x00000001a64bab9c in -[_UISceneHostingController createSceneWithConfiguration:] ()
#11 0x00000001a64ba838 in -[_UISceneHostingController initWithAdvancedConfiguration:] ()
#12 0x00000001a64ba8cc in -[_UISceneHostingController initWithProcessIdentity:sceneSpecification:] ()
#13 0x00000001c05a8bd0 in -[SHSheetRemoteScene setupSceneHosting] ()
#14 0x00000001c05a88d0 in -[SHSheetRemoteScene activate] ()
#15 0x00000001c05f2e88 in -[SHSheetInteractor startSession] ()
#16 0x00000001c05ebc08 in -[SHSheetPresenter initWithRouter:interactor:] ()
#17 0x00000001c05de3c8 in +[SHSheetFactory createMainPresenterWithContext:] ()
#18 0x00000001c05d4dec in -[UIActivityViewController _createMainPresenterIfNeeded] ()
#19 0x00000001c05d6320 in -[UIActivityViewController _viewControllerPresentationDidInitiate] ()
#20 0x00000001a5a109c4 in -[UIViewController _presentViewController:withAnimationController:completion:] ()
#21 0x00000001a5a1311c in __63-[UIViewController _presentViewController:animated:completion:]_block_invoke ()
#22 0x00000001a5a0d248 in -[UIViewController _performCoordinatedPresentOrDismiss:animated:] ()
#23 0x00000001a5a0ceb4 in -[UIViewController _presentViewController:animated:completion:] ()
#24 0x00000001a5a0ccc0 in -[UIViewController presentViewController:animated:completion:] ()
#25 0x00000002001f3660 in -[UIPrintPanelViewController showSharePanelForPDFURL:] ()
In log throwing an error (I'm not 100% sure that is related - but last class in stack trace the same):
failure in void _realizeSettingsExtension(__unsafe_unretained Class, __unsafe_unretained Class) (FBSSceneExtension.m:502) : could not convert "1" to an integer
What I have is that big project with a lot of dependencies and etc..
Trying on clear project and it worked. (sharing simple local image with url).
Testing on my project:
With no controllers at all - check.
With different approaches (htm content; pdf data, image local url) - check.
With UIActivityViewController - check
With day of UIPrintInteractionController creation (not a thread/related issue) - check.
No other work can be done from my side (except rewrite 10years project).
Help me please to debug it from private api prospective as I have no Idea what is happening. (trying to remove all appearances and all dependency - not easy task).
Thanks
I have a Catalyst app that uses popovers frequently, and I'd love to have them stay active when the app loses focus. It appears this is controlled in a native AppKit app via NSPopover.Behavior. Is this functionality exposed somewhere in Catalyst?
class Coordinator: NSObject, UIContextMenuInteractionDelegate, ContextMenuManagerDelegate {
var container: ContextMenuContainer
var auxiliaryContent: AuxiliaryContent
var menuItems: () -> [UIMenuElement]
init(container: ContextMenuContainer) {
self.container = container
self.auxiliaryContent = container.auxiliaryContent
self.menuItems = container.menuItems
super.init()
}
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint
) -> UIContextMenuConfiguration? {
container.viewModel.contextMenuManager?.notifyOnContextMenuInteraction(
interaction,
configurationForMenuAtLocation: location
)
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
UIMenu(title: "", children: self?.menuItems() ?? [])
}
}
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
willDisplayMenuFor configuration: UIContextMenuConfiguration,
animator: UIContextMenuInteractionAnimating?
) {
container.viewModel.contextMenuManager?.notifyOnContextMenuInteraction(
interaction,
willDisplayMenuFor: configuration,
animator: animator
)
}
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
willEndFor configuration: UIContextMenuConfiguration,
animator: UIContextMenuInteractionAnimating?
) {
container.viewModel.contextMenuManager?.notifyOnContextMenuInteraction(
interaction,
willEndFor: configuration,
animator: animator
)
}
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration
) -> UITargetedPreview? {
guard let targetView = interaction.view else { return nil }
let bubbleWithTail = BubbleWithTailPath()
let customPath = bubbleWithTail.path(in: targetView.bounds)
let parameters = UIPreviewParameters()
parameters.visiblePath = customPath
return UITargetedPreview(
view: targetView,
parameters: parameters
)
}
func onRequestMenuAuxiliaryPreview(sender: ContextMenuManager) -> UIView? {
let hostingController = UIHostingController(rootView: auxiliaryContent)
hostingController.view.backgroundColor = .clear
return hostingController.view
}
}
i am trying to recreate apple's iMessage tapback reaction context menu. i have figured out how to attach an auxiliary view to the view with the menu, however the menu itself varies in position. if i bring up the context menu on a view below half the screen height, the menu appears above the view. else, it appears below. i need it to always be below.
By default, when using Context Menu the menu will flip positions to be above or below the content based on the current screen position. Many apps however, tend to keep the menu anchored to the bottom of the content it is associated with and move the content up to fit the menu (iMessage messages for example). In order to get this behavior, where the menu is always anchored to the bottom and the content "shifts up" to make space you have to use a custom preview by passing a previewProvider. So to get the desired behavior you can use a preview that is the same content as the original view. However, when you do this there is a small fade in animation that gets added and since the content of the original view and preview is the same this appears as a flicker. Is there any way to disable this fade animation or force anchor the menu to the bottom when using the standard preview?
Hi,
I'm making a WatchKit game app with SpriteKit and Objective-C, and I'm encountering an annoyance where system gestures, namely long-pressing the top and bottom edges to pull Notification/Control Center, interfere with the controls of the game.
In iOS, this can be mitigated by using overriding preferredScreenEdgesDeferringSystemGestures in UIViewController, but I couldn't find any equivalent API in any WatchKit class, and searching for similar symbols only yielded a single private API (-[_UISystemAppearanceManager screenEdgesDeferringSystemGestures]) that isn't ever called on watchOS.
Any idea how to achieve a similar effect on watchOS?
Is there a way to make UITextField activate when double-tapped? Single-tapping makes it a little too easy to do something calamitous in my app.
0 CoreFoundation 0x0000000183f687cc ___exceptionPreprocess + 164
1 libobjc.A.dylib 0x000000018123b2e4 _objc_exception_throw + 88
2 CoreFoundation 0x000000018406e5f0 +[NSObject(NSObject) doesNotRecognizeSelector:] + 0
3 UIKitCore 0x0000000186849a48 -[UIButtonLegacyVisualProvider _newLabelWithFrame:] + 60
4 UIKitCore 0x00000001867652b0 -[UIButtonLegacyVisualProvider _setupTitleViewRequestingLayout:] + 84
5 UIKitCore 0x0000000186763ba4 -[UIButtonLegacyVisualProvider titleViewCreateIfNeeded:] + 44
6 UIKitCore 0x00000001867d3f74 -[UIButton titleLabel] + 36
Hello bro, in IOS 18 our team find issue ,We created a button like this:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.titleLabel.font = [UIFont fontWithName:paramFontName size: fontSize];
Please tell me whether we need to set button frame before we call button.titleLabe?
As of macOS Sequoia 15.1 (and probably earlier), in System Settings under Accessibility -> Display, there's a Text Size option that looks an awful lot like Dynamic Type on iOS:
I have an iOS app with robust support for Dynamic Type that I've brought to the Mac via Catalyst. Is there any way for me to opt this app into supporting this setting, maybe with some Info.plist key?
Calendar's Info.plist has a CTIgnoreUserFonts value set to true, but the Info.plist for Notes has no such value.