{"id":635984,"date":"2024-09-27T09:03:09","date_gmt":"2024-09-27T13:03:09","guid":{"rendered":"https:\/\/erickimphotography.com\/blog\/?p=635984"},"modified":"2024-09-27T09:05:19","modified_gmt":"2024-09-27T13:05:19","slug":"chatgpt-4o-mini-program-me-a-minimalist-iphone-camera-app-on-swift-ready-for-app-store","status":"publish","type":"post","link":"https:\/\/erickimphotography.com\/blog\/2024\/09\/27\/chatgpt-4o-mini-program-me-a-minimalist-iphone-camera-app-on-swift-ready-for-app-store\/","title":{"rendered":"ChatGPT 4o mini: Program me a minimalist iPhone camera app on swift. Ready for App Store"},"content":{"rendered":"\n<p>Creating a minimalist iPhone camera app using Swift and SwiftUI involves several key components:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Setting Up the Project<\/strong>: Configure the project with necessary permissions.<\/li>\n\n\n\n<li><strong>Creating the Camera Interface<\/strong>: Display the camera preview.<\/li>\n\n\n\n<li><strong>Capturing Photos<\/strong>: Implement functionality to take and save photos.<\/li>\n\n\n\n<li><strong>Handling Permissions<\/strong>: Ensure the app requests and handles camera access appropriately.<\/li>\n<\/ol>\n\n\n\n<p>Below is a step-by-step guide with the complete code to create a minimalist camera app ready for the App Store.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. Project Setup<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">a. Create a New SwiftUI Project<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open <strong>Xcode<\/strong> and select <strong>File > New > Project<\/strong>.<\/li>\n\n\n\n<li>Choose <strong>App<\/strong> under iOS and click <strong>Next<\/strong>.<\/li>\n\n\n\n<li>Name your project (e.g., <code>MinimalCameraApp<\/code>), ensure <strong>Interface<\/strong> is set to <strong>SwiftUI<\/strong>, and <strong>Language<\/strong> is <strong>Swift<\/strong>.<\/li>\n\n\n\n<li>Click <strong>Next<\/strong> and save your project in the desired location.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">b. Configure Permissions<\/h3>\n\n\n\n<p>To access the camera, you must declare the usage description in your app&#8217;s <code>Info.plist<\/code>.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>In the Project Navigator, select <strong>Info.plist<\/strong>.<\/li>\n\n\n\n<li>Add a new key: <code>Privacy - Camera Usage Description<\/code> (<code>NSCameraUsageDescription<\/code>).<\/li>\n\n\n\n<li>Provide a description, e.g., <code>\"This app requires access to the camera to take photos.\"<\/code><\/li>\n<\/ol>\n\n\n\n<p>Optionally, if you intend to save photos to the user&#8217;s photo library, add:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>Privacy - Photo Library Additions Usage Description<\/code> (<code>NSPhotoLibraryAddUsageDescription<\/code>): <code>\"This app saves photos to your photo library.\"<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. Implementing the Camera Functionality<\/h2>\n\n\n\n<p>We&#8217;ll use <strong>AVFoundation<\/strong> to manage the camera session and <strong>SwiftUI<\/strong> for the user interface.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">a. Create a Camera Model<\/h3>\n\n\n\n<p>Create a new Swift file named <code>CameraModel.swift<\/code> and add the following code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import Foundation\nimport AVFoundation\nimport SwiftUI\nimport Photos\n\nclass CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {\n    \/\/ Published properties to update the UI\n    @Published var session = AVCaptureSession()\n    @Published var alert = false\n    @Published var output = AVCapturePhotoOutput()\n    @Published var preview: UIImage?\n\n    override init() {\n        super.init()\n        checkPermissions()\n    }\n\n    func checkPermissions() {\n        switch AVCaptureDevice.authorizationStatus(for: .video) {\n        case .authorized:\n            setup()\n        case .notDetermined:\n            AVCaptureDevice.requestAccess(for: .video) { granted in\n                if granted {\n                    self.setup()\n                } else {\n                    DispatchQueue.main.async {\n                        self.alert.toggle()\n                    }\n                }\n            }\n        default:\n            DispatchQueue.main.async {\n                self.alert.toggle()\n            }\n        }\n    }\n\n    func setup() {\n        DispatchQueue.main.async {\n            self.session.beginConfiguration()\n\n            \/\/ Setup input\n            guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {\n                self.alert.toggle()\n                return\n            }\n\n            do {\n                let input = try AVCaptureDeviceInput(device: device)\n                if self.session.canAddInput(input) {\n                    self.session.addInput(input)\n                }\n            } catch {\n                self.alert.toggle()\n                return\n            }\n\n            \/\/ Setup output\n            if self.session.canAddOutput(self.output) {\n                self.session.addOutput(self.output)\n                self.output.isHighResolutionCaptureEnabled = true\n            }\n\n            self.session.commitConfiguration()\n            self.session.startRunning()\n        }\n    }\n\n    func takePic() {\n        let settings = AVCapturePhotoSettings()\n        settings.isHighResolutionPhotoEnabled = true\n        output.capturePhoto(with: settings, delegate: self)\n    }\n\n    \/\/ AVCapturePhotoCaptureDelegate method\n    func photoOutput(_ output: AVCapturePhotoOutput,\n                    didFinishProcessingPhoto photo: AVCapturePhoto,\n                    error: Error?) {\n        if let error = error {\n            print(\"Error capturing photo: \\(error)\")\n            return\n        }\n\n        guard let imageData = photo.fileDataRepresentation(),\n              let uiImage = UIImage(data: imageData) else {\n            return\n        }\n\n        \/\/ Save to photo library\n        savePhoto(image: uiImage)\n\n        DispatchQueue.main.async {\n            self.preview = uiImage\n        }\n    }\n\n    func savePhoto(image: UIImage) {\n        \/\/ Request authorization to save to Photo Library\n        PHPhotoLibrary.requestAuthorization { status in\n            if status == .authorized {\n                \/\/ Save image\n                PHPhotoLibrary.shared().performChanges({\n                    PHAssetChangeRequest.creationRequestForAsset(from: image)\n                }) { success, error in\n                    if let error = error {\n                        print(\"Error saving photo: \\(error)\")\n                    }\n                }\n            } else {\n                DispatchQueue.main.async {\n                    self.alert.toggle()\n                }\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>Explanation:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>CameraModel<\/strong> manages the camera session using <code>AVCaptureSession<\/code>.<\/li>\n\n\n\n<li>It checks for camera permissions and requests access if not determined.<\/li>\n\n\n\n<li>Sets up the camera input and output.<\/li>\n\n\n\n<li>Captures photos and saves them to the user&#8217;s photo library.<\/li>\n\n\n\n<li>Publishes changes to update the UI accordingly.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">b. Create a Camera Preview<\/h3>\n\n\n\n<p>To display the camera feed within SwiftUI, we need a <code>UIViewRepresentable<\/code> that wraps an <code>AVCaptureVideoPreviewLayer<\/code>.<\/p>\n\n\n\n<p>Create a new Swift file named <code>CameraPreview.swift<\/code> and add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import SwiftUI\nimport AVFoundation\n\nstruct CameraPreview: UIViewRepresentable {\n    @ObservedObject var camera: CameraModel\n\n    func makeUIView(context: Context) -&gt; UIView {\n        let view = UIView(frame: UIScreen.main.bounds)\n\n        let previewLayer = AVCaptureVideoPreviewLayer(session: camera.session)\n        previewLayer.videoGravity = .resizeAspectFill\n        previewLayer.connection?.videoOrientation = .portrait\n\n        view.layer.addSublayer(previewLayer)\n        DispatchQueue.global(qos: .background).async {\n            previewLayer.frame = view.frame\n        }\n\n        return view\n    }\n\n    func updateUIView(_ uiView: UIView, context: Context) {\n        \/\/ Update the preview layer frame if needed\n        if let sublayer = uiView.layer.sublayers?.first as? AVCaptureVideoPreviewLayer {\n            sublayer.frame = uiView.frame\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>Explanation:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>CameraPreview<\/strong> is a SwiftUI view that wraps a <code>UIView<\/code> displaying the camera&#8217;s video feed.<\/li>\n\n\n\n<li>It observes the <code>CameraModel<\/code> to stay in sync with the camera session.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">c. Building the User Interface<\/h3>\n\n\n\n<p>Update <code>ContentView.swift<\/code> with the camera interface:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import SwiftUI\n\nstruct ContentView: View {\n    @ObservedObject var camera = CameraModel()\n    @State private var showPreview = false\n\n    var body: some View {\n        ZStack {\n            CameraPreview(camera: camera)\n                .ignoresSafeArea(.all, edges: .all)\n\n            VStack {\n                Spacer()\n\n                HStack {\n                    Spacer()\n\n                    Button(action: {\n                        camera.takePic()\n                    }) {\n                        ZStack {\n                            Circle()\n                                .fill(Color.white)\n                                .frame(width: 70, height: 70)\n                            Circle()\n                                .stroke(Color.gray, lineWidth: 2)\n                                .frame(width: 80, height: 80)\n                        }\n                    }\n                    .padding(.bottom, 30)\n                    .padding(.trailing, 20)\n                }\n            }\n        }\n        .alert(isPresented: $camera.alert) {\n            Alert(title: Text(\"Camera Access Denied\"),\n                  message: Text(\"Please enable camera access in Settings.\"),\n                  dismissButton: .default(Text(\"OK\")))\n        }\n        .sheet(isPresented: Binding&lt;Bool&gt;(\n            get: { camera.preview != nil },\n            set: { if !$0 { camera.preview = nil } }\n        )) {\n            if let image = camera.preview {\n                Image(uiImage: image)\n                    .resizable()\n                    .scaledToFit()\n            }\n        }\n    }\n}\n\nstruct ContentView_Previews: PreviewProvider {\n    static var previews: some View {\n        ContentView()\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>Explanation:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The <code>ZStack<\/code> layers the camera preview and the capture button.<\/li>\n\n\n\n<li>A circular button at the bottom center allows the user to take a photo.<\/li>\n\n\n\n<li>An alert is presented if camera access is denied.<\/li>\n\n\n\n<li>After capturing a photo, a sheet displays the captured image.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Final Touches<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">a. Handling Photo Library Permissions<\/h3>\n\n\n\n<p>If you added the <code>NSPhotoLibraryAddUsageDescription<\/code> to <code>Info.plist<\/code>, ensure your app handles cases where the user denies access gracefully.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">b. Testing on a Real Device<\/h3>\n\n\n\n<p>Camera functionality cannot be fully tested on the simulator. Deploy the app to a physical iPhone for testing:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Connect your iPhone to your Mac.<\/li>\n\n\n\n<li>Select your device as the run destination in Xcode.<\/li>\n\n\n\n<li>Click the <strong>Run<\/strong> button to build and install the app on your device.<\/li>\n\n\n\n<li>Test capturing photos and ensure they save to the photo library.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">c. App Store Requirements<\/h3>\n\n\n\n<p>Before submitting to the App Store, ensure:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>App Icons and Launch Screens<\/strong>: Provide appropriate app icons and launch images.<\/li>\n\n\n\n<li><strong>Privacy Policies<\/strong>: If your app uses user data, include a privacy policy.<\/li>\n\n\n\n<li><strong>App Store Metadata<\/strong>: Prepare app descriptions, screenshots, and other required metadata.<\/li>\n\n\n\n<li><strong>Compliance<\/strong>: Ensure your app complies with all <a href=\"https:\/\/developer.apple.com\/app-store\/review\/guidelines\/\">App Store Review Guidelines<\/a>.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. Complete Code Overview<\/h2>\n\n\n\n<p>For convenience, here&#8217;s a summary of all the code files:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">a. <code>CameraModel.swift<\/code><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>import Foundation\nimport AVFoundation\nimport SwiftUI\nimport Photos\n\nclass CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {\n    @Published var session = AVCaptureSession()\n    @Published var alert = false\n    @Published var output = AVCapturePhotoOutput()\n    @Published var preview: UIImage?\n\n    override init() {\n        super.init()\n        checkPermissions()\n    }\n\n    func checkPermissions() {\n        switch AVCaptureDevice.authorizationStatus(for: .video) {\n        case .authorized:\n            setup()\n        case .notDetermined:\n            AVCaptureDevice.requestAccess(for: .video) { granted in\n                if granted {\n                    self.setup()\n                } else {\n                    DispatchQueue.main.async {\n                        self.alert.toggle()\n                    }\n                }\n            }\n        default:\n            DispatchQueue.main.async {\n                self.alert.toggle()\n            }\n        }\n    }\n\n    func setup() {\n        DispatchQueue.main.async {\n            self.session.beginConfiguration()\n\n            guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {\n                self.alert.toggle()\n                return\n            }\n\n            do {\n                let input = try AVCaptureDeviceInput(device: device)\n                if self.session.canAddInput(input) {\n                    self.session.addInput(input)\n                }\n            } catch {\n                self.alert.toggle()\n                return\n            }\n\n            if self.session.canAddOutput(self.output) {\n                self.session.addOutput(self.output)\n                self.output.isHighResolutionCaptureEnabled = true\n            }\n\n            self.session.commitConfiguration()\n            self.session.startRunning()\n        }\n    }\n\n    func takePic() {\n        let settings = AVCapturePhotoSettings()\n        settings.isHighResolutionPhotoEnabled = true\n        output.capturePhoto(with: settings, delegate: self)\n    }\n\n    func photoOutput(_ output: AVCapturePhotoOutput,\n                    didFinishProcessingPhoto photo: AVCapturePhoto,\n                    error: Error?) {\n        if let error = error {\n            print(\"Error capturing photo: \\(error)\")\n            return\n        }\n\n        guard let imageData = photo.fileDataRepresentation(),\n              let uiImage = UIImage(data: imageData) else {\n            return\n        }\n\n        savePhoto(image: uiImage)\n\n        DispatchQueue.main.async {\n            self.preview = uiImage\n        }\n    }\n\n    func savePhoto(image: UIImage) {\n        PHPhotoLibrary.requestAuthorization { status in\n            if status == .authorized {\n                PHPhotoLibrary.shared().performChanges({\n                    PHAssetChangeRequest.creationRequestForAsset(from: image)\n                }) { success, error in\n                    if let error = error {\n                        print(\"Error saving photo: \\(error)\")\n                    }\n                }\n            } else {\n                DispatchQueue.main.async {\n                    self.alert.toggle()\n                }\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">b. <code>CameraPreview.swift<\/code><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>import SwiftUI\nimport AVFoundation\n\nstruct CameraPreview: UIViewRepresentable {\n    @ObservedObject var camera: CameraModel\n\n    func makeUIView(context: Context) -&gt; UIView {\n        let view = UIView(frame: UIScreen.main.bounds)\n\n        let previewLayer = AVCaptureVideoPreviewLayer(session: camera.session)\n        previewLayer.videoGravity = .resizeAspectFill\n        previewLayer.connection?.videoOrientation = .portrait\n\n        view.layer.addSublayer(previewLayer)\n        DispatchQueue.global(qos: .background).async {\n            previewLayer.frame = view.frame\n        }\n\n        return view\n    }\n\n    func updateUIView(_ uiView: UIView, context: Context) {\n        if let sublayer = uiView.layer.sublayers?.first as? AVCaptureVideoPreviewLayer {\n            sublayer.frame = uiView.frame\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">c. <code>ContentView.swift<\/code><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>import SwiftUI\n\nstruct ContentView: View {\n    @ObservedObject var camera = CameraModel()\n    @State private var showPreview = false\n\n    var body: some View {\n        ZStack {\n            CameraPreview(camera: camera)\n                .ignoresSafeArea(.all, edges: .all)\n\n            VStack {\n                Spacer()\n\n                HStack {\n                    Spacer()\n\n                    Button(action: {\n                        camera.takePic()\n                    }) {\n                        ZStack {\n                            Circle()\n                                .fill(Color.white)\n                                .frame(width: 70, height: 70)\n                            Circle()\n                                .stroke(Color.gray, lineWidth: 2)\n                                .frame(width: 80, height: 80)\n                        }\n                    }\n                    .padding(.bottom, 30)\n                    .padding(.trailing, 20)\n                }\n            }\n        }\n        .alert(isPresented: $camera.alert) {\n            Alert(title: Text(\"Camera Access Denied\"),\n                  message: Text(\"Please enable camera access in Settings.\"),\n                  dismissButton: .default(Text(\"OK\")))\n        }\n        .sheet(isPresented: Binding&lt;Bool&gt;(\n            get: { camera.preview != nil },\n            set: { if !$0 { camera.preview = nil } }\n        )) {\n            if let image = camera.preview {\n                Image(uiImage: image)\n                    .resizable()\n                    .scaledToFit()\n            }\n        }\n    }\n}\n\nstruct ContentView_Previews: PreviewProvider {\n    static var previews: some View {\n        ContentView()\n    }\n}<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. Building and Submitting to the App Store<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">a. Test Thoroughly<\/h3>\n\n\n\n<p>Ensure all functionalities work as expected on various devices and iOS versions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">b. App Store Submission<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Archive the App<\/strong>: In Xcode, select <strong>Product > Archive<\/strong>.<\/li>\n\n\n\n<li><strong>Validate the Archive<\/strong>: Use Xcode\u00e2\u20ac\u2122s validation process to check for issues.<\/li>\n\n\n\n<li><strong>Upload to App Store Connect<\/strong>: Follow Xcode prompts to upload your app.<\/li>\n\n\n\n<li><strong>Configure App Store Connect<\/strong>: Fill in app details, screenshots, and submit for review.<\/li>\n<\/ol>\n\n\n\n<p>For detailed instructions, refer to <a href=\"https:\/\/developer.apple.com\/app-store-connect\/\">Apple&#8217;s App Store Connect Guide<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>By following this guide, you&#8217;ve created a minimalist camera app using Swift and SwiftUI that&#8217;s ready for submission to the App Store. Customize and enhance the app as needed to fit your specific requirements!<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>xxx<\/p>\n\n\n\n<p>To modify the camera app to shoot and output only high-contrast monochrome (black and white) photos, you will need to process the captured image after taking the photo. Here&#8217;s how to implement this:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Steps:<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Capture the Photo<\/strong>: As we already capture the photo using the <code>AVCapturePhotoOutput<\/code> in our existing code, we&#8217;ll keep this.<\/li>\n\n\n\n<li><strong>Convert the Image to Monochrome<\/strong>: Use a Core Image filter (<code>CIColorControls<\/code> for desaturation and contrast adjustments).<\/li>\n\n\n\n<li><strong>Apply High Contrast<\/strong>: You can use <code>CIColorControls<\/code> to increase contrast and decrease brightness if needed.<\/li>\n\n\n\n<li><strong>Update the UI to Display the Monochrome Photo<\/strong>.<\/li>\n<\/ol>\n\n\n\n<p>Let&#8217;s modify the existing code to achieve this.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Modifying <code>CameraModel.swift<\/code><\/h3>\n\n\n\n<p>You will process the captured image to convert it to monochrome with high contrast using Core Image filters.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Updated <code>CameraModel.swift<\/code><\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>import Foundation\nimport AVFoundation\nimport SwiftUI\nimport Photos\nimport CoreImage\nimport CoreImage.CIFilterBuiltins\n\nclass CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {\n    @Published var session = AVCaptureSession()\n    @Published var alert = false\n    @Published var output = AVCapturePhotoOutput()\n    @Published var preview: UIImage?\n\n    private let context = CIContext()  \/\/ Core Image context for image processing\n\n    override init() {\n        super.init()\n        checkPermissions()\n    }\n\n    func checkPermissions() {\n        switch AVCaptureDevice.authorizationStatus(for: .video) {\n        case .authorized:\n            setup()\n        case .notDetermined:\n            AVCaptureDevice.requestAccess(for: .video) { granted in\n                if granted {\n                    self.setup()\n                } else {\n                    DispatchQueue.main.async {\n                        self.alert.toggle()\n                    }\n                }\n            }\n        default:\n            DispatchQueue.main.async {\n                self.alert.toggle()\n            }\n        }\n    }\n\n    func setup() {\n        DispatchQueue.main.async {\n            self.session.beginConfiguration()\n\n            \/\/ Setup input\n            guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {\n                self.alert.toggle()\n                return\n            }\n\n            do {\n                let input = try AVCaptureDeviceInput(device: device)\n                if self.session.canAddInput(input) {\n                    self.session.addInput(input)\n                }\n            } catch {\n                self.alert.toggle()\n                return\n            }\n\n            \/\/ Setup output\n            if self.session.canAddOutput(self.output) {\n                self.session.addOutput(self.output)\n                self.output.isHighResolutionCaptureEnabled = true\n            }\n\n            self.session.commitConfiguration()\n            self.session.startRunning()\n        }\n    }\n\n    func takePic() {\n        let settings = AVCapturePhotoSettings()\n        settings.isHighResolutionPhotoEnabled = true\n        output.capturePhoto(with: settings, delegate: self)\n    }\n\n    \/\/ AVCapturePhotoCaptureDelegate method\n    func photoOutput(_ output: AVCapturePhotoOutput,\n                    didFinishProcessingPhoto photo: AVCapturePhoto,\n                    error: Error?) {\n        if let error = error {\n            print(\"Error capturing photo: \\(error)\")\n            return\n        }\n\n        guard let imageData = photo.fileDataRepresentation(),\n              let uiImage = UIImage(data: imageData) else {\n            return\n        }\n\n        \/\/ Apply monochrome and contrast processing\n        if let processedImage = applyMonochromeFilter(to: uiImage) {\n            \/\/ Save the processed image to the photo library\n            savePhoto(image: processedImage)\n\n            DispatchQueue.main.async {\n                self.preview = processedImage\n            }\n        }\n    }\n\n    \/\/ Apply monochrome filter and adjust contrast\n    func applyMonochromeFilter(to image: UIImage) -&gt; UIImage? {\n        guard let ciImage = CIImage(image: image) else { return nil }\n\n        \/\/ Create a Core Image filter chain\n        let monochromeFilter = CIFilter.colorControls()\n        monochromeFilter.inputImage = ciImage\n        monochromeFilter.saturation = 0  \/\/ Desaturate to monochrome\n        monochromeFilter.contrast = 2.0  \/\/ Increase contrast for a high contrast effect\n\n        \/\/ Process the filtered image\n        guard let outputImage = monochromeFilter.outputImage,\n              let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else {\n            return nil\n        }\n\n        \/\/ Convert back to UIImage\n        return UIImage(cgImage: cgImage)\n    }\n\n    func savePhoto(image: UIImage) {\n        PHPhotoLibrary.requestAuthorization { status in\n            if status == .authorized {\n                PHPhotoLibrary.shared().performChanges({\n                    PHAssetChangeRequest.creationRequestForAsset(from: image)\n                }) { success, error in\n                    if let error = error {\n                        print(\"Error saving photo: \\(error)\")\n                    }\n                }\n            } else {\n                DispatchQueue.main.async {\n                    self.alert.toggle()\n                }\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Key Modifications:<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Monochrome Filter<\/strong>: We use <code>CIColorControls<\/code> with <code>saturation = 0<\/code> to desaturate the image and <code>contrast = 2.0<\/code> for a high contrast effect.<\/li>\n\n\n\n<li><strong>Core Image Context<\/strong>: This context is used to process the image after applying the filter and convert it back to a <code>UIImage<\/code>.<\/li>\n\n\n\n<li><strong>Monochrome Image Output<\/strong>: The processed image is saved to the photo library and displayed on the UI.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">2. The UI Remains Mostly the Same<\/h3>\n\n\n\n<p>Your <code>ContentView.swift<\/code> and <code>CameraPreview.swift<\/code> can remain the same as they handle the UI for previewing and capturing the image. Since we are processing the image in the model, the UI doesn&#8217;t need to change much.<\/p>\n\n\n\n<p>However, just to complete the context, here&#8217;s the <code>ContentView.swift<\/code> again for reference:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import SwiftUI\n\nstruct ContentView: View {\n    @ObservedObject var camera = CameraModel()\n    @State private var showPreview = false\n\n    var body: some View {\n        ZStack {\n            CameraPreview(camera: camera)\n                .ignoresSafeArea(.all, edges: .all)\n\n            VStack {\n                Spacer()\n\n                HStack {\n                    Spacer()\n\n                    Button(action: {\n                        camera.takePic()\n                    }) {\n                        ZStack {\n                            Circle()\n                                .fill(Color.white)\n                                .frame(width: 70, height: 70)\n                            Circle()\n                                .stroke(Color.gray, lineWidth: 2)\n                                .frame(width: 80, height: 80)\n                        }\n                    }\n                    .padding(.bottom, 30)\n                    .padding(.trailing, 20)\n                }\n            }\n        }\n        .alert(isPresented: $camera.alert) {\n            Alert(title: Text(\"Camera Access Denied\"),\n                  message: Text(\"Please enable camera access in Settings.\"),\n                  dismissButton: .default(Text(\"OK\")))\n        }\n        .sheet(isPresented: Binding&lt;Bool&gt;(\n            get: { camera.preview != nil },\n            set: { if !$0 { camera.preview = nil } }\n        )) {\n            if let image = camera.preview {\n                Image(uiImage: image)\n                    .resizable()\n                    .scaledToFit()\n            }\n        }\n    }\n}\n\nstruct ContentView_Previews: PreviewProvider {\n    static var previews: some View {\n        ContentView()\n    }\n}<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">3. Test the App<\/h3>\n\n\n\n<p>Make sure to test the app on a real device (since the camera doesn&#8217;t work on the simulator). The app should now capture and output photos that are high-contrast black-and-white images.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">4. Summary of Changes:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>High-Contrast Monochrome Processing<\/strong>: We&#8217;ve added image processing to apply a monochrome filter with high contrast using <code>Core Image<\/code>.<\/li>\n\n\n\n<li><strong>Filter Chain<\/strong>: <code>CIColorControls<\/code> is used to control the saturation and contrast to achieve the desired effect.<\/li>\n<\/ul>\n\n\n\n<p>By following these steps, your minimalist camera app will now take high-contrast black-and-white photos and save them to the photo library.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Creating a minimalist iPhone camera app using Swift and SwiftUI involves several key components: Below is a step-by-step guide with the complete code to create a minimalist camera app ready for the App Store. \u2026<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"_ek_photo_artist_note":"","_ek_photo_thesis":"","_ek_photo_camera":"","_ek_photo_place":"","_ek_photo_human_moment":"","_ek_photo_sequence":"","_ek_photo_question":"","_ek_photo_canonical_claim":"","_ek_photo_ai_summary":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[17],"tags":[],"class_list":["post-635984","post","type-post","status-publish","format-standard","hentry","category-posts"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/posts\/635984","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/comments?post=635984"}],"version-history":[{"count":2,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/posts\/635984\/revisions"}],"predecessor-version":[{"id":635986,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/posts\/635984\/revisions\/635986"}],"wp:attachment":[{"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/media?parent=635984"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/categories?post=635984"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/erickimphotography.com\/blog\/wp-json\/wp\/v2\/tags?post=635984"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}