From 997ba21d4ea7fb1178f59a596ee08555a08c387c Mon Sep 17 00:00:00 2001 From: Igor Kulman Date: Tue, 27 May 2025 09:23:52 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Replace=20Swinject=20w?= =?UTF-8?q?ith=20a=20simple=20manual=20container?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - .../iOSSampleApp.xcodeproj/project.pbxproj | 141 ++++-------------- .../xcschemes/iOSSampleApp.xcscheme | 2 +- Sources/iOSSampleApp/AppDelegate.swift | 5 +- Sources/iOSSampleApp/Container.swift | 72 +++++++++ Sources/iOSSampleApp/Data/Licenses.plist | 80 ++-------- .../About/Coordinators/AboutCoordinator.swift | 7 +- .../Common/Coordinators/AppCoordinator.swift | 5 +- .../Feed/Coordinators/FeedCoordinator.swift | 5 +- .../Setup/Coordinators/SetupCoordinator.swift | 7 +- .../Supporting Files/AppDelegate+Setup.swift | 57 ------- .../Coordinators/Coordinator.swift | 6 - .../ViewControllerLeakTests+Setup.swift | 48 ------ .../ViewControllerLeakTests.swift | 25 ++-- 14 files changed, 135 insertions(+), 326 deletions(-) create mode 100644 Sources/iOSSampleApp/Container.swift delete mode 100644 Sources/iOSSampleApp/Supporting Files/AppDelegate+Setup.swift delete mode 100644 Sources/iOSSampleAppTests/ViewControllerLeakTests+Setup.swift diff --git a/README.md b/README.md index ac91e5a..a88d139 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ Sample iOS app written the way I write iOS apps because I cannot share the app I - [RxSwift](https://github.com/ReactiveX/RxSwift) - Reactive Programming in Swift - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) - A collection of Rx operators & tools not found in the core RxSwift distribution -- [Swinject](https://github.com/Swinject/Swinject) - Dependency injection framework for Swift - [Reusable](https://github.com/AliSoftware/Reusable) - A Swift mixin for reusing views easily and in a type-safe way - [Nuke](https://github.com/kean/Nuke) - A powerful image loading and caching system - [FeedKit](https://github.com/nmdias/FeedKit) - An RSS, Atom and JSON Feed parser written in Swift diff --git a/Sources/iOSSampleApp.xcodeproj/project.pbxproj b/Sources/iOSSampleApp.xcodeproj/project.pbxproj index c37a146..a8d17a5 100644 --- a/Sources/iOSSampleApp.xcodeproj/project.pbxproj +++ b/Sources/iOSSampleApp.xcodeproj/project.pbxproj @@ -8,25 +8,14 @@ /* Begin PBXBuildFile section */ F300AE2B244F76DD00F5E060 /* NotificationBannerSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F300AE2A244F76DD00F5E060 /* NotificationBannerSwift */; }; - F3208A891F84E83900B57B0E /* DataServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3208A881F84E83900B57B0E /* DataServiceTests.swift */; }; - F3208A921F84EAF400B57B0E /* CustomSourceViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3208A911F84EAF400B57B0E /* CustomSourceViewModelTests.swift */; }; F33474E7244F64220034B1C2 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F33474E6244F64220034B1C2 /* RxSwift */; }; F33474E9244F64220034B1C2 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = F33474E8244F64220034B1C2 /* RxCocoa */; }; F33474EB244F64220034B1C2 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = F33474EA244F64220034B1C2 /* RxRelay */; }; - F33474EE244F645A0034B1C2 /* Swinject in Frameworks */ = {isa = PBXBuildFile; productRef = F33474ED244F645A0034B1C2 /* Swinject */; }; - F33474F1244F64900034B1C2 /* SwinjectAutoregistration in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F0244F64900034B1C2 /* SwinjectAutoregistration */; }; F33474F4244F64D80034B1C2 /* Reusable in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F3244F64D80034B1C2 /* Reusable */; }; F33474F7244F65050034B1C2 /* FeedKit in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F6244F65050034B1C2 /* FeedKit */; }; F33474FA244F67270034B1C2 /* RxSwiftExt in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F9244F67270034B1C2 /* RxSwiftExt */; }; F35BD6392065111F000AE4E8 /* AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F35BD6382065111F000AE4E8 /* AppUITests.swift */; }; F376423625A4689800C58CE5 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = F376423525A4689800C58CE5 /* Nuke */; }; - F3A812B81F83740E00A09AAB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A812B71F83740E00A09AAB /* AppDelegate.swift */; }; - F3A812BF1F83740E00A09AAB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F3A812BE1F83740E00A09AAB /* Assets.xcassets */; }; - F3A9678A21AC38D8005E7F3F /* ViewControllerLeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A9678921AC38D8005E7F3F /* ViewControllerLeakTests.swift */; }; - F3A9678C21AC3961005E7F3F /* ViewControllerLeakTests+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A9678B21AC3961005E7F3F /* ViewControllerLeakTests+Setup.swift */; }; - F3C8DB3F214EA3AA00C1A654 /* SourceSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C8DB3E214EA3AA00C1A654 /* SourceSelectionViewModelTests.swift */; }; - F3C8DB41214EA7F200C1A654 /* FeedViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C8DB40214EA7F200C1A654 /* FeedViewModelTests.swift */; }; - F3D6865C1F9B761E00879154 /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3D6865B1F9B761E00879154 /* TestExtensions.swift */; }; F3EEA696234A3BE800A2FCB5 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EEA695234A3BE800A2FCB5 /* SnapshotHelper.swift */; }; /* End PBXBuildFile section */ @@ -49,31 +38,33 @@ /* Begin PBXFileReference section */ F3208A7A1F84E48100B57B0E /* iOSSampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSSampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - F3208A7E1F84E48100B57B0E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F3208A881F84E83900B57B0E /* DataServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataServiceTests.swift; sourceTree = ""; }; - F3208A911F84EAF400B57B0E /* CustomSourceViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSourceViewModelTests.swift; sourceTree = ""; }; F35BD6362065111F000AE4E8 /* iOSSampleAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSSampleAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F35BD6382065111F000AE4E8 /* AppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUITests.swift; sourceTree = ""; }; F35BD63A2065111F000AE4E8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F3A812B41F83740E00A09AAB /* iOSSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; - F3A812B71F83740E00A09AAB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - F3A812BE1F83740E00A09AAB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - F3A812C31F83740E00A09AAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F3A9678921AC38D8005E7F3F /* ViewControllerLeakTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerLeakTests.swift; sourceTree = ""; }; - F3A9678B21AC3961005E7F3F /* ViewControllerLeakTests+Setup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewControllerLeakTests+Setup.swift"; sourceTree = ""; }; - F3C8DB3E214EA3AA00C1A654 /* SourceSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceSelectionViewModelTests.swift; sourceTree = ""; }; - F3C8DB40214EA7F200C1A654 /* FeedViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewModelTests.swift; sourceTree = ""; }; - F3D6865B1F9B761E00879154 /* TestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestExtensions.swift; sourceTree = ""; }; F3EEA695234A3BE800A2FCB5 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = ../../fastlane/SnapshotHelper.swift; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + F311CEE32DE595F6001B8E7A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = F3A812B31F83740E00A09AAB /* iOSSampleApp */; + }; + F311CEFD2DE595FD001B8E7A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = F3208A791F84E48100B57B0E /* iOSSampleAppTests */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ - F38AE3A42D2BE0EB00A685E7 /* Scenarios */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Scenarios; sourceTree = ""; }; - F38AE3C52D2BE0EF00A685E7 /* Data */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Data; sourceTree = ""; }; - F38AE3C92D2BE0F200A685E7 /* Resources */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Resources; sourceTree = ""; }; - F38AE3DE2D2BE0F600A685E7 /* Supporting Files */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "Supporting Files"; sourceTree = ""; }; - F38AE3F12D2BE0FC00A685E7 /* Support */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Support; sourceTree = ""; }; - F38AE3F52D2BE0FE00A685E7 /* Mocks */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Mocks; sourceTree = ""; }; + F311CEAD2DE595F6001B8E7A /* iOSSampleApp */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (F311CEE32DE595F6001B8E7A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = iOSSampleApp; sourceTree = ""; }; + F311CEF12DE595FD001B8E7A /* iOSSampleAppTests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (F311CEFD2DE595FD001B8E7A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = iOSSampleAppTests; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -97,11 +88,9 @@ files = ( F33474EB244F64220034B1C2 /* RxRelay in Frameworks */, F33474FA244F67270034B1C2 /* RxSwiftExt in Frameworks */, - F33474F1244F64900034B1C2 /* SwinjectAutoregistration in Frameworks */, F300AE2B244F76DD00F5E060 /* NotificationBannerSwift in Frameworks */, F33474E9244F64220034B1C2 /* RxCocoa in Frameworks */, F33474E7244F64220034B1C2 /* RxSwift in Frameworks */, - F33474EE244F645A0034B1C2 /* Swinject in Frameworks */, F33474F7244F65050034B1C2 /* FeedKit in Frameworks */, F33474F4244F64D80034B1C2 /* Reusable in Frameworks */, F376423625A4689800C58CE5 /* Nuke in Frameworks */, @@ -111,23 +100,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - F3208A7B1F84E48100B57B0E /* iOSSampleAppTests */ = { - isa = PBXGroup; - children = ( - F38AE3F12D2BE0FC00A685E7 /* Support */, - F38AE3F52D2BE0FE00A685E7 /* Mocks */, - F3208A7E1F84E48100B57B0E /* Info.plist */, - F3208A881F84E83900B57B0E /* DataServiceTests.swift */, - F3C8DB40214EA7F200C1A654 /* FeedViewModelTests.swift */, - F3208A911F84EAF400B57B0E /* CustomSourceViewModelTests.swift */, - F3C8DB3E214EA3AA00C1A654 /* SourceSelectionViewModelTests.swift */, - F3A9678921AC38D8005E7F3F /* ViewControllerLeakTests.swift */, - F3A9678B21AC3961005E7F3F /* ViewControllerLeakTests+Setup.swift */, - F3D6865B1F9B761E00879154 /* TestExtensions.swift */, - ); - path = iOSSampleAppTests; - sourceTree = ""; - }; F35BD6372065111F000AE4E8 /* iOSSampleAppUITests */ = { isa = PBXGroup; children = ( @@ -141,8 +113,8 @@ F3A812AB1F83740E00A09AAB = { isa = PBXGroup; children = ( - F3A812B61F83740E00A09AAB /* iOSSampleApp */, - F3208A7B1F84E48100B57B0E /* iOSSampleAppTests */, + F311CEAD2DE595F6001B8E7A /* iOSSampleApp */, + F311CEF12DE595FD001B8E7A /* iOSSampleAppTests */, F35BD6372065111F000AE4E8 /* iOSSampleAppUITests */, F3A812B51F83740E00A09AAB /* Products */, F3A812E21F8376A900A09AAB /* Frameworks */, @@ -161,20 +133,6 @@ name = Products; sourceTree = ""; }; - F3A812B61F83740E00A09AAB /* iOSSampleApp */ = { - isa = PBXGroup; - children = ( - F38AE3C52D2BE0EF00A685E7 /* Data */, - F38AE3C92D2BE0F200A685E7 /* Resources */, - F38AE3A42D2BE0EB00A685E7 /* Scenarios */, - F38AE3DE2D2BE0F600A685E7 /* Supporting Files */, - F3A812B71F83740E00A09AAB /* AppDelegate.swift */, - F3A812BE1F83740E00A09AAB /* Assets.xcassets */, - F3A812C31F83740E00A09AAB /* Info.plist */, - ); - path = iOSSampleApp; - sourceTree = ""; - }; F3A812E21F8376A900A09AAB /* Frameworks */ = { isa = PBXGroup; children = ( @@ -199,8 +157,7 @@ F3208A801F84E48100B57B0E /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( - F38AE3F12D2BE0FC00A685E7 /* Support */, - F38AE3F52D2BE0FE00A685E7 /* Mocks */, + F311CEF12DE595FD001B8E7A /* iOSSampleAppTests */, ); name = iOSSampleAppTests; packageProductDependencies = ( @@ -241,18 +198,13 @@ F32344CF29D814C000B1886D /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( - F38AE3A42D2BE0EB00A685E7 /* Scenarios */, - F38AE3C52D2BE0EF00A685E7 /* Data */, - F38AE3C92D2BE0F200A685E7 /* Resources */, - F38AE3DE2D2BE0F600A685E7 /* Supporting Files */, + F311CEAD2DE595F6001B8E7A /* iOSSampleApp */, ); name = iOSSampleApp; packageProductDependencies = ( F33474E6244F64220034B1C2 /* RxSwift */, F33474E8244F64220034B1C2 /* RxCocoa */, F33474EA244F64220034B1C2 /* RxRelay */, - F33474ED244F645A0034B1C2 /* Swinject */, - F33474F0244F64900034B1C2 /* SwinjectAutoregistration */, F33474F3244F64D80034B1C2 /* Reusable */, F33474F6244F65050034B1C2 /* FeedKit */, F33474F9244F67270034B1C2 /* RxSwiftExt */, @@ -271,7 +223,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1520; + LastUpgradeCheck = 1630; ORGANIZATIONNAME = "Igor Kulman"; TargetAttributes = { F3208A791F84E48100B57B0E = { @@ -305,8 +257,6 @@ mainGroup = F3A812AB1F83740E00A09AAB; packageReferences = ( F33474E5244F64220034B1C2 /* XCRemoteSwiftPackageReference "RxSwift" */, - F33474EC244F645A0034B1C2 /* XCRemoteSwiftPackageReference "Swinject" */, - F33474EF244F64900034B1C2 /* XCRemoteSwiftPackageReference "SwinjectAutoregistration" */, F33474F2244F64D80034B1C2 /* XCRemoteSwiftPackageReference "Reusable" */, F33474F5244F65050034B1C2 /* XCRemoteSwiftPackageReference "FeedKit" */, F33474F8244F67270034B1C2 /* XCRemoteSwiftPackageReference "RxSwiftExt" */, @@ -345,7 +295,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - F3A812BF1F83740E00A09AAB /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -356,13 +305,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F3208A921F84EAF400B57B0E /* CustomSourceViewModelTests.swift in Sources */, - F3C8DB41214EA7F200C1A654 /* FeedViewModelTests.swift in Sources */, - F3D6865C1F9B761E00879154 /* TestExtensions.swift in Sources */, - F3A9678C21AC3961005E7F3F /* ViewControllerLeakTests+Setup.swift in Sources */, - F3C8DB3F214EA3AA00C1A654 /* SourceSelectionViewModelTests.swift in Sources */, - F3208A891F84E83900B57B0E /* DataServiceTests.swift in Sources */, - F3A9678A21AC38D8005E7F3F /* ViewControllerLeakTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,7 +321,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F3A812B81F83740E00A09AAB /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -409,7 +350,6 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S6EJ3ZVM4G; INFOPLIST_FILE = iOSSampleAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -431,7 +371,6 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S6EJ3ZVM4G; INFOPLIST_FILE = iOSSampleAppTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -450,7 +389,6 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S6EJ3ZVM4G; INFOPLIST_FILE = iOSSampleAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -469,7 +407,6 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S6EJ3ZVM4G; INFOPLIST_FILE = iOSSampleAppUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -521,6 +458,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = S6EJ3ZVM4G; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -584,6 +522,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = S6EJ3ZVM4G; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -609,7 +548,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S6EJ3ZVM4G; INFOPLIST_FILE = iOSSampleApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -627,7 +565,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = S6EJ3ZVM4G; INFOPLIST_FILE = iOSSampleApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -706,22 +643,6 @@ version = 6.6.0; }; }; - F33474EC244F645A0034B1C2 /* XCRemoteSwiftPackageReference "Swinject" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Swinject/Swinject"; - requirement = { - kind = exactVersion; - version = 2.7.1; - }; - }; - F33474EF244F64900034B1C2 /* XCRemoteSwiftPackageReference "SwinjectAutoregistration" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Swinject/SwinjectAutoregistration"; - requirement = { - kind = exactVersion; - version = 2.7.0; - }; - }; F33474F2244F64D80034B1C2 /* XCRemoteSwiftPackageReference "Reusable" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/AliSoftware/Reusable"; @@ -790,16 +711,6 @@ package = F33474E5244F64220034B1C2 /* XCRemoteSwiftPackageReference "RxSwift" */; productName = RxRelay; }; - F33474ED244F645A0034B1C2 /* Swinject */ = { - isa = XCSwiftPackageProductDependency; - package = F33474EC244F645A0034B1C2 /* XCRemoteSwiftPackageReference "Swinject" */; - productName = Swinject; - }; - F33474F0244F64900034B1C2 /* SwinjectAutoregistration */ = { - isa = XCSwiftPackageProductDependency; - package = F33474EF244F64900034B1C2 /* XCRemoteSwiftPackageReference "SwinjectAutoregistration" */; - productName = SwinjectAutoregistration; - }; F33474F3244F64D80034B1C2 /* Reusable */ = { isa = XCSwiftPackageProductDependency; package = F33474F2244F64D80034B1C2 /* XCRemoteSwiftPackageReference "Reusable" */; diff --git a/Sources/iOSSampleApp.xcodeproj/xcshareddata/xcschemes/iOSSampleApp.xcscheme b/Sources/iOSSampleApp.xcodeproj/xcshareddata/xcschemes/iOSSampleApp.xcscheme index ae2b7be..abee66d 100644 --- a/Sources/iOSSampleApp.xcodeproj/xcshareddata/xcschemes/iOSSampleApp.xcscheme +++ b/Sources/iOSSampleApp.xcodeproj/xcshareddata/xcschemes/iOSSampleApp.xcscheme @@ -1,6 +1,6 @@ Bool { - setupDependencies() + container = Container() return true } diff --git a/Sources/iOSSampleApp/Container.swift b/Sources/iOSSampleApp/Container.swift new file mode 100644 index 0000000..da50182 --- /dev/null +++ b/Sources/iOSSampleApp/Container.swift @@ -0,0 +1,72 @@ +// +// AppContainer.swift +// iOSSampleApp +// +// Created by Igor Kulman on 27.05.2025. +// Copyright © 2025 Igor Kulman. All rights reserved. +// + +import Foundation +import OSLog + +final class Container { + + init() { + Logger.appFlow.debug("Registering dependencies") + #if DEBUG + if ProcessInfo().arguments.contains("testMode") { + Logger.appFlow.debug("Running in UI tests, deleting selected source to start clean") + settingsService.selectedSource = nil + } + #endif + } + + // MARK: - Services + + lazy var dataService: DataService = RssDataService() + lazy var settingsService: SettingsService = UserDefaultsSettingsService() + + // MARK: - ViewModels + + func makeSourceSelectionViewModell() -> SourceSelectionViewModel { + SourceSelectionViewModel(settingsService: settingsService) + } + + func makeCustomSourceViewModel() -> CustomSourceViewModel { + CustomSourceViewModel() + } + + func makeFeedViewModel() -> FeedViewModel { + FeedViewModel(dataService: dataService, settingsService: settingsService) + } + + func makeLibrariesViewModel() -> LibrariesViewModel { + LibrariesViewModel() + } + + func makeAboutViewModel() -> AboutViewModel { + AboutViewModel() + } + + // MARK: - ViewControllers + + func makeSourceSelectionViewController() -> SourceSelectionViewController { + SourceSelectionViewController(viewModel: makeSourceSelectionViewModell()) + } + + func makeCustomSourceViewController() -> CustomSourceViewController { + CustomSourceViewController(viewModel: makeCustomSourceViewModel()) + } + + func makeFeedViewController() -> FeedViewController { + FeedViewController(viewModel: makeFeedViewModel()) + } + + func makeLibrariesViewController() -> LibrariesViewController { + LibrariesViewController(viewModel: makeLibrariesViewModel()) + } + + func makeAboutViewController() -> AboutViewController { + AboutViewController(viewModel: makeAboutViewModel()) + } +} diff --git a/Sources/iOSSampleApp/Data/Licenses.plist b/Sources/iOSSampleApp/Data/Licenses.plist index f5c2ccf..43b9ee2 100644 --- a/Sources/iOSSampleApp/Data/Licenses.plist +++ b/Sources/iOSSampleApp/Data/Licenses.plist @@ -11,7 +11,7 @@ Copyright (c) 2016 AliSoftware Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal +of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is @@ -20,7 +20,7 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -38,7 +38,7 @@ SOFTWARE. Copyright (c) 2017-2023 Daltron <daltonhint4@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal +of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is @@ -47,7 +47,7 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -66,11 +66,11 @@ THE SOFTWARE. **Copyright © 2015 Krunoslav Zaher, Shai Mishali** **All rights reserved.** -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. title RxSwift @@ -82,7 +82,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI Copyright (c) 2016-latest RxSwiftCommunity https://github.com/RxSwiftCommunity Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal +of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is @@ -91,7 +91,7 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -108,66 +108,10 @@ THE SOFTWARE. text The MIT License (MIT) -Copyright (c) 2015 Swinject Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - title - Swinject - - - license - MIT License - text - Copyright (c) 2016 Swinject Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - title - SwinjectAutoregistration - - - license - MIT License - text - The MIT License (MIT) - Copyright (c) 2015-2024 Alexander Grebenyuk Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal +of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is @@ -176,7 +120,7 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER @@ -196,7 +140,7 @@ SOFTWARE. Copyright (c) 2016 - 2018 Nuno Manuel Dias Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal +of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is @@ -205,7 +149,7 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER diff --git a/Sources/iOSSampleApp/Scenarios/About/Coordinators/AboutCoordinator.swift b/Sources/iOSSampleApp/Scenarios/About/Coordinators/AboutCoordinator.swift index f4ebe52..c15f8f3 100644 --- a/Sources/iOSSampleApp/Scenarios/About/Coordinators/AboutCoordinator.swift +++ b/Sources/iOSSampleApp/Scenarios/About/Coordinators/AboutCoordinator.swift @@ -8,7 +8,6 @@ import Foundation import SafariServices -import Swinject import UIKit protocol AboutCoordinatorDelegate: AnyObject { @@ -23,7 +22,7 @@ final class AboutCoordinator: NavigationCoordinator { // MARK: - Properties let navigationController: UINavigationController - let container: Container + private let container: Container weak var delegate: AboutCoordinatorDelegate? init(container: Container, navigationController: UINavigationController) { @@ -37,7 +36,7 @@ final class AboutCoordinator: NavigationCoordinator { Starts the Abotu flow by showing the basic info and additional menu items */ func start() { - let aboutViewController = container.resolve(AboutViewController.self)! &> { + let aboutViewController = container.makeAboutViewController() &> { $0.delegate = self } navigationController.setBackButton() @@ -48,7 +47,7 @@ final class AboutCoordinator: NavigationCoordinator { Shows the list of open source libraries used by the app */ private func showLibraries() { - let librariesViewController = container.resolve(LibrariesViewController.self)! + let librariesViewController = container.makeLibrariesViewController() navigationController.pushViewController(librariesViewController, animated: true) } diff --git a/Sources/iOSSampleApp/Scenarios/Common/Coordinators/AppCoordinator.swift b/Sources/iOSSampleApp/Scenarios/Common/Coordinators/AppCoordinator.swift index d10e8eb..423f32d 100644 --- a/Sources/iOSSampleApp/Scenarios/Common/Coordinators/AppCoordinator.swift +++ b/Sources/iOSSampleApp/Scenarios/Common/Coordinators/AppCoordinator.swift @@ -8,7 +8,6 @@ import Foundation import OSLog -import Swinject import UIKit enum AppChildCoordinator { @@ -24,7 +23,7 @@ final class AppCoordinator: Coordinator { // MARK: - Properties private let window: UIWindow - let container: Container + private let container: Container private var childCoordinators = [AppChildCoordinator: Coordinator]() private let settingsService: SettingsService private let navigationController: UINavigationController @@ -46,7 +45,7 @@ final class AppCoordinator: Coordinator { $0.navigationBar.scrollEdgeAppearance = $0.navigationBar.standardAppearance } - settingsService = self.container.resolve(SettingsService.self)! + settingsService = container.settingsService self.window.rootViewController = navigationController } diff --git a/Sources/iOSSampleApp/Scenarios/Feed/Coordinators/FeedCoordinator.swift b/Sources/iOSSampleApp/Scenarios/Feed/Coordinators/FeedCoordinator.swift index 94a6465..403ef69 100644 --- a/Sources/iOSSampleApp/Scenarios/Feed/Coordinators/FeedCoordinator.swift +++ b/Sources/iOSSampleApp/Scenarios/Feed/Coordinators/FeedCoordinator.swift @@ -7,7 +7,6 @@ // import Foundation -import Swinject import UIKit enum FeedChildCoordinator { @@ -26,7 +25,7 @@ final class FeedCoordinator: NavigationCoordinator { // MARK: - Properties let navigationController: UINavigationController - let container: Container + private let container: Container private var childCoordinators = [FeedChildCoordinator: Coordinator]() weak var delegate: FeedCoordinatorDelegate? @@ -43,7 +42,7 @@ final class FeedCoordinator: NavigationCoordinator { */ func start() { let isNavigationStackEmpty = navigationController.viewControllers.isEmpty - let feedViewController = container.resolve(FeedViewController.self)! &> { + let feedViewController = container.makeFeedViewController() &> { $0.delegate = self $0.navigationItem.hidesBackButton = true } diff --git a/Sources/iOSSampleApp/Scenarios/Setup/Coordinators/SetupCoordinator.swift b/Sources/iOSSampleApp/Scenarios/Setup/Coordinators/SetupCoordinator.swift index dd56713..fcde920 100644 --- a/Sources/iOSSampleApp/Scenarios/Setup/Coordinators/SetupCoordinator.swift +++ b/Sources/iOSSampleApp/Scenarios/Setup/Coordinators/SetupCoordinator.swift @@ -7,7 +7,6 @@ // import Foundation -import Swinject import UIKit protocol SetupCoordinatorDelegate: AnyObject { @@ -25,7 +24,7 @@ final class SetupCoordinator: NavigationCoordinator { // MARK: - Properties let navigationController: UINavigationController - let container: Container + private let container: Container weak var delegate: SetupCoordinatorDelegate? init(container: Container, navigationController: UINavigationController) { @@ -46,7 +45,7 @@ final class SetupCoordinator: NavigationCoordinator { Shows the screen asking the user to select the RSS source */ private func showSourceSelection() { - let sourceSelectionViewController = container.resolve(SourceSelectionViewController.self)! &> { + let sourceSelectionViewController = container.makeSourceSelectionViewController() &> { $0.delegate = self } navigationController.pushViewController(sourceSelectionViewController, animated: true) @@ -56,7 +55,7 @@ final class SetupCoordinator: NavigationCoordinator { Shows the user a screen to add a custom RSS source */ private func showAddSourceForm() { - let customSourceViewController = container.resolve(CustomSourceViewController.self)! &> { + let customSourceViewController = container.makeCustomSourceViewController() &> { $0.delegate = self } navigationController.pushViewController(customSourceViewController, animated: true) diff --git a/Sources/iOSSampleApp/Supporting Files/AppDelegate+Setup.swift b/Sources/iOSSampleApp/Supporting Files/AppDelegate+Setup.swift deleted file mode 100644 index 87ee563..0000000 --- a/Sources/iOSSampleApp/Supporting Files/AppDelegate+Setup.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// AppDelegate+Setup.swift -// iOSSampleApp -// -// Created by Igor Kulman on 03/10/2017. -// Copyright © 2017 Igor Kulman. All rights reserved. -// - -import Foundation -import OSLog -import Swinject -import SwinjectAutoregistration - -extension AppDelegate { - /** - Set up the depedency graph in the DI container - */ - internal func setupDependencies() { - Logger.appFlow.debug("Registering dependencies") - - // services - container.autoregister(SettingsService.self, initializer: UserDefaultsSettingsService.init).inObjectScope(ObjectScope.container) - container.autoregister(DataService.self, initializer: RssDataService.init).inObjectScope(ObjectScope.container) - - // viewmodels - container.autoregister(SourceSelectionViewModel.self, initializer: SourceSelectionViewModel.init) - container.autoregister(CustomSourceViewModel.self, initializer: CustomSourceViewModel.init) - container.autoregister(FeedViewModel.self, initializer: FeedViewModel.init) - container.autoregister(LibrariesViewModel.self, initializer: LibrariesViewModel.init) - container.autoregister(AboutViewModel.self, initializer: AboutViewModel.init) - - // view controllers - container.register(SourceSelectionViewController.self) { r in - SourceSelectionViewController(viewModel: r~>) - } - container.register(CustomSourceViewController.self) { r in - CustomSourceViewController(viewModel: r~>) - } - container.register(FeedViewController.self) { r in - FeedViewController(viewModel: r~>) - } - container.register(LibrariesViewController.self) { r in - LibrariesViewController(viewModel: r~>) - } - container.register(AboutViewController.self) { r in - AboutViewController(viewModel: r~>) - } - - #if DEBUG - if ProcessInfo().arguments.contains("testMode") { - Logger.appFlow.debug("Running in UI tests, deleting selected source to start clean") - let settingsService = container.resolve(SettingsService.self)! - settingsService.selectedSource = nil - } - #endif - } -} diff --git a/Sources/iOSSampleApp/Supporting Files/Coordinators/Coordinator.swift b/Sources/iOSSampleApp/Supporting Files/Coordinators/Coordinator.swift index de8ea47..b2b3dbf 100644 --- a/Sources/iOSSampleApp/Supporting Files/Coordinators/Coordinator.swift +++ b/Sources/iOSSampleApp/Supporting Files/Coordinators/Coordinator.swift @@ -7,15 +7,9 @@ // import Foundation -import Swinject import UIKit protocol Coordinator: AnyObject { - /** - DI container - */ - var container: Container { get } - /** Entry point starting the coordinator */ diff --git a/Sources/iOSSampleAppTests/ViewControllerLeakTests+Setup.swift b/Sources/iOSSampleAppTests/ViewControllerLeakTests+Setup.swift deleted file mode 100644 index 71e0331..0000000 --- a/Sources/iOSSampleAppTests/ViewControllerLeakTests+Setup.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// ViewControllerLeakTests+Setup.swift -// iOSSampleAppTests -// -// Created by Igor Kulman on 26/11/2018. -// Copyright © 2018 Igor Kulman. All rights reserved. -// - -import Foundation -@testable import iOSSampleApp -import Swinject -import SwinjectAutoregistration - -extension ViewControllerLeakTests { - func setupDependencies() -> Container { - let container = Container() - - // services - container.autoregister(SettingsService.self, initializer: SettingsServiceMock.init).inObjectScope(ObjectScope.container) - container.autoregister(DataService.self, initializer: DataServiceMock.init).inObjectScope(ObjectScope.container) - - // viewmodels - container.autoregister(SourceSelectionViewModel.self, initializer: SourceSelectionViewModel.init) - container.autoregister(CustomSourceViewModel.self, initializer: CustomSourceViewModel.init) - container.autoregister(FeedViewModel.self, initializer: FeedViewModel.init) - container.autoregister(LibrariesViewModel.self, initializer: LibrariesViewModel.init) - container.autoregister(AboutViewModel.self, initializer: AboutViewModel.init) - - // view controllers - container.register(SourceSelectionViewController.self) { r in - SourceSelectionViewController(viewModel: r~>) - } - container.register(CustomSourceViewController.self) { r in - CustomSourceViewController(viewModel: r~>) - } - container.register(FeedViewController.self) { r in - FeedViewController(viewModel: r~>) - } - container.register(LibrariesViewController.self) { r in - LibrariesViewController(viewModel: r~>) - } - container.register(AboutViewController.self) { r in - AboutViewController(viewModel: r~>) - } - - return container - } -} diff --git a/Sources/iOSSampleAppTests/ViewControllerLeakTests.swift b/Sources/iOSSampleAppTests/ViewControllerLeakTests.swift index 9cac730..8ea6913 100644 --- a/Sources/iOSSampleAppTests/ViewControllerLeakTests.swift +++ b/Sources/iOSSampleAppTests/ViewControllerLeakTests.swift @@ -11,27 +11,28 @@ import Foundation import XCTest class ViewControllerLeakTests: XCTestCase { + private lazy var container = Container() &> { + $0.settingsService = SettingsServiceMock() + $0.dataService = DataServiceMock() + } + func testAboutViewController() { - let container = setupDependencies() - let viewController = container.resolve(AboutViewController.self)! + let viewController = container.makeAboutViewController() assertDeallocatedAfterTest(viewController) viewController.loadViewIfNeeded() } func testLibrariesViewController() { - let container = setupDependencies() - let viewController = container.resolve(LibrariesViewController.self)! + let viewController = container.makeLibrariesViewController() assertDeallocatedAfterTest(viewController) viewController.loadViewIfNeeded() } func testFeedViewController() { - let container = setupDependencies() - let settings = container.resolve(SettingsService.self)! - settings.selectedSource = RssSource(title: "Test", url: URL(string:"https://blog.kulman.sk")!, rss: URL(string:"https://blog.kulman.sk/index.xml")!, icon: nil) - let dataService = container.resolve(DataService.self)! as! DataServiceMock + container.settingsService.selectedSource = RssSource(title: "Test", url: URL(string:"https://blog.kulman.sk")!, rss: URL(string:"https://blog.kulman.sk/index.xml")!, icon: nil) + let dataService = container.dataService as! DataServiceMock dataService.result = .success([]) - let viewController = container.resolve(FeedViewController.self)! + let viewController = container.makeFeedViewController() assertDeallocatedAfterTest(viewController) viewController.loadViewIfNeeded() } @@ -43,15 +44,13 @@ class ViewControllerLeakTests: XCTestCase { } func testCustomSourceViewController() { - let container = setupDependencies() - let viewController = container.resolve(CustomSourceViewController.self)! + let viewController = container.makeCustomSourceViewController() assertDeallocatedAfterTest(viewController) viewController.loadViewIfNeeded() } func testSourceSelectionViewController() { - let container = setupDependencies() - let viewController = container.resolve(SourceSelectionViewController.self)! + let viewController = container.makeSourceSelectionViewController() assertDeallocatedAfterTest(viewController) viewController.loadViewIfNeeded() } From ad8b8f90c7922748c5973603745a83a95118aded Mon Sep 17 00:00:00 2001 From: Igor Kulman Date: Tue, 27 May 2025 09:36:57 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Replace=20Reusable=20l?= =?UTF-8?q?ibrary=20with=20a=20simpler=20implementation=20of=20just=20what?= =?UTF-8?q?=20the=20app=20uses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - .../iOSSampleApp.xcodeproj/project.pbxproj | 17 ---------- Sources/iOSSampleApp/Data/Licenses.plist | 29 ---------------- .../Scenarios/About/Cells/AboutCell.swift | 1 - .../Scenarios/About/Cells/LibraryCell.swift | 1 - .../Scenarios/Feed/Cells/FeedCell.swift | 1 - .../Scenarios/Setup/Cells/RssSourceCell.swift | 1 - .../Supporting Files/Reusable.swift | 33 +++++++++++++++++++ 8 files changed, 33 insertions(+), 51 deletions(-) create mode 100644 Sources/iOSSampleApp/Supporting Files/Reusable.swift diff --git a/README.md b/README.md index a88d139..5b7e8d6 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ Sample iOS app written the way I write iOS apps because I cannot share the app I - [RxSwift](https://github.com/ReactiveX/RxSwift) - Reactive Programming in Swift - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) - A collection of Rx operators & tools not found in the core RxSwift distribution -- [Reusable](https://github.com/AliSoftware/Reusable) - A Swift mixin for reusing views easily and in a type-safe way - [Nuke](https://github.com/kean/Nuke) - A powerful image loading and caching system - [FeedKit](https://github.com/nmdias/FeedKit) - An RSS, Atom and JSON Feed parser written in Swift - [NotificationBanner](https://github.com/Daltron/NotificationBanner) - The easiest way to display highly customizable in app notification banners in iOS diff --git a/Sources/iOSSampleApp.xcodeproj/project.pbxproj b/Sources/iOSSampleApp.xcodeproj/project.pbxproj index a8d17a5..676b34f 100644 --- a/Sources/iOSSampleApp.xcodeproj/project.pbxproj +++ b/Sources/iOSSampleApp.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ F33474E7244F64220034B1C2 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F33474E6244F64220034B1C2 /* RxSwift */; }; F33474E9244F64220034B1C2 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = F33474E8244F64220034B1C2 /* RxCocoa */; }; F33474EB244F64220034B1C2 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = F33474EA244F64220034B1C2 /* RxRelay */; }; - F33474F4244F64D80034B1C2 /* Reusable in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F3244F64D80034B1C2 /* Reusable */; }; F33474F7244F65050034B1C2 /* FeedKit in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F6244F65050034B1C2 /* FeedKit */; }; F33474FA244F67270034B1C2 /* RxSwiftExt in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F9244F67270034B1C2 /* RxSwiftExt */; }; F35BD6392065111F000AE4E8 /* AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F35BD6382065111F000AE4E8 /* AppUITests.swift */; }; @@ -92,7 +91,6 @@ F33474E9244F64220034B1C2 /* RxCocoa in Frameworks */, F33474E7244F64220034B1C2 /* RxSwift in Frameworks */, F33474F7244F65050034B1C2 /* FeedKit in Frameworks */, - F33474F4244F64D80034B1C2 /* Reusable in Frameworks */, F376423625A4689800C58CE5 /* Nuke in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -205,7 +203,6 @@ F33474E6244F64220034B1C2 /* RxSwift */, F33474E8244F64220034B1C2 /* RxCocoa */, F33474EA244F64220034B1C2 /* RxRelay */, - F33474F3244F64D80034B1C2 /* Reusable */, F33474F6244F65050034B1C2 /* FeedKit */, F33474F9244F67270034B1C2 /* RxSwiftExt */, F300AE2A244F76DD00F5E060 /* NotificationBannerSwift */, @@ -257,7 +254,6 @@ mainGroup = F3A812AB1F83740E00A09AAB; packageReferences = ( F33474E5244F64220034B1C2 /* XCRemoteSwiftPackageReference "RxSwift" */, - F33474F2244F64D80034B1C2 /* XCRemoteSwiftPackageReference "Reusable" */, F33474F5244F65050034B1C2 /* XCRemoteSwiftPackageReference "FeedKit" */, F33474F8244F67270034B1C2 /* XCRemoteSwiftPackageReference "RxSwiftExt" */, F300AE29244F76DC00F5E060 /* XCRemoteSwiftPackageReference "NotificationBanner" */, @@ -643,14 +639,6 @@ version = 6.6.0; }; }; - F33474F2244F64D80034B1C2 /* XCRemoteSwiftPackageReference "Reusable" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/AliSoftware/Reusable"; - requirement = { - kind = exactVersion; - version = 4.1.1; - }; - }; F33474F5244F65050034B1C2 /* XCRemoteSwiftPackageReference "FeedKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nmdias/FeedKit"; @@ -711,11 +699,6 @@ package = F33474E5244F64220034B1C2 /* XCRemoteSwiftPackageReference "RxSwift" */; productName = RxRelay; }; - F33474F3244F64D80034B1C2 /* Reusable */ = { - isa = XCSwiftPackageProductDependency; - package = F33474F2244F64D80034B1C2 /* XCRemoteSwiftPackageReference "Reusable" */; - productName = Reusable; - }; F33474F6244F65050034B1C2 /* FeedKit */ = { isa = XCSwiftPackageProductDependency; package = F33474F5244F65050034B1C2 /* XCRemoteSwiftPackageReference "FeedKit" */; diff --git a/Sources/iOSSampleApp/Data/Licenses.plist b/Sources/iOSSampleApp/Data/Licenses.plist index 43b9ee2..6272815 100644 --- a/Sources/iOSSampleApp/Data/Licenses.plist +++ b/Sources/iOSSampleApp/Data/Licenses.plist @@ -2,35 +2,6 @@ - - license - MIT License - text - The MIT License (MIT) - -Copyright (c) 2016 AliSoftware - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - title - Reusable - license MIT License diff --git a/Sources/iOSSampleApp/Scenarios/About/Cells/AboutCell.swift b/Sources/iOSSampleApp/Scenarios/About/Cells/AboutCell.swift index 3ac21c4..dce388f 100644 --- a/Sources/iOSSampleApp/Scenarios/About/Cells/AboutCell.swift +++ b/Sources/iOSSampleApp/Scenarios/About/Cells/AboutCell.swift @@ -7,7 +7,6 @@ // import Foundation -import Reusable import UIKit final class AboutCell: UITableViewCell, Reusable { diff --git a/Sources/iOSSampleApp/Scenarios/About/Cells/LibraryCell.swift b/Sources/iOSSampleApp/Scenarios/About/Cells/LibraryCell.swift index ee69f82..0baddf7 100644 --- a/Sources/iOSSampleApp/Scenarios/About/Cells/LibraryCell.swift +++ b/Sources/iOSSampleApp/Scenarios/About/Cells/LibraryCell.swift @@ -7,7 +7,6 @@ // import Foundation -import Reusable import UIKit final class LibraryCell: UITableViewCell, Reusable { diff --git a/Sources/iOSSampleApp/Scenarios/Feed/Cells/FeedCell.swift b/Sources/iOSSampleApp/Scenarios/Feed/Cells/FeedCell.swift index f7bc496..dfb677f 100644 --- a/Sources/iOSSampleApp/Scenarios/Feed/Cells/FeedCell.swift +++ b/Sources/iOSSampleApp/Scenarios/Feed/Cells/FeedCell.swift @@ -6,7 +6,6 @@ // Copyright © 2017 Igor Kulman. All rights reserved. // -import Reusable import UIKit final class FeedCell: UITableViewCell, Reusable { diff --git a/Sources/iOSSampleApp/Scenarios/Setup/Cells/RssSourceCell.swift b/Sources/iOSSampleApp/Scenarios/Setup/Cells/RssSourceCell.swift index 43be368..68f607f 100644 --- a/Sources/iOSSampleApp/Scenarios/Setup/Cells/RssSourceCell.swift +++ b/Sources/iOSSampleApp/Scenarios/Setup/Cells/RssSourceCell.swift @@ -6,7 +6,6 @@ // Copyright © 2017 Igor Kulman. All rights reserved. // -import Reusable import RxSwift import UIKit diff --git a/Sources/iOSSampleApp/Supporting Files/Reusable.swift b/Sources/iOSSampleApp/Supporting Files/Reusable.swift new file mode 100644 index 0000000..8510934 --- /dev/null +++ b/Sources/iOSSampleApp/Supporting Files/Reusable.swift @@ -0,0 +1,33 @@ +// +// Reusable.swift +// iOSSampleApp +// +// Created by Igor Kulman on 27.05.2025. +// Copyright © 2025 Igor Kulman. All rights reserved. +// + +import Foundation +import UIKit + +protocol Reusable: AnyObject { + static var reuseIdentifier: String { get } +} + +extension Reusable { + static var reuseIdentifier: String { + return String(describing: Self.self) + } +} + +extension UITableView { + func register(cellType: T.Type) where T: Reusable { + self.register(cellType, forCellReuseIdentifier: cellType.reuseIdentifier) + } + + func dequeueReusableCell(for indexPath: IndexPath) -> T where T: Reusable { + guard let cell = self.dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else { + fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)") + } + return cell + } +} From 35a6f2cf0a3f440cca5801d2b195ddd9a82fa4ef Mon Sep 17 00:00:00 2001 From: Igor Kulman Date: Tue, 27 May 2025 11:05:36 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Replace=20Nuke=20with?= =?UTF-8?q?=20simple=20url=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - .../iOSSampleApp.xcodeproj/project.pbxproj | 17 ---------- Sources/iOSSampleApp/Data/Licenses.plist | 29 ----------------- .../Setup/ViewModels/RssSourceViewModel.swift | 32 ++++++++++++++----- 4 files changed, 24 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 5b7e8d6..da3206f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ Sample iOS app written the way I write iOS apps because I cannot share the app I - [RxSwift](https://github.com/ReactiveX/RxSwift) - Reactive Programming in Swift - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) - A collection of Rx operators & tools not found in the core RxSwift distribution -- [Nuke](https://github.com/kean/Nuke) - A powerful image loading and caching system - [FeedKit](https://github.com/nmdias/FeedKit) - An RSS, Atom and JSON Feed parser written in Swift - [NotificationBanner](https://github.com/Daltron/NotificationBanner) - The easiest way to display highly customizable in app notification banners in iOS - [SwiftLint](https://github.com/realm/SwiftLint) - A tool to enforce Swift style and conventions diff --git a/Sources/iOSSampleApp.xcodeproj/project.pbxproj b/Sources/iOSSampleApp.xcodeproj/project.pbxproj index 676b34f..e083050 100644 --- a/Sources/iOSSampleApp.xcodeproj/project.pbxproj +++ b/Sources/iOSSampleApp.xcodeproj/project.pbxproj @@ -14,7 +14,6 @@ F33474F7244F65050034B1C2 /* FeedKit in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F6244F65050034B1C2 /* FeedKit */; }; F33474FA244F67270034B1C2 /* RxSwiftExt in Frameworks */ = {isa = PBXBuildFile; productRef = F33474F9244F67270034B1C2 /* RxSwiftExt */; }; F35BD6392065111F000AE4E8 /* AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F35BD6382065111F000AE4E8 /* AppUITests.swift */; }; - F376423625A4689800C58CE5 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = F376423525A4689800C58CE5 /* Nuke */; }; F3EEA696234A3BE800A2FCB5 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EEA695234A3BE800A2FCB5 /* SnapshotHelper.swift */; }; /* End PBXBuildFile section */ @@ -91,7 +90,6 @@ F33474E9244F64220034B1C2 /* RxCocoa in Frameworks */, F33474E7244F64220034B1C2 /* RxSwift in Frameworks */, F33474F7244F65050034B1C2 /* FeedKit in Frameworks */, - F376423625A4689800C58CE5 /* Nuke in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -206,7 +204,6 @@ F33474F6244F65050034B1C2 /* FeedKit */, F33474F9244F67270034B1C2 /* RxSwiftExt */, F300AE2A244F76DD00F5E060 /* NotificationBannerSwift */, - F376423525A4689800C58CE5 /* Nuke */, ); productName = iOSSampleApp; productReference = F3A812B41F83740E00A09AAB /* iOSSampleApp.app */; @@ -257,7 +254,6 @@ F33474F5244F65050034B1C2 /* XCRemoteSwiftPackageReference "FeedKit" */, F33474F8244F67270034B1C2 /* XCRemoteSwiftPackageReference "RxSwiftExt" */, F300AE29244F76DC00F5E060 /* XCRemoteSwiftPackageReference "NotificationBanner" */, - F376423425A4689800C58CE5 /* XCRemoteSwiftPackageReference "Nuke" */, F32344CD29D814B900B1886D /* XCRemoteSwiftPackageReference "SwiftLint" */, F3DFFC5C2A4C84E2001F5565 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */, ); @@ -655,14 +651,6 @@ version = 6.2.1; }; }; - F376423425A4689800C58CE5 /* XCRemoteSwiftPackageReference "Nuke" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/kean/Nuke"; - requirement = { - kind = exactVersion; - version = 9.2.3; - }; - }; F3DFFC5C2A4C84E2001F5565 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/BookBeat/SwiftGenPlugin"; @@ -709,11 +697,6 @@ package = F33474F8244F67270034B1C2 /* XCRemoteSwiftPackageReference "RxSwiftExt" */; productName = RxSwiftExt; }; - F376423525A4689800C58CE5 /* Nuke */ = { - isa = XCSwiftPackageProductDependency; - package = F376423425A4689800C58CE5 /* XCRemoteSwiftPackageReference "Nuke" */; - productName = Nuke; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = F3A812AC1F83740E00A09AAB /* Project object */; diff --git a/Sources/iOSSampleApp/Data/Licenses.plist b/Sources/iOSSampleApp/Data/Licenses.plist index 6272815..15059f9 100644 --- a/Sources/iOSSampleApp/Data/Licenses.plist +++ b/Sources/iOSSampleApp/Data/Licenses.plist @@ -79,35 +79,6 @@ THE SOFTWARE. text The MIT License (MIT) -Copyright (c) 2015-2024 Alexander Grebenyuk - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - title - Nuke - - - license - MIT License - text - The MIT License (MIT) - Copyright (c) 2016 - 2018 Nuno Manuel Dias Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Sources/iOSSampleApp/Scenarios/Setup/ViewModels/RssSourceViewModel.swift b/Sources/iOSSampleApp/Scenarios/Setup/ViewModels/RssSourceViewModel.swift index c8bd404..e2b8aaf 100644 --- a/Sources/iOSSampleApp/Scenarios/Setup/ViewModels/RssSourceViewModel.swift +++ b/Sources/iOSSampleApp/Scenarios/Setup/ViewModels/RssSourceViewModel.swift @@ -7,7 +7,6 @@ // import Foundation -import Nuke import RxCocoa import RxSwift import UIKit @@ -17,6 +16,8 @@ final class RssSourceViewModel { let isSelected = BehaviorRelay(value: false) let icon: Driver + private static let cache = NSCache() + init(source: RssSource) { self.source = source @@ -25,18 +26,33 @@ final class RssSourceViewModel { return } - icon = Observable.create { observer in - let task = ImagePipeline.shared.loadImage(with: iconUrl) { result in - switch result { - case .failure: + icon = Observable.create { observer in + if let cached = RssSourceViewModel.cache.object(forKey: iconUrl as NSURL) { + observer.onNext(cached) + observer.onCompleted() + return Disposables.create() + } + + let task = URLSession.shared.dataTask(with: iconUrl) { data, _, error in + guard let data = data, + let image = UIImage(data: data), + error == nil else { observer.onNext(nil) - case let .success(response): - observer.onNext(response.image) + observer.onCompleted() + return } + + RssSourceViewModel.cache.setObject(image, forKey: iconUrl as NSURL) + observer.onNext(image) + observer.onCompleted() } + + task.resume() + return Disposables.create { task.cancel() } - }.asDriver(onErrorJustReturn: nil) + } + .asDriver(onErrorJustReturn: nil) } } From bc5d49532a443d0cf9ef135daeccfc34c28a0007 Mon Sep 17 00:00:00 2001 From: Igor Kulman Date: Tue, 27 May 2025 15:16:13 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Replace=20Notification?= =?UTF-8?q?Banner=20with=20a=20simple=20custom=20error=20banner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # README.md --- README.md | 1 - .../iOSSampleApp.xcodeproj/project.pbxproj | 17 ----- Sources/iOSSampleApp/Data/Licenses.plist | 27 -------- .../UIViewController+Extensions.swift | 69 +++++++++++++++++-- 4 files changed, 65 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index da3206f..13f5f92 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ Sample iOS app written the way I write iOS apps because I cannot share the app I - [RxSwift](https://github.com/ReactiveX/RxSwift) - Reactive Programming in Swift - [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt) - A collection of Rx operators & tools not found in the core RxSwift distribution - [FeedKit](https://github.com/nmdias/FeedKit) - An RSS, Atom and JSON Feed parser written in Swift -- [NotificationBanner](https://github.com/Daltron/NotificationBanner) - The easiest way to display highly customizable in app notification banners in iOS - [SwiftLint](https://github.com/realm/SwiftLint) - A tool to enforce Swift style and conventions ## Author diff --git a/Sources/iOSSampleApp.xcodeproj/project.pbxproj b/Sources/iOSSampleApp.xcodeproj/project.pbxproj index e083050..1ee556a 100644 --- a/Sources/iOSSampleApp.xcodeproj/project.pbxproj +++ b/Sources/iOSSampleApp.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - F300AE2B244F76DD00F5E060 /* NotificationBannerSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F300AE2A244F76DD00F5E060 /* NotificationBannerSwift */; }; F33474E7244F64220034B1C2 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F33474E6244F64220034B1C2 /* RxSwift */; }; F33474E9244F64220034B1C2 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = F33474E8244F64220034B1C2 /* RxCocoa */; }; F33474EB244F64220034B1C2 /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = F33474EA244F64220034B1C2 /* RxRelay */; }; @@ -86,7 +85,6 @@ files = ( F33474EB244F64220034B1C2 /* RxRelay in Frameworks */, F33474FA244F67270034B1C2 /* RxSwiftExt in Frameworks */, - F300AE2B244F76DD00F5E060 /* NotificationBannerSwift in Frameworks */, F33474E9244F64220034B1C2 /* RxCocoa in Frameworks */, F33474E7244F64220034B1C2 /* RxSwift in Frameworks */, F33474F7244F65050034B1C2 /* FeedKit in Frameworks */, @@ -203,7 +201,6 @@ F33474EA244F64220034B1C2 /* RxRelay */, F33474F6244F65050034B1C2 /* FeedKit */, F33474F9244F67270034B1C2 /* RxSwiftExt */, - F300AE2A244F76DD00F5E060 /* NotificationBannerSwift */, ); productName = iOSSampleApp; productReference = F3A812B41F83740E00A09AAB /* iOSSampleApp.app */; @@ -253,7 +250,6 @@ F33474E5244F64220034B1C2 /* XCRemoteSwiftPackageReference "RxSwift" */, F33474F5244F65050034B1C2 /* XCRemoteSwiftPackageReference "FeedKit" */, F33474F8244F67270034B1C2 /* XCRemoteSwiftPackageReference "RxSwiftExt" */, - F300AE29244F76DC00F5E060 /* XCRemoteSwiftPackageReference "NotificationBanner" */, F32344CD29D814B900B1886D /* XCRemoteSwiftPackageReference "SwiftLint" */, F3DFFC5C2A4C84E2001F5565 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */, ); @@ -611,14 +607,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - F300AE29244F76DC00F5E060 /* XCRemoteSwiftPackageReference "NotificationBanner" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Daltron/NotificationBanner"; - requirement = { - kind = exactVersion; - version = 3.0.4; - }; - }; F32344CD29D814B900B1886D /* XCRemoteSwiftPackageReference "SwiftLint" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/realm/SwiftLint"; @@ -662,11 +650,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - F300AE2A244F76DD00F5E060 /* NotificationBannerSwift */ = { - isa = XCSwiftPackageProductDependency; - package = F300AE29244F76DC00F5E060 /* XCRemoteSwiftPackageReference "NotificationBanner" */; - productName = NotificationBannerSwift; - }; F32344CE29D814C000B1886D /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = F32344CD29D814B900B1886D /* XCRemoteSwiftPackageReference "SwiftLint" */; diff --git a/Sources/iOSSampleApp/Data/Licenses.plist b/Sources/iOSSampleApp/Data/Licenses.plist index 15059f9..5d89f9e 100644 --- a/Sources/iOSSampleApp/Data/Licenses.plist +++ b/Sources/iOSSampleApp/Data/Licenses.plist @@ -2,33 +2,6 @@ - - license - MIT License - text - Copyright (c) 2017-2023 Daltron <daltonhint4@gmail.com> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - title - NotificationBanner - license MIT License diff --git a/Sources/iOSSampleApp/Supporting Files/Extensions/UIViewController+Extensions.swift b/Sources/iOSSampleApp/Supporting Files/Extensions/UIViewController+Extensions.swift index 282ee40..34c0686 100644 --- a/Sources/iOSSampleApp/Supporting Files/Extensions/UIViewController+Extensions.swift +++ b/Sources/iOSSampleApp/Supporting Files/Extensions/UIViewController+Extensions.swift @@ -7,16 +7,77 @@ // import Foundation -import NotificationBannerSwift import UIKit protocol ToastCapable: AnyObject { func showErrorToast(message: String) } -extension ToastCapable where Self: UIViewController { +extension ToastCapable { func showErrorToast(message: String) { - let banner = StatusBarNotificationBanner(title: message, style: .danger) - banner.show() + ToastBanner.show(message: message) + } +} + +final class ToastBanner { + static func show(message: String) { + guard let windowScene = UIApplication.shared.connectedScenes + .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene, + let window = windowScene.windows.first(where: { $0.isKeyWindow }) else { + return + } + + let banner = UIView() &> { + $0.backgroundColor = .systemRed + $0.translatesAutoresizingMaskIntoConstraints = false + } + + let label = UILabel() &> { + $0.text = message + $0.textColor = .white + $0.font = UIFont.preferredFont(forTextStyle: .body) + $0.textAlignment = .center + $0.numberOfLines = 0 + $0.translatesAutoresizingMaskIntoConstraints = false + } + + banner.addSubview(label) + window.addSubview(banner) + + let topConstraint = banner.topAnchor.constraint(equalTo: window.topAnchor) + NSLayoutConstraint.activate([ + banner.leadingAnchor.constraint(equalTo: window.leadingAnchor), + banner.trailingAnchor.constraint(equalTo: window.trailingAnchor), + topConstraint, + + label.leadingAnchor.constraint(equalTo: banner.leadingAnchor, constant: 12), + label.trailingAnchor.constraint(equalTo: banner.trailingAnchor, constant: -12), + label.topAnchor.constraint(equalTo: banner.safeAreaLayoutGuide.topAnchor, constant: 6), + label.bottomAnchor.constraint(equalTo: banner.bottomAnchor, constant: -6) + ]) + + window.layoutIfNeeded() + let height = banner.frame.height + banner.transform = CGAffineTransform(translationX: 0, y: -height) + + UIView.animate( + withDuration: 0.3, + animations: { + banner.transform = .identity + }, + completion: { _ in + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + UIView.animate( + withDuration: 0.3, + animations: { + banner.transform = CGAffineTransform(translationX: 0, y: -height) + }, + completion: { _ in + banner.removeFromSuperview() + } + ) + } + } + ) } }