I've noticed that depending when I call NSApp.runModal(for:), the table view contained in the presented window is unresponsive: it either doesn't scroll at all, or the content only updates after one or two seconds, presumably after the inertial scrolling has ended.
In the sample code below I call NSApp.runModal(for:) in 3 different ways:
with a direct call
inside the callback to perform(_:with:afterDelay:)
inside the callback to DispatchQueue.main.async.
Only method 2 works. Why?
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
let window = NSWindow(contentViewController: ViewController(nibName: nil, bundle: nil))
// 1. doesn't work
runModal(for: window)
// 2. works
// perform(#selector(runModal), with: window, afterDelay: 0)
// 3. doesn't work
// DispatchQueue.main.async {
// self.runModal(for: window)
// }
}
@objc func runModal(for window: NSWindow) {
NSApp.runModal(for: window)
}
}
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
override func loadView() {
let tableView = NSTableView()
tableView.addTableColumn(NSTableColumn())
tableView.dataSource = self
tableView.delegate = self
let scrollView = NSScrollView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
scrollView.documentView = tableView
view = scrollView
}
func numberOfRows(in tableView: NSTableView) -> Int {
return 100
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return "\(row)"
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = NSTableCellView()
cell.addSubview(NSTextField(labelWithString: "\(row)"))
return cell
}
}
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
On iOS I can create a UIFont that automatically adapts to the font size chosen in the Settings app by the user:
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true
(Copy-pasted from here.) I couldn't find a similar API for macOS. In the Accessibility settings I can change the font size and some apps react to it, like System Settings and Finder automatically increase the labels. Is there a way to create NSFont or NSTextField that automatically adapts to the chosen font size?
I currently have a toolbar item group with 3 items, but clicking on any of the items doesn't do anything. Also none of the items appear to be highlighted, not even when manually setting NSToolbarItemGroup.selectedIndex. What am I missing? Setting the action property on the individual items rather than the group makes the items clickable, but still none of them appear to be selected.
class ViewController: NSViewController, NSToolbarDelegate {
let toolbarItemIdentifier = NSToolbarItem.Identifier("group")
let toolbarItemIdentifierItem1 = NSToolbarItem.Identifier("item1")
let toolbarItemIdentifierItem2 = NSToolbarItem.Identifier("item2")
let toolbarItemIdentifierItem3 = NSToolbarItem.Identifier("item3")
override func viewDidAppear() {
let toolbar = NSToolbar()
toolbar.delegate = self
view.window!.toolbar = toolbar
view.window!.toolbarStyle = .expanded
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.flexibleSpace, toolbarItemIdentifier]
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.flexibleSpace, toolbarItemIdentifier, .flexibleSpace]
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
switch itemIdentifier {
case toolbarItemIdentifier:
let item1 = NSToolbarItem(itemIdentifier: toolbarItemIdentifierItem1)
item1.image = NSImage(named: NSImage.addTemplateName)!
item1.label = "add"
let item2 = NSToolbarItem(itemIdentifier: toolbarItemIdentifierItem2)
item2.image = NSImage(named: NSImage.homeTemplateName)!
item2.label = "home"
let item3 = NSToolbarItem(itemIdentifier: toolbarItemIdentifierItem3)
item3.image = NSImage(named: NSImage.pathTemplateName)!
item3.label = "path"
let group = NSToolbarItemGroup(itemIdentifier: itemIdentifier)
group.subitems = [item1, item2, item3]
group.selectionMode = .selectOne
group.selectedIndex = 0
group.target = self
group.action = #selector(selectItem(_:))
return group
default:
return nil
}
}
@objc func selectItem(_ sender: Any) {
print(0)
}
}
I have a table view where each row has two labels, one left-aligned and one right-aligned. I would like to reload a single row, but doing so causes the right-aligned label to hug the left-aligned label.
Before the reload:
After the reload:
Reloading the whole table view instead, or disabling automatic row height, solves the issue. Can a single row be reloaded without resorting to these two workaround?
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
override func loadView() {
let tableView = NSTableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.delegate = self
tableView.usesAutomaticRowHeights = true
let column = NSTableColumn()
column.width = 400
tableView.addTableColumn(column)
let scrollView = NSScrollView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.documentView = tableView
view = scrollView
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
print("reload")
tableView.reloadData(forRowIndexes: IndexSet(integer: 2), columnIndexes: IndexSet(integer: 0))
// tableView.reloadData()
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
return 5
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = NSTableCellView()
let textField1 = NSTextField(labelWithString: "hello")
textField1.translatesAutoresizingMaskIntoConstraints = false
let textField2 = NSTextField(wrappingLabelWithString: "world")
textField2.translatesAutoresizingMaskIntoConstraints = false
textField2.alignment = .right
let stack = NSStackView(views: [
textField1,
textField2
])
stack.translatesAutoresizingMaskIntoConstraints = false
stack.distribution = .fill
cell.addSubview(stack)
NSLayoutConstraint.activate([stack.topAnchor.constraint(equalTo: cell.topAnchor, constant: 0), stack.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 0), stack.bottomAnchor.constraint(equalTo: cell.bottomAnchor, constant: 0), stack.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: 0)])
return cell
}
}
Swift loop execution time is 20x slower when adding print statement that is executed once at the end
My Swift app iterates over two Array<String> and compares their elements.
Something very strange is going on. I have the impression the compiler is doing some optimizations that I cannot understand: commenting out the print statement in MyInterface.run() improves the runtime from about 10 seconds to below 0.5 seconds, and that print statement is executed only once at the end of the program. Even commenting out the if above it has the same effect.
I'm running the app in Xcode Instruments. When debugging it in Xcode, there is no difference when commenting out any of those two lines.
Instruments shows that most of the time is spent in protocol witness for Collection.subscript.read in conformance [A], Array.subscript.read and similar, which in turn call different malloc, initialize, free and release methods.
What's the problem with this code?
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let x = Array(repeating: "adsf", count: 100000)
let y = Array(repeating: "adsf", count: 100000)
let diff = MyInterface(x: x, y: y)
diff.run()
}
}
class MyInterface<List: RandomAccessCollection & MutableCollection> where List.Element: Comparable, List.Index == Int {
private let algorithm: Algorithm<List>
init(x: List, y: List) {
algorithm = AlgorithmSubclass(x: x, y: y)
}
func run() {
algorithm.run()
if (0..<1).randomElement() == 0 {
print(algorithm.x.count) // commenting out this line, or the if above, makes the program 20x faster
}
}
}
class Algorithm<List: RandomAccessCollection> where List.Element: Equatable, List.Index == Int {
var x: List
var y: List
init(x: List, y: List) {
self.x = x
self.y = y
}
func run() {
}
}
class AlgorithmSubclass<List: RandomAccessCollection>: Algorithm<List> where List.Element: Equatable, List.Index == Int {
override func run() {
var count = 0
for _ in 0..<1000 {
for i in 0..<min(x.endIndex, y.endIndex) {
if x[i] == y[i] {
count += 1
}
}
}
let alert = NSAlert()
alert.messageText = "\(count)"
alert.runModal()
}
}
In my previous post I asked why copyfile is slower than the cp Terminal command. In this other post I asked how I can make copyfile faster by changing the block size.
Now I discovered that the cp implementation on macOS is open source and that when copying regular files it doesn't use copyfile but fcopyfile. In a test I noticed that fcopyfile by default seems to be faster than copyfile.
When copying a 7 GB file I get about the same results I observed when comparing filecopy to cp:
copyfile: 4.70 s
fcopyfile: 3.44 s
When setting a block size of 16_777_216, copyfile becomes faster than fcopyfile:
copyfile: 3.20 s
fcopyfile: 3.53 s
Is this expected and why is it so? I would have expected that they both have the same performance, and when changing the block size they would still have the same performance.
Here is the test code. Change #if true to #if false to switch from fcopyfile to copyfile:
import Foundation
import System
let source = "/path/to/source"
let destination = "/path/to/destination"
#if true
let state = copyfile_state_alloc()
defer {
copyfile_state_free(state)
}
//var bsize = 16_777_216
//copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &bsize)
let sourceFd = try! FileDescriptor.open(source, .readOnly)
let destinationFd = try! FileDescriptor.open(destination, .writeOnly)
if fcopyfile(sourceFd.rawValue, destinationFd.rawValue, state, copyfile_flags_t(COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_EXCL | COPYFILE_UNLINK)) != 0 {
print(NSError(domain: NSPOSIXErrorDomain, code: Int(errno)))
}
try! sourceFd.close()
try! destinationFd.close()
#else
source.withCString { sourcePath in
destination.withCString { destinationPath in
let state = copyfile_state_alloc()
defer {
copyfile_state_free(state)
}
// var bsize = 16_777_216
// copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &bsize)
if copyfile(sourcePath, destinationPath, state, copyfile_flags_t(COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_EXCL | COPYFILE_UNLINK)) != 0 {
print(NSError(domain: NSPOSIXErrorDomain, code: Int(errno)))
}
}
}
#endif
For the current app, I can get the Application Scripts directory with FileManager.url(for: .applicationScriptsDirectory, in: .userDomainMask, appropriateFor: nil, create: true), but I cannot find a similar API for application groups. Does one exist, or do I have to construct the URL manually? That would probably be ~/Library/Application Scripts/[app group id], but there doesn't seem to be a FileManager API to access ~ either, as FileManager.default.homeDirectoryForCurrentUser returns /Users/username/Library/Containers/[app id]/Data/.
Recently I realized that even though I was able to add my app's intents in the Shortcuts app, selecting any of the parameters didn't show the popup list of suggestions (the ones declared by DynamicOptionsProvider in the AppIntent subclass) and running it showed an error.
In the image you can see the two suggestions for the working app version ("asdf" and "bla") that would not be there in the non-working version.
I knew that when I had added this functionality, it worked, so I found the app version that caused the App Intents Extension to stop working. Apparently, the problem was that I had removed the Swift files declared in the app extension from my main app's target. Probably when I first added the App Intents Extension I had noticed that adding the extension's source files to the main target made it work, but later thought that it shouldn't be necessary and didn't test if it still worked.
Today I created an empty project with a new App Intents Extension and confirmed that the Shortcuts app was correctly showing the parameter popup suggestions, even without including the extension's source files in the main target. Then during the course of almost an entire day I gradually reduced my original Xcode project to this new sample project to find what else would make the extension work, other than including the extension's source files in the main target.
My very last resource was changing the bundle identifier, which solved the issue. My original project's targets have identifiers like org.domain.OriginalApp and org.domain.OriginalApp.AppIntent, while the sample project's targets have identifiers like org.domain.SampleApp and org.domain.SampleApp.AppIntent.
How could including the App Intents Extension's source files in the main target or changing the bundle identifiers cause the Shortcuts app to correctly show the parameter popup suggestions?
Every now and then I notice that the date modified of some files on my FTP server has slightly changed by setting the time to midnight. I notice this because I regularly sync the files from the FTP server to a folder on my Mac by comparing the date modified, and it happens every now and then that files that weren’t modified are listed for syncing. For many months after their creation, the time is correct and they are only synced once, but at some point the time is displayed as midnight for some reason and they are suddenly marked for syncing.
The wrong time is reported both programmatically as well as in the Finder.
The same files are displayed with what seems to be the correct time modified in the app Cyberduck.
In this case it seems to be exactly 6 months old files. I didn't check this for the other files that this issue happened with in the past, but it could be about the same timeframe.
Is this an issue with macOS? Is there a workaround? I already filed feedback FB13671336.
I have a file named ä.txt (with German umlaut) on my FTP server. I select it like this:
let openPanel = NSOpenPanel()
openPanel.runModal()
let source = openPanel.urls[0]
Running this code unexpectedly throws an error:
do {
print(try source.checkResourceIsReachable())
} catch {
print(error) // Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory”
}
Manipulating the URL also seems to change the underlying characters:
print(source) // file:///Volumes/abc.com/httpdocs/%C3%A4.txt
print(URL(fileURLWithPath: source.path)) // file:///Volumes/abc.com/httpdocs/a%CC%88.txt
Note that both variants of the URL above also throw the same error when running URL.checkResourceIsReachable().
If I download the file to my Mac, then both variants print file:///Users/me/Downloads/a%CC%88.txt and neither of them throws an error when running URL.checkResourceIsReachable().
What is the problem? How can I correctly access this file on the FTP server?
I want to show a file importer that allows to select both regular files as well as directories. When running the following code on iOS, I can tap a PDF file and the file importer closes as expected, but when tapping a directory, the file importer shows its contents. How can I instead select that directory and close the file importer? The navigation bar shows a Cancel button, but no Open button.
struct FileView: View {
@State private var showFileImporter = false
var body: some View {
ScrollView {
VStack(alignment: .leading) {
VStack(alignment: .center) {
Button("Open") {
showFileImporter = true
}
}
}
}
.fileImporter(isPresented: $showFileImporter, allowedContentTypes: [.pdf, .directory], onCompletion: { result in
// TODO
})
}
}
I use the App Store Connect API to run many parallel requests to update different parts of a single app. I am randomly getting errors such as
An unexpected error occurred on the server side.
or
The request timed out.
Usually when these errors happen, I can simply run the unsuccessful requests one or two more times and then they succeed.
Is there an explanation for this? Is this possibly caused by too many parallel requests? What is the maximum suggested number of parallel requests?
Topic:
App Store Distribution & Marketing
SubTopic:
App Store Connect API
Tags:
App Store Connect API
I have a data object that dynamically changes the UIImage assigned to one of its instance variables, but when showing this image in SwiftUI, it's always black and white. The following sample code shows the difference between the same image, but using first the native constructor Image(systemName:) and then Image(uiImage:).
When using AppKit and Image(nsImage:) this issue doesn't happen.
import SwiftUI
import UIKit
struct ContentView: View {
@State var object = MyObject()
var body: some View {
Image(systemName: "exclamationmark.triangle.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.white, .yellow)
Image(uiImage: object.image)
}
}
class MyObject {
var image = UIImage(systemName: "exclamationmark.triangle.fill")!
.applyingSymbolConfiguration(.init(paletteColors: [.white, .systemYellow]))!
}
#Preview {
ContentView()
}
Apparently UI tests are unable to tap menu buttons but can tap regular buttons inside forms. Earlier today I was able to see in the Simulator that the UI test tries to tap the button by tapping the center of the containing form row, which works for regular buttons, but not for menu buttons. In fact, when trying in the SwiftUI preview in Xcode it seems that menu buttons have to be tapped exactly on top of them, while regular buttons can be tapped anywhere in the form row. (Now I’m not able to see touches performed by the UI test anymore in the Simulator for an unknown reason, even though I have “Show single touches” enabled in the Simulator settings.)
How can I open a menu button in a UI test?
The UI code:
struct ContentView: View {
@State private var label1 = "Menu 1"
@State private var label2 = "Menu 2"
var body: some View {
NavigationStack {
Form {
LabeledContent("Menu 1") {
Button(label1) {
label1 = "Menu 1 tapped"
}
.accessibilityIdentifier("menu1")
}
LabeledContent("Menu 2") {
Menu(label2) {
Button("Button") {
}
.accessibilityIdentifier("button")
}
.accessibilityIdentifier("menu2")
}
}
}
}
}
#Preview {
ContentView()
}
And the test:
final class problemUITests: XCTestCase {
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
app.collectionViews.element(boundBy: 0).buttons["menu1"].tap()
app.collectionViews.element(boundBy: 0).buttons["menu2"].tap()
app.collectionViews.element(boundBy: 0).buttons["button"].tap()
}
}
XCUIElement has two methods named typeKey(_:modifierFlags:): the first one takes a String as an argument, the second one a XCUIKeyboardKey, which has constants like .downArrow. Now, even if the documentation says that both methods are "Available in macOS and in iPadOS 15 and later", when compiling the UI tests for iOS, the following line
app.typeKey(.downArrow, modifierFlags: [])
produces a compiler error
Type 'String' has no member 'downArrow'
Am I missing something or is there a workaround?