Disclaimer: Two months new to programming Swift/Xcode, so please forgive me for any errors.
Like others here, I needed to apply some watermarks as well, and in a multi-platform app. Most examples depended on "AppKit" components, which I'm learning are exclusive to macOS (again, I'm new); or they depended on "UIKit" components, which are apparently exclusive to iOS. Plus, the most helpful/capable suggestions for me that I found here used "UIGraphicsBeginImageContext". When I caved-in (iOS only) to use the examples, they worked great, but that one, particular UI method is marked as 'deprecated' (at least by late 2024), so I felt I had to look further.
My endeavors led me down to the core, the 'Core' that is. All the lower-level graphics I had to do forced me into Core Imaging and Core Graphics frameworks anyway, where everything is touchy, optional, somewhat tricky, and can differ to UI orientations, but I got it to all work great, and decided I should try to share, hoping it might help someone else. There are small preliminary steps to convert other images back down to CI/CG, which seems to be their basis anyway but it's not hard to do and feels native to UIImage and NSImage.
You'd need to add the 'import CoreImage.CIFilterBuiltins' to your standard imports, if you hadn't already by that point. Also, in the example, I have 2 pre-made overlays with transparent alpha channels stored prior ("theWatermarkCIImage" and "theClippingRectCIImage") that are the same size [easiest] as the expected underlaid (background) image is, and the func lets the input boolean choose which to apply (this choice-function is custom for my uses, but you could just pass in an overlay/watermark image instead of the boolean as the second input, and edit the code with "let dummer.inputImage = myWatermarkImage"). And also, there is an 'if false {}' block which I switched on and off while developing for immediately testing the results. May be helpful for you too, so left it in here:
import CoreImage.CIFilterBuiltins
func applyOverlayToCIImage(_ theUnderlaidImage: CIImage, _ useWatermark: Bool) -> CGImage {
let dum = CIContext()
let dummer = CIFilter.sourceOverCompositing()
dummer.inputImage = (useWatermark ? theWatermarkCIImage : theClippingRectCIImage)
dummer.backgroundImage = theUnderlaidImage
let dummest = dummer.outputImage!
let cgImage = dum.createCGImage(dummest, from: dummest.extent)
if false {
let dumURL = URL(filePath: "/Users/someUserName/Desktop/someUniqueFileName.jpg")
do {try dum.writeJPEGRepresentation(of: dummest, to: dumURL, colorSpace: CGColorSpaceCreateDeviceRGB()) }
catch { print("Could not write JPEG to file to: \(dumURL)\nBecause Error: \(error)") } }
return cgImage!
}
Hope it helps.