Hi All, this question has been asked before by others, without any success so far.
I'm using the following (standard) method to open the system provided settings screen for my macOS (only) app in SwiftUI.
var body: some Scene {
Settings {
PreferencesView()
}
if #available(macOS 13, *) {
// MenuBarExtra requires macOS 13, but SettingsLink used in here requires macOS 14
MenuBarExtra("...", image: "...") {
...
// this one requires macOS 14
if #available(macOS 14, *) {
SettingsLink() {
Text("Settings...")
}
} else {
Button {
NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
} label: {
Image(systemName: "gear")
}
}
}
}
}
I want to know when the settings window closes. onDisappear does not work as the settings window is not really closed, but hidden. So the view stays active. All other hacks I have found neither work.
Does anyone have any idea?
Post
Replies
Boosts
Views
Activity
Problem A macOS app built in Swift (using XCode) shall show up in the "Open With" context menu for any file. But it does not show up at all.
Current status
The app has a document type for public.item defined.
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Any file</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>public.item</string>
</array>
<key>NSDocumentClass</key>
<string>NSDocument</string>
</dict>
</array>
When I start the app (from XCode), no file has my app in the "Open With" menu. I've seen a lot of tutorials on how to add a custom type. But not any tutorial for a public type. Is this even possible?
Actual question:
How can I configure a Swift app to show up in the "Open With" menu for any file?
Scenario: The following Swift code uses NSItemProviderWriting to load a file from some data source async (The code actually just uses a for loop to simulate the time it needs to load the file). It returns the file url then. This code is used for drag & drop behavior in a macOS app from an app to another app. As the other app does only support file URLs as drop types, I can't send over the data itself.
class AnyFile: NSObject, NSItemProviderWriting {
let url: URL
init(url: URL) {
self.url = url
}
static var writableTypeIdentifiersForItemProvider: [String] {
return [UTType.fileURL.identifier]
}
func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void) -> Progress? {
let count = 100000
let progress = Progress(totalUnitCount: Int64(count))
print("a")
Task {
print("b")
for i in 0...100000 {
progress.completedUnitCount = Int64(i)
print(i)
}
print("c")
completionHandler(url.dataRepresentation, nil)
print("d")
}
print("e")
return progress
}
}
Problem: Drag & drop works, but when I start to drag the view (SwiftUI view using .onDrag), the view gets stuck as long as the loop is running. It is not clear to me why this happens as the loop us running inside a task. The console output also shows that progress is returned before the loop as finished. But still, it is stuck until the end of the loop. So the user experience is like not using async and the task.
Log output (shortened)
a
e
b
0
...
100000
c
d
Actual Question: Why is this Swift code for dragging files not behaving asynchronously even though it uses Task, completionHandler, and async/await constructs?
Alternatives tried:
I'm not bound to NSItemProviderWriting in particular. I have tried other ways, like Transferable, .draggable(), and NSItemProvider.registerFileRepresentation. But either they won't return a URL, or do not allow async at all. I also tried AppKit instead of SwiftUI with similar issues.
Hi All,
I have successfully implemented drag & drop of files from and to other apps on macOS successfully using different methods. Including NSFilePromiseProvider for example.
There is one app however, that I use on a regular basis, that only support file urls as drop types. So I have to use public.file-url to drag & drop files there. The problem is that the file does not exist when dragging starts. That's why I would like to send a URL promise to the app, instead of a file promise. I can't create the file beforehand because then I would need to create hundreds of large files which will just take up space.
So whenever I drag an item from my app to the other app, then the file shall be created and the url shall be sent to that app to be opened. But as the other app only checks the pasteboard for urls without handling promises, this is not working. I do have their code available, but can't change the code.
Question: Is there a way in Swift on macOS to drag and drop a an item to another app so that upon mouse up on the other app some async tasks is executed and at the end the other app receives the URL as a result of the async task?
Hi All, I have a macOS app written in SwiftUI where I want to add a simple drop down button with a menu to the toolbar for a view. It currently looks like this:
You can see that there is no gap between the drop down arrow and the symbol. And the icon/symbol color sometimes seems to be grayed out. When the app starts it also shows black sometimes (not constantly reproducible).
var body: some Scene {
WindowGroup {
ContentView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.navigationTitle("Title")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
Button {
...
} label: {
Text("Open cache directory")
}
.help("Show application support folder")
.disabled(applicationSupportDirectory == nil)
Button {
...
} label: {
Text("Clear cache")
}
.help("Clears all content from the cache")
.disabled(applicationSupportDirectory == nil)
} label: {
Image(systemName: "gear")
}
}
}
}
.windowToolbarStyle(.unified)
}
I basically want to have a simple drop down toolbar item with a few menu items. The app is pretty new so it does not have any fancy code just yet. I wonder what I am missing. Looking forward to have a button like this:
I tried different symbols, applying padding and frame width on the Image. I tried different placements and ToolbarItemGroup. Strangely enough: When setting the toolbar style to unifiedcompact, at least the spacing works. But the colouring is still strange. Nothing helped so far.
Hi all, I'm working on a SwiftUI app (macOS only) that uses a Table to show contents of a directory. Each Table Cell seems to have a padding / gap which I would like to remove. If you look at the picture, I want the red rectangle to fill the whole cell. Does anybody know how to achieve this?
Here's my current code for that column:
TableColumn("name", value: \.name) { item in
HStack {
if item.type == .directory {
Image(systemName: "folder")
.frame(width: 14)
} else {
Text("")
.frame(width: 14)
}
Text(item.name)
Spacer()
}
.contentShape(Rectangle())
.gesture(TapGesture(count: 2).onEnded {
print(item)
doubleClick(path: item.path)
}).simultaneousGesture(TapGesture().onEnded {
self.selectedFileItem = item.id
})
.background(.red)
.padding(.all, 0)
}
I want this to be applied to all columns in the table. I only showed one column as a reference. The table style is
.tableStyle(.bordered(alternatesRowBackgrounds: true))
Hi, I'm following the iOS AppDev Tutorials by Apple, and stumbled upon the following lines of code:
struct TrailingIconLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.title
configuration.icon
}
}
}
extension LabelStyle where Self == TrailingIconLabelStyle {
static var trailingIcon: Self { Self() }
}
And I have a few questions here.
I understand that this is about extending the type LabelStyle. And from the docs, I understand that where Self == TrailingIconLabelStyle means that the extension will only take place on LabelStyle in case the type is TrailingIconLabelStyle. Why do I have to create a separate extension? Why not add trailingIcon to TrailingIconLabelStyle directly?
Why is trailingIcon static. Doesn't that mean that all instances always reference the same value?
What does Self { Self() } mean? I assume this is some closure that can also be written like Self(x in { Self() }) or so. But I don't really get this.
I'm trying to connect two players with each other using GameKit in a very simple game. I want to use GKMatchmaker.shared().findMatch as I don't want to show any GameCenter related view controllers. (to keep it simple)
Problem:
Even though GameKit creates a match after finding two players, an error occurs that prevents either player from sending any message to the others.
Current Situation:
The basic code is as follows (based on the docs described here: https://developer.apple.com/documentation/gamekit/finding_multiple_players_for_a_game)
print("Requesting multiplayer match")
let request = GKMatchRequest()
request.minPlayers = 2
request.maxPlayers = 2
request.recipientResponseHandler = {(player: GKPlayer, respnse: GKInviteRecipientResponse) -> Void in
print("new player about to join")
print(player.alias)
print(respnse)
}
GKMatchmaker.shared().findMatch(for: request, withCompletionHandler: {
(match: GKMatch?, error: Error?) -> Void in
if error != nil {
// Handle the error that occurred finding a match.
print("error during matchmaking")
print(error as Any)
} else if match != nil {
guard let match = match else { return }
print("connected to \(match.players.count) players")
// load the multiplayer data handler
let handler = MultiMatchHandler()
match.delegate = handler
// load the multiplayer service
let service = MultiMatchService(match: match)
service.sendMessageToAll(text: "Hello from the other side")
// finish the match making
GKMatchmaker.shared().finishMatchmaking(for: match)
// Start the game with the players in the match.
self.view?.presentScene(GameScene.newScene(multiplayer: service))
}
})
The output of that is
Requesting multiplayer match
2022-01-05 01:19:16.554959+0100 Grapefruit[38300:10026027] [Match] cannot set connecting state for players: (
"<GKPlayer: 0x282add280>(alias:... gamePlayerID:... teamPlayerID:... name:... status:(null) friendBiDirectional:0 friendPlayedWith:1 friendPlayedNearby:0 acceptedGameInviteFromThisFriend:0 initiatedGameInviteToThisFriend:0 automatchedTogether:1)"
), as there is no inviteDelegate set yet. The state might directly change to Ready when we set the inviteDelegate later and call sendQueuedStatesAndPackets.
2022-01-05 01:19:16.557002+0100 Grapefruit[38300:10026027] [Match] syncPlayers failed to loadPlayersForLegacyIdentifiers: (
"..."
)
connected to 0 players
sending text Hello from the other side failed
Findings:
minPlayers is set to 2. As the completion handler is called this means that at least one more player was found. But the number of players returned in match.players.count is 0
The matcher shows an error saying that cannot set connecting state for players ... as there is no inviteDelegate set yet. I can't find any info about this invite delegate.
Actual Question:
What is an inviteDelegate? Do I really need to implement such (if yes, then how?)? (I don't think so as the docs state that the match only starts after the invites are accepted).
How can I resolve this issue?