Skip to content

Commit

Permalink
iOS 17 Update (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonjrr authored Nov 29, 2023
1 parent f677c21 commit 34e0254
Show file tree
Hide file tree
Showing 18 changed files with 72 additions and 38 deletions.
8 changes: 4 additions & 4 deletions MVVM.Demo.SwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -797,7 +797,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
Expand All @@ -823,7 +823,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -853,7 +853,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
2 changes: 1 addition & 1 deletion MVVM.Demo.SwiftUI/Architecture/ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

typealias ViewModelDefinition = (ObservableObject & Identifiable & Hashable & HapticFeedbackProvider)
typealias ViewModelDefinition = (AnyObject & Identifiable & Hashable & HapticFeedbackProvider)

protocol ViewModel: ViewModelDefinition {}

Expand Down
7 changes: 4 additions & 3 deletions MVVM.Demo.SwiftUI/Core/SwiftUI/ObjectNavigationStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@
import SwiftUI

struct ObjectNavigationStack<Content>: View where Content : View {
@ObservedObject var path: ObjectNavigationPath
@State var path: ObjectNavigationPath
let content: () -> Content

var body: some View {
NavigationStack(path: self.$path.path, root: self.content)
}
}

class ObjectNavigationPath: ObservableObject {
@Observable
class ObjectNavigationPath {
typealias NavigationObject = AnyObject & Hashable & Equatable
@Published fileprivate var path: NavigationPath = NavigationPath()
fileprivate var path: NavigationPath = NavigationPath()
private var objects: [any NavigationObject] = []

private let semaphore: DispatchSemaphore = DispatchSemaphore(value: 1)
Expand Down
15 changes: 12 additions & 3 deletions MVVM.Demo.SwiftUI/Services/AlertService/AlertService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import Foundation
import Combine

// MARK: AlertManager
public class AlertManager: ObservableObject {
@Published var alert: AlertService.AlertPackage?
@Observable
public class AlertManager: Equatable {
var alert: AlertService.AlertPackage?

static public func ==(lhs: AlertManager, rhs: AlertManager) -> Bool {
lhs === rhs
}
}

// MARK: AlertServiceProtocol
Expand Down Expand Up @@ -52,7 +57,7 @@ class AlertService: AlertServiceProtocol {
}

extension AlertService {
struct AlertPackage: Identifiable {
struct AlertPackage: Identifiable, Equatable {
let id: UUID = UUID()
let title: String
let message: String?
Expand All @@ -72,6 +77,10 @@ extension AlertService {
self.primaryButton = primaryButton
self.secondaryButton = secondaryButton
}

static func ==(lhs: AlertPackage, rhs: AlertPackage) -> Bool {
lhs.id == rhs.id
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import Foundation
import Combine
import Swinject

@Observable
class AppRootCoordinator: ViewModel {
private let resolver: Resolver

@Published private(set) var landingViewModel: LandingViewModel!
private(set) var landingViewModel: LandingViewModel!

let path = ObjectNavigationPath()

@Published var signInViewModel: SignInViewModel?
@Published var colorWizardCoordinator: ColorWizardCoordinator?
var signInViewModel: SignInViewModel?
var colorWizardCoordinator: ColorWizardCoordinator?

init(resolver: Resolver) {
self.resolver = resolver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import SwiftUI
struct AppRootCoordinatorView: View {
@Environment(\.alertManager) var alertManager: AlertManager

@ObservedObject var coordinator: AppRootCoordinator
var coordinator: AppRootCoordinator

@State private var colorWizardCoordinator: ColorWizardCoordinator?
@State private var signInViewModel: SignInViewModel?
@State private var alert: AlertService.AlertPackage?

Expand All @@ -27,7 +28,7 @@ struct AppRootCoordinatorView: View {
.navigationDestination(for: PulseViewModel.self) {
PulseView(viewModel: $0)
}
.fullScreenCover(item: self.$coordinator.colorWizardCoordinator) { coordinator in
.fullScreenCover(item: self.$colorWizardCoordinator) { coordinator in
ColorWizardCoordinatorView(coordinator: coordinator)
}

Expand All @@ -38,9 +39,12 @@ struct AppRootCoordinatorView: View {
}
}
.navigationAlert(item: self.$alert)
.onReceive(self.alertManager.$alert) { self.alert = $0 }
.onReceive(self.coordinator.$signInViewModel, withAnimation: .easeInOut(duration: 0.375)) {
self.signInViewModel = $0
.onChange(of: self.coordinator.colorWizardCoordinator, initial: true) { _, value in
self.colorWizardCoordinator = value
}
.onChange(of: self.alertManager.alert, initial: true) { _, value in self.alert = value }
.onChange(of: self.coordinator.signInViewModel, initial: true) { _, value in
self.signInViewModel = value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ protocol ColorWizardCoordinatorDelegate: AnyObject {
func colorWizardCoordinatorDidComplete(_ source: ColorWizardCoordinator)
}

@Observable
class ColorWizardCoordinator: ViewModel {
private let resolver: Resolver

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import SwiftUI

struct ColorWizardCoordinatorView: View {
@ObservedObject var coordinator: ColorWizardCoordinator
var coordinator: ColorWizardCoordinator

var body: some View {
ObjectNavigationStack(path: self.coordinator.path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Foundation
import Combine
import SwiftUI

@Observable
class ColorWizardConfigurationViewModel: ViewModel {
let pages: [PageViewModel]

Expand All @@ -22,6 +23,7 @@ class ColorWizardConfigurationViewModel: ViewModel {
}

extension ColorWizardConfigurationViewModel {
@Observable
class PageViewModel: ViewModel {
let index: Int
let title: String
Expand All @@ -36,6 +38,7 @@ extension ColorWizardConfigurationViewModel {
}
}

@Observable
class ColorViewModel: ViewModel {
let id: String = UUID().uuidString
let color: Color
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import SwiftUI

struct ColorWizardContentView: View {
@ObservedObject var viewModel: ColorWizardContentViewModel
var viewModel: ColorWizardContentViewModel

var body: some View {
ZStack {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protocol ColorWizardContentViewModelDelegate: AnyObject {
func colorWizardContentViewModel(_ source: ColorWizardContentViewModel, didCompleteFromIndex index: Int)
}

@Observable
class ColorWizardContentViewModel: ViewModel {
private var pageViewModel: ColorWizardConfigurationViewModel.PageViewModel!
private weak var delegate: ColorWizardContentViewModelDelegate?
Expand Down
2 changes: 1 addition & 1 deletion MVVM.Demo.SwiftUI/UI/Landing/LandingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct LandingView: View {
@ScaledMetric private var buttonPadding: CGFloat = 8.0
@ScaledMetric private var inverseHorizontalPadding: CGFloat = 8.0

@ObservedObject var viewModel: LandingViewModel
@State var viewModel: LandingViewModel

@State private var isAuthenticated: Bool = false
@State private var username: String = .empty
Expand Down
1 change: 1 addition & 0 deletions MVVM.Demo.SwiftUI/UI/Landing/LandingViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ protocol LandingViewModelDelegate: AnyObject {
func landingViewModelDidTapColorWizard(_ source: LandingViewModel)
}

@Observable
class LandingViewModel: ViewModel {
private let alertService: AlertServiceProtocol
private let authenticationService: AuthenticationServiceProtocol
Expand Down
4 changes: 2 additions & 2 deletions MVVM.Demo.SwiftUI/UI/Pulse/PulseView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import SwiftUI

struct PulseView: View {
@ObservedObject var viewModel: PulseViewModel
var viewModel: PulseViewModel

@State private var title: String = .empty

Expand All @@ -33,7 +33,7 @@ struct PulseView: View {

extension PulseView {
struct PulseCircle: View {
@ObservedObject var viewModel: PulseViewModel.ColorItem
@State var viewModel: PulseViewModel.ColorItem

var body: some View {
Circle()
Expand Down
10 changes: 5 additions & 5 deletions MVVM.Demo.SwiftUI/UI/Pulse/PulseViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ import Foundation
import Combine
import SwiftUI

protocol PulseViewModelDelegate: AnyObject {

}
protocol PulseViewModelDelegate: AnyObject {}

@Observable
class PulseViewModel: ViewModel {
private let authenticationService: AuthenticationServiceProtocol
private let colorService: ColorServiceProtocol
private weak var delegate: PulseViewModelDelegate?

@Published private(set) var colors: [ColorItem] = []
private(set) var colors: [ColorItem] = []

var title: AnyPublisher<String, Never> {
self.authenticationService.user
Expand Down Expand Up @@ -61,10 +60,11 @@ class PulseViewModel: ViewModel {
}

extension PulseViewModel {
@Observable
class ColorItem: ViewModel {
let id: String = UUID().uuidString
let color: Color
@Published var opacity: Double = 0.0
var opacity: Double = 0.0

init(color model: ColorModel) {
switch model {
Expand Down
2 changes: 1 addition & 1 deletion MVVM.Demo.SwiftUI/UI/SignIn/SignInView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct SignInView: View {
@ScaledMetric private var buttonFontSize: CGFloat = 18.0
@ScaledMetric private var inverseCardPadding: CGFloat = 16.0

@ObservedObject var viewModel: SignInViewModel
@State var viewModel: SignInViewModel

@State private var showCard: Bool = false
@State private var signInDisabled: Bool = true
Expand Down
22 changes: 15 additions & 7 deletions MVVM.Demo.SwiftUI/UI/SignIn/SignInViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@ protocol SignInViewModelDelegate: AnyObject {
func signInViewModelDidSignIn(_ source: SignInViewModel)
}

@Observable
class SignInViewModel: ViewModel {
private let authenticationService: AuthenticationServiceProtocol

private weak var delegate: SignInViewModelDelegate?

@Published var username: String = .empty
@Published var password: String = .empty
var thing: String = .empty

var username: String = .empty {
didSet { self.username$.send(self.username) }
}
private let username$: CurrentValueSubject<String, Never> = CurrentValueSubject(.empty)

var password: String = .empty {
didSet { self.password$.send(self.password) }
}
private let password$: CurrentValueSubject<String, Never> = CurrentValueSubject(.empty)

let cancel: PassthroughSubject<Void, Never> = PassthroughSubject()
let signIn: PassthroughSubject<Void, Never> = PassthroughSubject()
Expand All @@ -33,8 +43,8 @@ class SignInViewModel: ViewModel {
self.authenticationService = authenticationService

self.canSignIn = [
self.$username,
self.$password
self.username$,
self.password$,
]
.combineLatest()
.map {
Expand Down Expand Up @@ -63,9 +73,7 @@ class SignInViewModel: ViewModel {
.store(in: &self.cancelBag)

self.signIn
.withLatestFrom(
self.$username,
self.$password)
.withLatestFrom(self.username$, self.password$)
.setFailureType(to: Error.self)
.flatMapLatest { username, password -> AnyPublisher<User, Error> in
authenticationService
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ MVVM Wiki: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel
- Greatly improves the ability to unit test your code by registering contained objects as Protocols which can be substituted with mocks
- Makes sharing observable data throughout the app trivial while keeping each class/service focused and independent

# Looking for an iOS 14 or 15 Example?
# Looking for older iOS Examples?

## iOS 16
https://github.com/jasonjrr/MVVM.Demo.SwiftUI/releases/tag/3.0.0

## iOS 14 & 15
https://github.com/jasonjrr/MVVM.Demo.SwiftUI/releases/tag/2.1.0

Version `2.1.0` was built on iOS 14 and solves the navigation problems most developers experiences with `NavigationView`.

0 comments on commit 34e0254

Please sign in to comment.