From 6c59d66afa47ce89dc710b2bf66cf3784bd66e00 Mon Sep 17 00:00:00 2001 From: Mustafa Merza <mustafa.merza95@gmail.com> Date: Sun, 21 Jul 2024 10:34:18 +0300 Subject: [PATCH] - Added custom main view, main view model and main coordinator to be reusable. - Added custom modifier to handle layout changes. --- MiniScanner.xcodeproj/project.pbxproj | 32 +++++++++++++++ .../Extensions/View/View+LayoutChanges.swift | 38 ++++++++++++++++++ .../Common/Data/Model/SupportedLanguage.swift | 4 ++ .../Common/Presentation/MainCoordinator.swift | 39 +++++++++++++++++++ .../Common/Presentation/MainView.swift | 33 ++++++++++++++++ .../Common/Presentation/MainViewModel.swift | 21 ++++++++++ 6 files changed, 167 insertions(+) create mode 100644 MiniScanner/Extensions/View/View+LayoutChanges.swift create mode 100644 MiniScanner/Features/Common/Presentation/MainCoordinator.swift create mode 100644 MiniScanner/Features/Common/Presentation/MainView.swift create mode 100644 MiniScanner/Features/Common/Presentation/MainViewModel.swift diff --git a/MiniScanner.xcodeproj/project.pbxproj b/MiniScanner.xcodeproj/project.pbxproj index 2066f5c..f2abd73 100644 --- a/MiniScanner.xcodeproj/project.pbxproj +++ b/MiniScanner.xcodeproj/project.pbxproj @@ -194,6 +194,10 @@ 672C46DA2C48018400497EF0 /* Color+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672C46D92C48018400497EF0 /* Color+Colors.swift */; }; 672C46DC2C48018D00497EF0 /* UIColors+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672C46DB2C48018D00497EF0 /* UIColors+Colors.swift */; }; 67807F8C2C48F49B00D1F168 /* HandleAppLanguageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67807F8B2C48F49B00D1F168 /* HandleAppLanguageUseCase.swift */; }; + 67807F952C49306C00D1F168 /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67807F942C49306C00D1F168 /* MainCoordinator.swift */; }; + 67807F992C49320100D1F168 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67807F982C49320100D1F168 /* MainViewModel.swift */; }; + 67807F9B2C49325A00D1F168 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67807F9A2C49325A00D1F168 /* MainView.swift */; }; + 67807FA02C4934BD00D1F168 /* View+LayoutChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67807F9F2C4934BD00D1F168 /* View+LayoutChanges.swift */; }; B827E5196CC419E773B843E1 /* Pods_MiniScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9A37DC9F9A8E3AF632DFB98 /* Pods_MiniScanner.framework */; }; EC0CF1FE254D8BBF00888722 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0CF1FD254D8BBF00888722 /* AppDelegate.swift */; }; EC0CF200254D8BBF00888722 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0CF1FF254D8BBF00888722 /* SceneDelegate.swift */; }; @@ -438,6 +442,10 @@ 672C46D92C48018400497EF0 /* Color+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Colors.swift"; sourceTree = "<group>"; }; 672C46DB2C48018D00497EF0 /* UIColors+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColors+Colors.swift"; sourceTree = "<group>"; }; 67807F8B2C48F49B00D1F168 /* HandleAppLanguageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleAppLanguageUseCase.swift; sourceTree = "<group>"; }; + 67807F942C49306C00D1F168 /* MainCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCoordinator.swift; sourceTree = "<group>"; }; + 67807F982C49320100D1F168 /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = "<group>"; }; + 67807F9A2C49325A00D1F168 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; }; + 67807F9F2C4934BD00D1F168 /* View+LayoutChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+LayoutChanges.swift"; sourceTree = "<group>"; }; E8AF4FB39674DF589D719DCF /* Pods-MiniScanner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MiniScanner.release.xcconfig"; path = "Target Support Files/Pods-MiniScanner/Pods-MiniScanner.release.xcconfig"; sourceTree = "<group>"; }; E9A37DC9F9A8E3AF632DFB98 /* Pods_MiniScanner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MiniScanner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC0CF1FA254D8BBF00888722 /* MiniScanner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MiniScanner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1000,6 +1008,7 @@ 672C46A62C47E8D700497EF0 /* DI */, 672C46C42C47E98A00497EF0 /* Data */, 672C46BD2C47E98900497EF0 /* Domain */, + 67807F972C49313C00D1F168 /* Presentation */, ); path = Common; sourceTree = "<group>"; @@ -1103,6 +1112,24 @@ path = Style; sourceTree = "<group>"; }; + 67807F972C49313C00D1F168 /* Presentation */ = { + isa = PBXGroup; + children = ( + 67807F9A2C49325A00D1F168 /* MainView.swift */, + 67807F982C49320100D1F168 /* MainViewModel.swift */, + 67807F942C49306C00D1F168 /* MainCoordinator.swift */, + ); + path = Presentation; + sourceTree = "<group>"; + }; + 67807F9C2C49348B00D1F168 /* View */ = { + isa = PBXGroup; + children = ( + 67807F9F2C4934BD00D1F168 /* View+LayoutChanges.swift */, + ); + path = View; + sourceTree = "<group>"; + }; 98E49D3F46C8E62718825860 /* Pods */ = { isa = PBXGroup; children = ( @@ -1193,6 +1220,7 @@ EC8A9B0A254DC2FD00F9AF99 /* Extensions */ = { isa = PBXGroup; children = ( + 67807F9C2C49348B00D1F168 /* View */, 672C46D82C48017C00497EF0 /* Style */, 672C46492C47BD8800497EF0 /* String+StringKeys.swift */, EC0CF21C254D8F3900888722 /* String+Extensions.swift */, @@ -1496,6 +1524,7 @@ 539996952C27130000671340 /* ConstraintConstantTarget.swift in Sources */, 672C46622C47C74300497EF0 /* DependencyInjector.swift in Sources */, 539996902C27130000671340 /* UILayoutSupport+Extensions.swift in Sources */, + 67807F952C49306C00D1F168 /* MainCoordinator.swift in Sources */, 53014F992C11A8E80071CE39 /* CGPoint+Utils.swift in Sources */, 53E7D3392C1B00880025A1D3 /* FSPageViewLayout.swift in Sources */, 53BEB1422C2967E0005A3567 /* FoldersViewController.swift in Sources */, @@ -1521,6 +1550,7 @@ EC702546254E1E9E00BE1958 /* WalkthroughModel.swift in Sources */, 53D9D1C22C1AF521004D1C1C /* StickerView.swift in Sources */, 672C46D52C47F8D000497EF0 /* HandleAppStartUseCase.swift in Sources */, + 67807FA02C4934BD00D1F168 /* View+LayoutChanges.swift in Sources */, 539996A22C27130000671340 /* ConstraintDirectionalInsets.swift in Sources */, 53E7D3352C1B00880025A1D3 /* FSPagerView.swift in Sources */, 672C46B32C47E8D700497EF0 /* DependencyManager.swift in Sources */, @@ -1571,6 +1601,7 @@ 53014F9E2C11A8E80071CE39 /* UIImage+Orientation.swift in Sources */, 53014FB82C11A8E80071CE39 /* Transformable.swift in Sources */, 53014FA22C11A8E80071CE39 /* ScannedPageViewController.swift in Sources */, + 67807F9B2C49325A00D1F168 /* MainView.swift in Sources */, 5399969D2C27130000671340 /* Constraint.swift in Sources */, EC8A9B1D254DCEC600F9AF99 /* File.swift in Sources */, 67807F8C2C48F49B00D1F168 /* HandleAppLanguageUseCase.swift in Sources */, @@ -1580,6 +1611,7 @@ 53014FBB2C11A8E80071CE39 /* CaptureSession+Orientation.swift in Sources */, 53014F942C11A8E80071CE39 /* DeviceOrientationHelper.swift in Sources */, EC8A9B27254DE91B00F9AF99 /* DocumentPreviewViewController.swift in Sources */, + 67807F992C49320100D1F168 /* MainViewModel.swift in Sources */, 5399964A2C26E86700671340 /* CropperConstantValues.m in Sources */, 53014F9F2C11A8E80071CE39 /* UIImage+Utils.swift in Sources */, 53CD5F582C1504CF0010424B /* UIButton+Extensions.swift in Sources */, diff --git a/MiniScanner/Extensions/View/View+LayoutChanges.swift b/MiniScanner/Extensions/View/View+LayoutChanges.swift new file mode 100644 index 0000000..8c57593 --- /dev/null +++ b/MiniScanner/Extensions/View/View+LayoutChanges.swift @@ -0,0 +1,38 @@ +// +// View+LayoutChanges.swift +// MiniScanner +// +// Created by Mustafa Merza on 7/18/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import SwiftUI + +struct LayoutChangesModifier: ViewModifier { + + @State var layoutDirection: LayoutDirection = .leftToRight + + @Inject var getLanguageUseCase: GetLanguageUseCase + + func body(content: Content) -> some View { + + content + .onAppear { + updateLayoutDirection() + } + .environment(\.layoutDirection, layoutDirection) + } + + private func updateLayoutDirection() { + let language = getLanguageUseCase.execute() + layoutDirection = language.layoutDirection + } +} + +extension View { + + func handleLayoutChanges() -> some View { + ModifiedContent(content: self, + modifier: LayoutChangesModifier()) + } +} diff --git a/MiniScanner/Features/Common/Data/Model/SupportedLanguage.swift b/MiniScanner/Features/Common/Data/Model/SupportedLanguage.swift index 12a94a7..3883c3b 100644 --- a/MiniScanner/Features/Common/Data/Model/SupportedLanguage.swift +++ b/MiniScanner/Features/Common/Data/Model/SupportedLanguage.swift @@ -33,6 +33,10 @@ enum SupportedLanguage: CaseIterable { var isRTL: Bool { LanguageManager.shared.isRightToLeft } + + var layoutDirection: LayoutDirection { + isRTL ? .rightToLeft : .leftToRight + } } extension SupportedLanguage { diff --git a/MiniScanner/Features/Common/Presentation/MainCoordinator.swift b/MiniScanner/Features/Common/Presentation/MainCoordinator.swift new file mode 100644 index 0000000..c086666 --- /dev/null +++ b/MiniScanner/Features/Common/Presentation/MainCoordinator.swift @@ -0,0 +1,39 @@ +// +// MainCoordinator.swift +// MiniScanner +// +// Created by Mustafa Merza on 7/18/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import SwiftUI + +protocol MainCoordinator { + + var navigationController: UINavigationController { get set } + + func start() + + func coordinate(to coordinator: MainCoordinator) + + func coordinateBack() + + func coordinateToView(_ view: some View, title: String?) +} + +extension MainCoordinator { + + func coordinate(to coordinator: MainCoordinator) { + coordinator.start() + } + + func coordinateBack() { + navigationController.popViewController(animated: true) + } + + func coordinateToView(_ view: some View, title: String? = nil) { + let hostingController = UIHostingController(rootView: view) + hostingController.title = title + navigationController.pushViewController(hostingController, animated: true) + } +} diff --git a/MiniScanner/Features/Common/Presentation/MainView.swift b/MiniScanner/Features/Common/Presentation/MainView.swift new file mode 100644 index 0000000..18675c4 --- /dev/null +++ b/MiniScanner/Features/Common/Presentation/MainView.swift @@ -0,0 +1,33 @@ +// +// MainView.swift +// MiniScanner +// +// Created by Mustafa Merza on 7/18/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import SwiftUI + +struct MainView<ViewModel: MainViewModel, Content: View>: View { + + var viewModel: ViewModel + + @ViewBuilder var content: () -> Content + + init(viewModel: ViewModel, + @ViewBuilder content: @escaping () -> Content) { + self.viewModel = viewModel + self.content = content + } + + var body: some View { + + content() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .onAppear { + viewModel.onAppear() + } + .onDisappear() { viewModel.onDisappear() } + .handleLayoutChanges() + } +} diff --git a/MiniScanner/Features/Common/Presentation/MainViewModel.swift b/MiniScanner/Features/Common/Presentation/MainViewModel.swift new file mode 100644 index 0000000..de1b2a6 --- /dev/null +++ b/MiniScanner/Features/Common/Presentation/MainViewModel.swift @@ -0,0 +1,21 @@ +// +// MainViewModel.swift +// MiniScanner +// +// Created by Mustafa Merza on 7/18/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import Foundation + +protocol MainViewModel: ObservableObject { + + func onAppear() + + func onDisappear() +} + +extension MainViewModel { + + func onDisappear() { } +} -- GitLab