diff --git a/Package.swift b/Package.swift index 0b5f962..04d6b88 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -32,7 +32,7 @@ let package = Package( .library(name: "OversizeHealthComponents", targets: ["OversizeHealthComponents"]), .library(name: "OversizeWeatherComponents", targets: ["OversizeWeatherComponents"]), ], - dependencies: productionDependencies, + dependencies: developmentDependencies, targets: [ .target( name: "OversizeComponents", diff --git a/Sources/OversizeComponents/CurrencyPicker/CurrencyPicker.swift b/Sources/OversizeComponents/CurrencyPicker/CurrencyPicker.swift index c763c04..7567643 100644 --- a/Sources/OversizeComponents/CurrencyPicker/CurrencyPicker.swift +++ b/Sources/OversizeComponents/CurrencyPicker/CurrencyPicker.swift @@ -7,7 +7,7 @@ import OversizeCore import OversizeUI import SwiftUI -@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +@available(macOS 14, iOS 16, tvOS 16, watchOS 9, *) public struct CurrencyPicker: View { @Environment(\.theme) private var theme: ThemeSettings @Binding private var selection: Locale.Currency @@ -41,7 +41,7 @@ public struct CurrencyPicker: View { } } -@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +@available(macOS 14, iOS 16, tvOS 16, watchOS 9, *) struct CurrencyPicker_Previews: PreviewProvider { static var previews: some View { CurrencyPicker(selection: .constant("USD")) diff --git a/Sources/OversizeComponents/FloatingTabBar/TabItemPreferenceKey.swift b/Sources/OversizeComponents/FloatingTabBar/TabItemPreferenceKey.swift index 20fb1be..093b23e 100644 --- a/Sources/OversizeComponents/FloatingTabBar/TabItemPreferenceKey.swift +++ b/Sources/OversizeComponents/FloatingTabBar/TabItemPreferenceKey.swift @@ -6,7 +6,9 @@ import SwiftUI struct TabItemPreferenceKey: PreferenceKey { - static var defaultValue: [TabItem] = [] + static var defaultValue: [TabItem] { + [] + } static func reduce(value: inout [TabItem], nextValue: () -> [TabItem]) { value += nextValue() diff --git a/Sources/OversizeComponents/LocationPicker/LocationPicker.swift b/Sources/OversizeComponents/LocationPicker/LocationPicker.swift index a510d01..32ed369 100644 --- a/Sources/OversizeComponents/LocationPicker/LocationPicker.swift +++ b/Sources/OversizeComponents/LocationPicker/LocationPicker.swift @@ -130,7 +130,7 @@ import SwiftUI } } - extension CLLocationCoordinate2D: Equatable {} + extension CLLocationCoordinate2D: @retroactive Equatable {} public func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool { lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude diff --git a/Sources/OversizeComponents/MailView/MailView.swift b/Sources/OversizeComponents/MailView/MailView.swift index 1f7b0e0..aaca25c 100644 --- a/Sources/OversizeComponents/MailView/MailView.swift +++ b/Sources/OversizeComponents/MailView/MailView.swift @@ -39,13 +39,14 @@ Coordinator(self) } - public class Coordinator: NSObject, MFMailComposeViewControllerDelegate { + public class Coordinator: NSObject, @preconcurrency MFMailComposeViewControllerDelegate { var parent: MailView public init(_ parent: MailView) { self.parent = parent } + @MainActor public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith _: MFMailComposeResult, error _: Error?) { controller.dismiss(animated: true) } diff --git a/Sources/OversizeComponents/PhoneSheet/PhoneSheet.swift b/Sources/OversizeComponents/PhoneSheet/PhoneSheet.swift index aa25365..643960b 100644 --- a/Sources/OversizeComponents/PhoneSheet/PhoneSheet.swift +++ b/Sources/OversizeComponents/PhoneSheet/PhoneSheet.swift @@ -6,6 +6,7 @@ import OversizeUI import SwiftUI +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct PhoneSheetNumber: Hashable { public let name: String? public let phone: String @@ -16,6 +17,7 @@ public struct PhoneSheetNumber: Hashable { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct PhoneCallSheet: View { private let numbers: [PhoneSheetNumber] private let title: String @@ -49,6 +51,7 @@ public struct PhoneCallSheet: View { } } +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) struct PhoneCallSheet_Previews: PreviewProvider { static var previews: some View { PhoneCallSheet("Phones", numbers: [ diff --git a/Sources/OversizeComponents/SorticngPicker/SorticngPicker.swift b/Sources/OversizeComponents/SorticngPicker/SorticngPicker.swift index 3ade813..afe3f74 100644 --- a/Sources/OversizeComponents/SorticngPicker/SorticngPicker.swift +++ b/Sources/OversizeComponents/SorticngPicker/SorticngPicker.swift @@ -6,6 +6,7 @@ import OversizeUI import SwiftUI +@available(iOS 15.0, macOS 14, tvOS 15.0, watchOS 9.0, *) public struct SortingPicker: View where Content: View, diff --git a/Sources/OversizeComponents/WebView/WebView.swift b/Sources/OversizeComponents/WebView/WebView.swift index 358c480..d327f9c 100644 --- a/Sources/OversizeComponents/WebView/WebView.swift +++ b/Sources/OversizeComponents/WebView/WebView.swift @@ -30,6 +30,8 @@ public struct WebView: View { } else { webView } + #elseif os(macOS) + webView #else EmptyView() #endif @@ -44,7 +46,7 @@ public struct WebView: View { openURL(url) })) }) - #if os(iOS) + #if os(iOS) || os(macOS) WebViewRepresentable(url: url) #endif } @@ -69,6 +71,24 @@ public struct WebView: View { } #endif +#if os(macOS) + public struct WebViewRepresentable: NSViewRepresentable { + private let request: URLRequest + + public init(url: URL) { + request = URLRequest(url: url) + } + + public func makeNSView(context _: Context) -> WKWebView { + WKWebView() + } + + public func updateNSView(_ uiView: WKWebView, context _: Context) { + uiView.load(request) + } + } +#endif + struct WebView_Previews: PreviewProvider { static var previews: some View { WebView(url: URL(string: "https://www.apple.com")!) diff --git a/Sources/OversizePhotoComponents/Camera/CameraController.swift b/Sources/OversizePhotoComponents/Camera/CameraController.swift index aa0ff04..8995c97 100644 --- a/Sources/OversizePhotoComponents/Camera/CameraController.swift +++ b/Sources/OversizePhotoComponents/Camera/CameraController.swift @@ -5,78 +5,82 @@ import AVFoundation #if canImport(UIKit) - import UIKit +import UIKit #endif #if os(iOS) - class CameraController: NSObject { - var captureSession: AVCaptureSession? - var frontCamera: AVCaptureDevice? - var frontCameraInput: AVCaptureDeviceInput? - var previewLayer: AVCaptureVideoPreviewLayer? +@MainActor +class CameraController: NSObject { + private var captureSession: AVCaptureSession? + private var frontCamera: AVCaptureDevice? + private var frontCameraInput: AVCaptureDeviceInput? + private var previewLayer: AVCaptureVideoPreviewLayer? - func prepare(completionHandler: @escaping (Error?) -> Void) { - func createCaptureSession() { - captureSession = AVCaptureSession() + func prepare(completionHandler: @escaping (Error?) -> Void) { + func createCaptureSession() { + captureSession = AVCaptureSession() + } + + func configureCaptureDevices() throws { + guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else { + throw CameraControllerError.noCamerasAvailable } - func configureCaptureDevices() throws { - let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .front) - - frontCamera = camera - - try camera?.lockForConfiguration() - camera?.unlockForConfiguration() + frontCamera = camera + try camera.lockForConfiguration() + camera.unlockForConfiguration() + } + + func configureDeviceInputs() throws { + guard let captureSession = captureSession else { + throw CameraControllerError.captureSessionIsMissing } - func configureDeviceInputs() throws { - guard let captureSession else { throw CameraControllerError.captureSessionIsMissing } - - if let frontCamera { - frontCameraInput = try AVCaptureDeviceInput(device: frontCamera) - - if captureSession.canAddInput(frontCameraInput!) { captureSession.addInput(frontCameraInput!) } - else { throw CameraControllerError.inputsAreInvalid } - } else { throw CameraControllerError.noCamerasAvailable } - - captureSession.startRunning() + guard let frontCamera = frontCamera else { + throw CameraControllerError.noCamerasAvailable } - - DispatchQueue(label: "prepare").async { - do { - createCaptureSession() - try configureCaptureDevices() - try configureDeviceInputs() - } catch { - DispatchQueue.main.async { - completionHandler(error) - } - - return - } - - DispatchQueue.main.async { - completionHandler(nil) - } + + frontCameraInput = try AVCaptureDeviceInput(device: frontCamera) + if captureSession.canAddInput(frontCameraInput!) { + captureSession.addInput(frontCameraInput!) + } else { + throw CameraControllerError.inputsAreInvalid } + + captureSession.startRunning() } - - func displayPreview(on view: UIView) throws { - guard let captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing } - - previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) - previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill - previewLayer?.connection?.videoOrientation = .portrait - - view.layer.insertSublayer(previewLayer!, at: 0) - previewLayer?.frame = view.frame + + Task { + do { + createCaptureSession() + try configureCaptureDevices() + try configureDeviceInputs() + completionHandler(nil) + } catch { + completionHandler(error) + } } } - - enum CameraControllerError: Swift.Error { - case captureSessionAlreadyRunning - case captureSessionIsMissing - case inputsAreInvalid - case invalidOperation - case noCamerasAvailable - case unknown + + @MainActor + func displayPreview(on view: UIView) throws { + guard let captureSession = captureSession, captureSession.isRunning else { + throw CameraControllerError.captureSessionIsMissing + } + + previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + previewLayer?.videoGravity = .resizeAspectFill + previewLayer?.connection?.videoOrientation = .portrait + + view.layer.insertSublayer(previewLayer!, at: 0) + previewLayer?.frame = view.bounds } +} + +enum CameraControllerError: Swift.Error { + case captureSessionAlreadyRunning + case captureSessionIsMissing + case inputsAreInvalid + case invalidOperation + case noCamerasAvailable + case unknown +} #endif diff --git a/Sources/OversizePhotoComponents/GellaryPicker/CameraPreviewView.swift b/Sources/OversizePhotoComponents/GellaryPicker/CameraPreviewView.swift index f4a03cb..72c4639 100644 --- a/Sources/OversizePhotoComponents/GellaryPicker/CameraPreviewView.swift +++ b/Sources/OversizePhotoComponents/GellaryPicker/CameraPreviewView.swift @@ -6,88 +6,98 @@ import AVFoundation import SwiftUI #if canImport(UIKit) - import UIKit +import UIKit #endif #if os(iOS) - class CameraPreviewUIView: UIView { - private var captureSession: AVCaptureSession? - - init() { - super.init(frame: .zero) - - var allowedAccess = false - let blocker: DispatchGroup = .init() - blocker.enter() - AVCaptureDevice.requestAccess(for: .video) { flag in - allowedAccess = flag - blocker.leave() - } - blocker.wait() - - if !allowedAccess { - return - } - let session: AVCaptureSession = .init() - session.beginConfiguration() +public class CameraPreviewUIView: UIView { + private var captureSession: AVCaptureSession? - let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, - for: .video, position: .unspecified) // alternate AVCaptureDevice.default(for: .video) - guard videoDevice != nil, let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!), session.canAddInput(videoDeviceInput) else { + public init() { + super.init(frame: .zero) + Task { + guard await requestCameraAccess() else { return } - session.addInput(videoDeviceInput) - session.commitConfiguration() - captureSession = session + await configureCaptureSession() } + } - override class var layerClass: AnyClass { - AVCaptureVideoPreviewLayer.self - } + public override class var layerClass: AnyClass { + AVCaptureVideoPreviewLayer.self + } - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var videoPreviewLayer: AVCaptureVideoPreviewLayer { + layer as! AVCaptureVideoPreviewLayer + } - var videoPreviewLayer: AVCaptureVideoPreviewLayer { - layer as! AVCaptureVideoPreviewLayer + public override func didMoveToSuperview() { + super.didMoveToSuperview() + + if superview != nil { + videoPreviewLayer.session = captureSession + videoPreviewLayer.videoGravity = .resizeAspectFill + Task { + await startSession() + } + } else { + Task { + await stopSession() + } } + } - override func didMoveToSuperview() { - super.didMoveToSuperview() - - if superview != nil { - videoPreviewLayer.session = captureSession - videoPreviewLayer.videoGravity = .resizeAspectFill - DispatchQueue.global().async { - self.captureSession?.startRunning() - } - } else { - DispatchQueue.global().async { - self.captureSession?.stopRunning() - } + private func requestCameraAccess() async -> Bool { + await withCheckedContinuation { continuation in + AVCaptureDevice.requestAccess(for: .video) { allowedAccess in + continuation.resume(returning: allowedAccess) } } } - struct CmeraPreviewVideo: UIViewRepresentable { - public init() {} + private func configureCaptureSession() async { + let session = AVCaptureSession() + session.beginConfiguration() - func makeUIView(context _: UIViewRepresentableContext) -> CameraPreviewUIView { - CameraPreviewUIView() + guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .unspecified), + let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice), + session.canAddInput(videoDeviceInput) else { + return } - func updateUIView(_: CameraPreviewUIView, context _: UIViewRepresentableContext) {} + session.addInput(videoDeviceInput) + session.commitConfiguration() + captureSession = session + } - typealias UIViewType = CameraPreviewUIView + private func startSession() async { + await MainActor.run { + captureSession?.startRunning() + } } - struct DemoVideoStreaming: View { - var body: some View { - VStack { - CmeraPreviewVideo() - }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) + private func stopSession() async { + await MainActor.run { + captureSession?.stopRunning() } } +} + +public struct CameraPreviewVideo: UIViewRepresentable { + public init() {} + + public func makeUIView(context: Context) -> CameraPreviewUIView { + CameraPreviewUIView() + } + + public func updateUIView(_ uiView: CameraPreviewUIView, context: Context) {} + + public typealias UIViewType = CameraPreviewUIView +} + #endif diff --git a/Sources/OversizePhotoComponents/GellaryPicker/GellaryPhotoPickerView.swift b/Sources/OversizePhotoComponents/GellaryPicker/GellaryPhotoPickerView.swift index cddd161..c908151 100644 --- a/Sources/OversizePhotoComponents/GellaryPicker/GellaryPhotoPickerView.swift +++ b/Sources/OversizePhotoComponents/GellaryPicker/GellaryPhotoPickerView.swift @@ -65,7 +65,7 @@ import SwiftUI isShowCamera.toggle() } label: { ZStack { - CmeraPreviewVideo() + CameraPreviewVideo() Circle() .fill(.black.opacity(0.2)) .frame(width: 48, height: 48) diff --git a/Sources/OversizePhotoComponents/GellaryPicker/GellaryPickerView.swift b/Sources/OversizePhotoComponents/GellaryPicker/GellaryPickerView.swift index bdfa362..a796344 100644 --- a/Sources/OversizePhotoComponents/GellaryPicker/GellaryPickerView.swift +++ b/Sources/OversizePhotoComponents/GellaryPicker/GellaryPickerView.swift @@ -85,7 +85,7 @@ import SwiftUI isShowCamera.toggle() } label: { ZStack { - CmeraPreviewVideo() + CameraPreviewVideo() Circle() .fill(.black.opacity(0.2)) .frame(width: 48, height: 48)