Sometimes its easier to just enable MacCatalyst on an iOS app to bring it to the Mac than to fiddle around with a dedicated Mac target or using the Mac version of SwiftUI. The drawback is that you don’t have access to AppKit but have to stick with UIKit / SwiftUI. So some quite useful features of AppKit are not available in MacCatalyst.
E.g. there is this nice AppKit construct to save and restore the window size and position between app launches:
| 
					 1  | 
						controller.windowFrameAutosaveName = NSWindow.FrameAutosaveName("MyWindow")  | 
					
But there is no equivalent in MacCatalyst since iOS apps don’t mess around with windows. In this post I’ll show how to achieve a similar behaviour in MacCatalyst.
First of all we have to implement a SceneDelegate for our app. That’s straightforward for UIKit but a little more complicated for SwiftUI. Take a look at this post for that:
https://www.fivestars.blog/articles/app-delegate-scene-delegate-swiftui/
It’s a good idea to remember the corresponding window scene in the delegate:
| 
					 1 2 3  | 
						class MySceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject {     var scene: UIWindowScene?                           // remember our window scene …  | 
					
And then we’ll implement this delegate function:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  | 
						    … 	func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { #if targetEnvironment(macCatalyst)                      // only for running on a Mac         guard let windowScene = scene as? UIWindowScene else { return }         self.scene = windowScene                        // remember our window scene         var preferredRect: CGRect? {                    // is a size preference present?             var rect: CGRect?                           // by default its not             let width = UserDefaults.standard.double(forKey: MyMessages.windowSizeWidth)             let height = UserDefaults.standard.double(forKey: MyMessages.windowSizeHeight)             let x = UserDefaults.standard.double(forKey: MyMessages.windowOriginX)             let y = UserDefaults.standard.double(forKey: MyMessages.windowOriginY)             if width > 0 && height > 0 {                // we do have a valid size                 rect = CGRect(x: x, y: y, width: width, height: height)             }             return rect                                 // return the rect (or nothing)         }         if let rect = preferredRect {                   // we do have a rect             let geoPrefs = UIWindowScene.GeometryPreferences.Mac(systemFrame: rect)             windowScene.requestGeometryUpdate(geoPrefs) // apply it to the current scene         } #endif     } }  | 
					
After we’ve made sure that we do have a window scene we’ll store that scene in the above mentioned var. In the computed property preferredRect we’ll look for a possibly stored window rect. How that is stored we’ll see in a minute.
If that rect is available we put it in GeometryPreferences for the Mac and request an update of the geometry. That’s it for the restore.
To store the current geometry of our window we can use any View in our app with a GeometryReader:
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  | 
						struct MyView: View {     @EnvironmentObject var sceneDelegate: MySceneDelegate …     var body: some View {         GeometryReader { geo in …             .onChange(of: geo.size) {               // size changed #if targetEnvironment(macCatalyst)                  // are we running on a Mac?                 if let rect = sceneDelegate.scene?.effectiveGeometry.systemFrame {  // get the system frame from the scene delegate                     UserDefaults.standard.set(rect.width, forKey: MyMessages.windowSizeWidth)    	// and set the user defaults                     UserDefaults.standard.set(rect.height, forKey: MyMessages.windowSizeHeight)                     UserDefaults.standard.set(rect.origin.x, forKey: MyMessages.windowOriginX)                     UserDefaults.standard.set(rect.origin.y, forKey: MyMessages.windowOriginY)                 } #endif             }        	}     } }  | 
					
Here we get the current system frame from our sceneDelegate and store in the UserDefaults. Well, that’s it. Not too difficult, is it?