Post

Replies

Boosts

Views

Activity

How to use protocols to support managing SwiftUI views from different modules ?
In out project, we are creating a modular architecture where each module conform to certain protocol requirements for displaying the UI. For example: public protocol ModuleProviding: Sendable { associatedtype Content: View /// Creates and returns the main entry point view for this module /// - Parameter completion: Optional closure to be called when the flow completes /// - Returns: The main view for this module @MainActor func createView(_ completion: (() -> Void)?) -> Content } This protocol can be implemented by all different modules in the app, and I use a ViewProvider structure to build the UI from the module provided: public struct ViewProvider: Equatable { let id = UUID() let provider: any ModuleProviding let completion: () -> Void public init(provider: any ModuleProviding, completion: @escaping () -> Void) { self.provider = provider self.completion = completion } @MainActor public func layoutUI() -> some View { provider.createView(completion) } This code throws an error: Type 'any View' cannot conform to 'View' To solve this error, there are two ways, one is to wrap it in AnyView, which I don't want to do. The other option is to type check the provider with its concrete type: @ViewBuilder @MainActor private func buildViewForProvider(_ provider: any ModuleProviding, completion: (() -> Void)?) -> some View { switch provider { case let p as LoginProvider: p.createView(completion) case let p as PostAuthViewProvider: p.createView(completion) case let p as OnboardingProvider: p.createView(completion) case let p as RewardsProvider: p.createView(completion) case let p as SplashScreenProvider: p.createView(completion) default: EmptyView() } } This approach worked, but it defeats the purpose of using protocols and it is not scalable anymore. Are there any other approaches I can look at ? Or is this limitation in SwiftUI ?
3
0
151
Jul ’25
SwiftUI Preview Runtime linking failure
Swift Package: I have an old objc library, that is added as XCFramework to swift package as a binary target. targets: [ .target( name: "CoreServices", dependencies: ["OldContainer"], path: "CoreServices" ), .binaryTarget( name: "OldContainer", path: "Frameworks/OldContainer.xcframework" ), ] This serves the purpose, it builds fine and able to run on simulator. However this framework is breaking the Xcode previews. Error: == PREVIEW UPDATE ERROR: [Remote] JITError: Runtime linking failure Additional Link Time Errors: Symbols not found: [ _OBJC_CLASS_$_SCSecureServicesFactory ] ================================== | [Remote] LLVMError | | LLVMError: LLVMError(description: "Failed to materialize symbols: { (static-Login, { __replacement_tag$1015 }) }") == VERSION INFO: Tools: 16C5032a OS: 23G93 PID: 38675 Model: MacBook Pro Arch: arm64e == ENVIRONMENT: openFiles = [ /Users/../Documents/GitHub/Packages/Login/Sources/Login/LoginView.swift ] wantsNewBuildSystem = true newBuildSystemAvailable = true activeScheme = Launch activeRunDestination = iPhone 16 Pro Max variant iphonesimulator arm64 workspaceArena = [x] buildArena = [x] buildableEntries = [ Login Login ] runMode = JIT Executor == SELECTED RUN DESTINATION: Simulator - iOS 18.2 | iphonesimulator | arm64 | iPhone 16 Pro Max | no proxy == EXECUTION MODE OVERRIDES: Workspace JIT mode user setting: true Falling back to Dynamic Replacement: false Based on the error, SCSecureServicesFactory is an objc file inside the XCFramework. Since this is a binary target, I could not add a swift setting module map flag to the XCFramework. Is there any workaround to get the previews working ? Or Am I blocked until the library is converted into swift ?
4
0
623
Mar ’25
Region monitoring not working after 1 hour the app is killed.
This is my setup: Granted always allow permission. I have location added in UIBackgroundModes, but I did NOT set allowsBackgroundLocationUpdates to true Note: I have this allowsBackgroundLocationUpdates = true set in my earlier version of app, which worked but we noticed it drained battery much faster, hence we removed all the settings that could affect battery. The location model is setup with 20 regions, when boundary crossing happen, app sends a local notification. This works fine when app is in foreground/background. If app is killed, the app receives notification for boundary crossing only once. Failed case for region monitoring: Setup region monitoring Kill the app cross the boundary, app sends a local notification. wait for 1 hour leave the device in same state (notification is not opened, app is still killed state) cross the boundary again expect a notification, but app did not register any event related to region monitoring. The console logs did not print anything in this second case. public class LocationViewModel: NSObject, ObservableObject { private let maxMonitoredRegions = 20 private var anyCancellable: AnyCancellable? private let locationManager: CLLocationManager @Published public var authorizationStatus: CLAuthorizationStatus @Published public var isMonitoringAvailable: Bool @Published public var monitoredRegions: [Region] @Published public var recentLocation: CLLocation? public var newlyEnteredRegionSignal = PassthroughSubject<CLRegion, Never>() public var recentLocationSignal = PassthroughSubject<CLLocation, Never>() public var authorizationStatusPublisher: Published<CLAuthorizationStatus>.Publisher { $authorizationStatus } public var isLocationEnabled: Bool { locationManager.authorizationStatus == .authorizedWhenInUse || locationManager.authorizationStatus == .authorizedAlways } public override init() { locationManager = CLLocationManager() authorizationStatus = locationManager.authorizationStatus isMonitoringAvailable = CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) monitoredRegions = [] super.init() locationManager.delegate = self monitoredRegions.append(contentsOf: getMonitoredRegions()) requestLocation() } public func requestLocation() { locationManager.requestLocation() } public func startRegionMonitoring(regions: [CLRegion]) { guard isMonitoringAvailable else { return } stopRegionMonitoring() if regions.isEmpty { return } if regions.count <= 20 { for region in regions { locationManager.startMonitoring(for: region) } } else { for region in regions[0...maxMonitoredRegions-1] { locationManager.startMonitoring(for: region) } } } public func stopRegionMonitoring() { guard isMonitoringAvailable else { return } if monitoredRegions.isEmpty { return } for region in monitoredRegions { let monitoredRegion = LocationUtils.convertRegionToCLRegion(region) locationManager.stopMonitoring(for: monitoredRegion) } monitoredRegions.removeAll() } private func getMonitoredRegions() -> [Region] { let monitoredRegions = locationManager.monitoredRegions var regions = [Region]() for monitoredRegion in monitoredRegions { if let region = LocationUtils.convertCLRegionToRegion(monitoredRegion) { regions.append(region) } } return regions } public func stopMonitoring() { recentLocation = nil stopRegionMonitoring() } } extension LocationViewModel: CLLocationManagerDelegate { public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { authorizationStatus = manager.authorizationStatus switch authorizationStatus { case .notDetermined: stopMonitoring() case .denied: stopMonitoring() case .authorizedAlways: break case .authorizedWhenInUse: // If user has requested whenInUse, request for always allow. locationManager.requestAlwaysAuthorization() @unknown default: break } if let location = manager.location { recentLocationSignal.send(location) recentLocation = location } } public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { if let recentLocation = locations.last { self.recentLocation = recentLocation recentLocationSignal.send(recentLocation) } } public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { } public func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) { if let monitoredRegion = LocationUtils.convertCLRegionToRegion(region) { let oldRegion = monitoredRegions.first { $0.identifier == monitoredRegion.identifier } if oldRegion == nil { monitoredRegions.append(monitoredRegion) } } } public func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) { } public func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { newlyEnteredRegionSignal.send(region) } public func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { newlyEnteredRegionSignal.send(region) } } When app is awaken due to location event on app delegate, we initialize this location model, and location manager, and remove old monitored regions, and call startMonitoringRegions again, to keep the regions updated. Please let me know if I'm missing any crucial information.
1
1
737
Jan ’24
Unable to use swift package module in safari extension target in Xcode.
I have created a swift package CommonPackage with files that are common to two targets. In my project I have two targets. MainApp Safari Extension target for MainApp Now, I added this package in the Frameworks, Libraries section for both targets. I am able to use this package in the main app target, but when I import this package in my Safari Extension target in SFSafariExtensionHandler class , it is giving me the No such module CommonPackage  error. The package file is pretty generic: let package = Package( name: "CommonPackage", platforms: [ .iOS(.v14) ], products: [ .library( name: "CommonPackage", targets: ["CommonPackage"]), ], dependencies: [ // Depends on another swift package, stored in separate repo. ], targets: [ .target( name: "CommonPackage", dependencies: []), .testTarget( name: "CommonPackage Tests", dependencies: ["CommonPackage"]), ] ) Not sure why I am not able to use this package in the safari extension target. I spent many hours trying different solutions to resolve No such module error, but none worked. In one of WWDC video they show, this kind of modularization is supported, but in my case it is not. I started to think, is it because of the way my project is supported? Note: The safari extension target is a child target to main app target, which means, the safari extension is added as a dependency in the main app target settings. Really appreciate if someone can point me in right direction. I can provide more details in the question, but not sure what relevant details I need to include.
1
0
703
Mar ’23