SwiftUI Camera Preview – Mirror App

import SwiftUI
import UIKit
import AVFoundation

class CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {
    
    enum CameraDirection {
        case front
        case back
    }
    
    @Published var session:AVCaptureSession!
    @Published var cameraDirection = AVCaptureDevice.Position.front
    @Published var input: AVCaptureDeviceInput?
    @Published var output = AVCapturePhotoOutput()
    
    @Published var preview: AVCaptureVideoPreviewLayer!

    override init() {
        let lsession = AVCaptureSession()
        preview = AVCaptureVideoPreviewLayer(session: lsession)
        session = lsession
    }
    
    func Check() {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            print("authorized")
            setup()
        case .notDetermined:
            print("not determined")
            AVCaptureDevice.requestAccess(for: .video) { status in
                if status {
                    self.setup()
                }
            }
        default:
            print("unknown")
        }
    }
    
    let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes:
        [.builtInTrueDepthCamera, .builtInDualCamera, .builtInWideAngleCamera],
        mediaType: .video, position: .unspecified)
    
    func bestDevice(in position: AVCaptureDevice.Position) -> AVCaptureDevice {
        let devices = self.discoverySession.devices
        guard !devices.isEmpty else { fatalError("Missing capture devices.")}

        return devices.first(where: { device in device.position == position })!
    }
    
    
    private func getDevice(direction: AVCaptureDevice.Position) -> AVCaptureDevice? {
        if let device = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: direction) {
            return device
        }
        
        // Find the built-in Wide-Angle Camera, if it exists.
        if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: direction) {
            return device
        }
        
        fatalError("Missing expected back camera device.")
        //throw Error("No camera device")
    }
    
    func setup() {
        do {
            self.session.beginConfiguration()
            
            let device =  bestDevice(in: cameraDirection)
            input  = try AVCaptureDeviceInput(device: device)
            
            if input != nil && self.session.canAddInput(input!) {
                self.session.addInput(input!)
            }
            
            if self.session.canAddOutput(output) {
                self.session.addOutput(output)
            }
            
            self.session.commitConfiguration()
        }
        catch {
            print(error.localizedDescription)
        }
        
    }
}


struct CameraView: UIViewRepresentable {
    @ObservedObject var camera: CameraModel
    
    func makeUIView(context: Context) -> some UIView {
        let view = UIView(frame: UIScreen.main.bounds)
        
        camera.preview.frame = view.frame
        camera.preview.videoGravity = .resizeAspectFill
        view.layer.addSublayer(camera.preview)
        
        DispatchQueue.global(qos: .background).async {
            camera.session.startRunning()
        }
        return view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    
}

struct MirrorView: View {
    @StateObject var camera = CameraModel()
    var body: some View {
        ZStack {
            CameraView(camera: camera)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .edgesIgnoringSafeArea(.all)
        }
        .onAppear {
            camera.Check()
        }
    }
}



struct MirrorView_Previews: PreviewProvider {
    static var previews: some View {
        MirrorView()
    }
}

Unity Procedural Sphere

Drag this on the GameObject. You will need the Custom/VertexColorShader which can also be found on here.

using UnityEngine;

public class ProceduralSphere : MonoBehaviour
{
    public float radius = 1.0f;
    public int subdivisions = 16;
    public Color color = Color.white;

    private void Start()
    {
        // Create a new mesh
        Mesh mesh = new Mesh();

        // Define the vertices of the sphere
        Vector3[] vertices = new Vector3[(subdivisions + 1) * (subdivisions + 1)];
        for (int i = 0; i <= subdivisions; i++)
        {
            float v = (float)i / (float)subdivisions * Mathf.PI;
            float sinV = Mathf.Sin(v);
            float cosV = Mathf.Cos(v);
            for (int j = 0; j <= subdivisions; j++)
            {
                float u = (float)j / (float)subdivisions * Mathf.PI * 2.0f;
                float sinU = Mathf.Sin(u);
                float cosU = Mathf.Cos(u);
                int index = i * (subdivisions + 1) + j;
                vertices[index] = new Vector3(sinU * sinV, cosV, cosU * sinV) * radius;
            }
        }

        // Define the triangles that make up the sphere
        int[] triangles = new int[subdivisions * subdivisions * 6];
        int triangleIndex = 0;
        for (int i = 0; i < subdivisions; i++)
        {
            for (int j = 0; j < subdivisions; j++)
            {
                int index = i * (subdivisions + 1) + j;
                triangles[triangleIndex++] = index;
                triangles[triangleIndex++] = index + 1;
                triangles[triangleIndex++] = index + subdivisions + 1;

                triangles[triangleIndex++] = index + 1;
                triangles[triangleIndex++] = index + subdivisions + 2;
                triangles[triangleIndex++] = index + subdivisions + 1;
            }
        }

        // Define the colors of each vertex
        Color[] colors = new Color[vertices.Length];
        for (int i = 0; i < colors.Length; i++)
        {
            colors[i] = color;
        }

        // Assign the vertices, triangles, and colors to the mesh
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.colors = colors;

        // Create a new mesh renderer and assign the mesh to it
        MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer>();
        meshRenderer.sharedMaterial = new Material(Shader.Find("Custom/VertexColorShader"));

        // Create a new mesh filter and assign the mesh to it
        MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();
        meshFilter.mesh = mesh;
    }
}

Unity Procedural Cube

Drag this on the GameObject. You will need the Custom/VertexColorShader which can also be found on here.

using UnityEngine;

public class ProceduralCube : MonoBehaviour
{
    public float size = 1.0f;
    public Color color = Color.white;

    private void Start()
    {
        // Create a new mesh
        Mesh mesh = new Mesh();

        // Define the vertices of the cube
        Vector3[] vertices = new Vector3[]
        {
            new Vector3(-size, -size, -size),
            new Vector3(-size, -size, size),
            new Vector3(-size, size, -size),
            new Vector3(-size, size, size),
            new Vector3(size, -size, -size),
            new Vector3(size, -size, size),
            new Vector3(size, size, -size),
            new Vector3(size, size, size),
        };

        // Define the triangles that make up the cube
        int[] triangles = new int[]
        {
            0, 1, 2, // front
            2, 1, 3,
            4, 0, 6, // back
            6, 0, 2,
            5, 4, 7, // right
            7, 4, 6,
            1, 5, 3, // left
            3, 5, 7,
            2, 3, 6, // top
            6, 3, 7,
            4, 5, 0, // bottom
            0, 5, 1,
        };

        // Define the colors of each vertex
        Color[] colors = new Color[]
        {
            color, color, color, color, color, color, color, color,
        };

        // Assign the vertices, triangles, and colors to the mesh
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.colors = colors;

        // Create a new mesh renderer and assign the mesh to it
        MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer>();
        meshRenderer.sharedMaterial = new Material(Shader.Find("Custom/VertexColorShader"));

        // Create a new mesh filter and assign the mesh to it
        MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();
        meshFilter.mesh = mesh;
    }
}

Unity Vertex Color Shader

Shader "Custom/VertexColorShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader {
        Tags {"Queue"="Transparent" "RenderType"="Opaque"}
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float4 color : COLOR;
            };

            struct v2f {
                float4 vertex : SV_POSITION;
                float4 color : COLOR;
            };

            float4 _Color;

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.color = v.color * _Color;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                return i.color;
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

iOS List Pending Notifications

You may want to show a list of the upcoming application notifications, you can do this by using the following code.

import UserNotifications

UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
    for request in requests {
        print(request)
    }
}

iOS Notifications While App is Open

You first need a class that acts as the delegate for the userNotificationCenter.

class AppNotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // Customize the appearance of the notification, for example:
        completionHandler([.banner, .sound])
    }
}

Then you need to create an instance of this on your @main, and then tell the notification center what the delegate is.

let notificationDelegate = POSTAppNotificationDelegate()
    
init() {
    let notificationCenter = UNUserNotificationCenter.current()
    notificationCenter.delegate = notificationDelegate
}

iOS Notifications

Below is code for creating a notification locally on iOS, and triggering it 5 seconds later.

import SwiftUI
import UserNotifications

struct ContentView: View {
    var body: some View {
        Button("Trigger Notification") {
            let content = UNMutableNotificationContent()
            content.title = "Hello"
            content.subtitle = "World"
            content.sound = UNNotificationSound.default
            
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
            
            let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
            
            UNUserNotificationCenter.current().add(request) { error in
                if let error = error {
                    print("Error: \(error.localizedDescription)")
                } else {
                    print("Notification scheduled!")
                }
            }
        }
    }
}

Don’t forget to request permissions to send notifications.

        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
    if let error = error {
         print("Error: \(error.localizedDescription)")
    } else {
         scheduleNotification()
    }
}

StoreKit2 With XCode

When testing StoreKit connected directly to XCode, you need to have your .storekit file open and then use the debug menu to test purchases.

This is the first time I realized that the menus changed what they have in them dependent on what file you have open. This also means when you go under Editor menu that also changes too.

This caused me to go in circles on why clearing Tester purchases in App Store Connect wasn’t working.

Bowling Score Tracker

My girlfriend is part of a bowling league and every week she writes her scores down on a piece of paper. After seeing her do this for many seasons of bowling I’ve decided I would build her a simple mobile application that she can enter these scores into.

If you have a iPhone with iOS 15.6 or higher you can direct message me on twitter (with your email address, and first and last name) and I’ll invite you to the TestFlight. I’m planning on running this TestFlight beta until mid November 2022.