Cross Platform IOS (UIPasteboard) vs MacOS (NSPasteboard) compatibility

I am developing an app which should run on Ipad, Iphone and Mac. From the app the user shall be able to press a button and copy a result (pure text) into the clipboard so it can be pasted into other apps on the same device (command + V).

This works fairly well on my Ipad (or Iphone) where I use this (IOS) code:

import SwiftUI
import UniformTypeIdentifiers
.....
  UIPasteboard.general.setValue(output, 
         forPasteboardType: UTType.plainText.identifier)
....

The problem is that when I run this on "My Mac (Designed for iPad) - it does not copy the text when run on Mac as compatible iPad app.

I take note that to access the clipboard on MacOS you would need another (MacOS) code like this.

import Cocoa
  ...
let pasteboard = NSPasteboard.general()
pasteboard.declareTypes([NSPasteboardTypeString], owner: nil)
pasteboard.setString(output, forType: NSPasteboardTypeString)
 ....

The problem is that Cocoa can only be imported into native MacOS app - and while I recognise this could be handled with a lot of pre-processor statements separating native IOS and native MacOS code - it would highly complicate my code - and I would almost need to write the app twice just switching all the UI's to NS's.

#if os(iOS)
    UIPasteboard.general.setValue(output, 
         forPasteboardType: UTType.plainText.identifier)
#elseif os(macOS)
    let pasteboard = NSPasteboard.general() 
    pasteboard.declareTypes([NSPasteboardTypeString], owner: nil)
    pasteboard.setString(output, forType: NSPasteboardTypeString)
#endif

Even for the clipboard example it would start to complicate things. Then add to this that have have several references to other UI-classes which would be NS-classes on a MacOS.

Can this be handled in a smarter way now we actually can run SwiftUI and IOS apps on a Mac????

I know one way could be to copy my output into a text-field from which the user could "Command + C" - but it is really not what I want.

I hope there is something clean and smart for this - so we can assume that IOS apps can actually run on Mac with full compatibility.

Thanks in advance

Replies

Designed for iPad yields an app using the iOS Apps on Mac technology. In that case you have to use UIPasteboard. NSPasteboard is only supported for an AppKit app.

Having said that, UIPasteboard should work just fine for this. I just tested this myself:

  1. I create a new app from the iOS > App template.

  2. I selected My Mac (Designed for iPad) as the run destination.

  3. I ran the app, just to get it working.

  4. I then added button this button to the VStack:

    Button("Copy") {
        let pb = UIPasteboard.general
        pb.setObjects(["Hello Cruel World!"])
    }
    
  5. And ran the app again.

  6. When I clicked on the Copy button, it set the clipboard to Hello Cruel World!.

I’m not sure what’s going on with your app, but the basics seem to work.

ps This is using Xcode 15.3 on macOS 14.4.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I did it differently to be able to run on iOS or macOS, testing for OS type. Xcode 15.0 and 15.3.

@eskimo: is it useless ?

#if os(macOS)
    typealias XPasteboard = NSPasteboard
#else
    typealias XPasteboard = UIPasteboard
#endif

extension XPasteboard {
    
    func copyString(_ text: String) {
#if os(macOS)
        self.clearContents()
        self.setString(text, forType: .string)
#else
        self.string = text
#endif
    }
    
    func copyImage(_ image: Data) {
#if os(macOS)
        self.clearContents()
        self.setData(image, forType: .png)
#else
        self.image = UIImage(data: image)
#endif
    }
    
}

This gets complicated. There are three ways of running SwiftUI code on your Mac:


Westerlin asked about Designed for iPad. That runs your iOS binary unmodified on macOS, which means that you can’t use compile-time conditional code to change its behaviour. You can, however, use the isiOSAppOnMac property to change behaviour at runtime.


In Mac Catalyst your app is built specifically for the Mac, so you can use conditional compilation. However, you can’t use os(macOS) because… well… that gets into philosophy (-: But consider this:

#if os(macOS)
#warning("building for macOS")
#endif

#if targetEnvironment(macCatalyst)
#warning("building for Mac Catalyst")
#endif

A Mac Catalyst app generates the second warning, not the first.

A Mac Catalyst app can import AppKit but it can’t use NSPasteboard. You get this error:

let pb = NSPasteboard.general
      // ^ 'NSPasteboard' is unavailable in Mac Catalyst

Folks have various tricks for getting around this, but I recommend against going down that path.


A native SwiftUI app has full access to AppKit and all other macOS APIs. It is, IMO, the best way to build a Mac app. And the nice thing about SwiftUI is that you can reuse a lot of your UI code across all Apple platforms.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"