diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..7a9fec3 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to MobileCore + +We want to make contributing to this project as easy and transparent as possible. Here are a few guidelines for making all our lives easier. + +## Reporting Issues + +A great way to contribute to the project is to send a detailed issue when you encounter an problem. +It is very important to check for the same problem or suggestion in the project's issue list first. If you find a match, just add a small comment there. +Doing this helps prioritize the most common problems and requests. + +When reporting issues, please include the following: + +- The platform name and version (e.g. iOS 13) +- The application version or commit hash +- The version of Xcode you're using +- The full output of any stack trace or compiler error +- Any other details that would be useful in understanding the problem + +This information will help us review and fix your issue faster. + +Please do not be offended if we close your issue and reference this document. +If you believe the issue is truly a fault in the project’s codebase, re-open it. + +## Pull Requests + +We gladly accept any PR's assuming they are well written, documented ( if necessary ) and preferably have test code. +If you're unsure if we'll accept a new feature please open an issue requesting it and we can have a discussion before you code and submit a PR. + +Checklist: +- Fork the repo and create your branch from the latest master (to minimize the conflicts) +- If you've added code that should be tested, add tests. +- If you've changed APIs, update the documentation. +- Ensure the test suite passes. +- Make sure your code lints (swiftlint). diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..7b8e43e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,19 @@ + + +# Report + +## What did you do? + +ℹ Please replace this with what you did. + +## What did you expect to happen? + +ℹ Please replace this with what you expected to happen. + +## What happened instead? + +ℹ Please replace this with of what happened instead. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6c78098 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,10 @@ + diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..3a78eb3 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,31 @@ + +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 30 + +# Number of days of inactivity before a stale Issue or Pull Request is closed +daysUntilClose: 7 + +# Issues or Pull Requests with these labels will never be considered stale +exemptLabels: + - "enhancement" + - "confirmed bug" + +# Label to use when marking as stale +staleLabel: stale + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. +# Comment to post when removing the stale label. Set to `false` to disable +unmarkComment: false + +# Comment to post when closing a stale Issue or Pull Request. Set to `false` to disable +closeComment: > + This issue has been auto-closed because there hasn't been any activity for at least 37 days. + However, we really appreciate your contribution, so thank you for that! 🙏 + Also, feel free to [open a new issue]https://github.com/ppraveentr/MobileCore/issues/new) if you still experience this problem 👍. +# Limit to only `issues` +only: issues diff --git a/.github/workflows/on-push.yml b/.github/workflows/on-push.yml index 0844f86..1f27f92 100644 --- a/.github/workflows/on-push.yml +++ b/.github/workflows/on-push.yml @@ -12,6 +12,8 @@ jobs: - uses: actions/checkout@v2 - name: Install Bundle run: bundle install + - name: Install Swiftlint + run: brew install swiftlint - name: Run swiftlint run: bundle exec fastlane lint @@ -29,7 +31,5 @@ jobs: run: bundle exec fastlane test env: destination: ${{ matrix.destination }} - - name: Pod Lib Lint - run: bundle exec fastlane podlibLint - name: Updload to codecov run: bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/pod-deploy.yml b/.github/workflows/pod-deploy.yml index 2327b4f..b6c99cb 100644 --- a/.github/workflows/pod-deploy.yml +++ b/.github/workflows/pod-deploy.yml @@ -12,6 +12,8 @@ jobs: - uses: actions/checkout@v2 - name: Install Bundle run: bundle install + - name: Install Swiftlint + run: brew install swiftlint - name: Run swiftlint run: bundle exec fastlane lint diff --git a/Example/MobileCoreExample.xcodeproj/project.pbxproj b/Example/MobileCoreExample.xcodeproj/project.pbxproj index 684801f..e6110f1 100644 --- a/Example/MobileCoreExample.xcodeproj/project.pbxproj +++ b/Example/MobileCoreExample.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -12,6 +12,12 @@ D44E56F92341CFAB00A0E56C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D44E56F82341CFAB00A0E56C /* UIKit.framework */; }; D49FE3182345D45A002B3742 /* SampleTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49FE3162345D459002B3742 /* SampleTableViewController.swift */; }; D49FE3192345D45A002B3742 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49FE3172345D459002B3742 /* ViewController.swift */; }; + D4E050642CCB470B0028D1CE /* MobileTheming in Frameworks */ = {isa = PBXBuildFile; productRef = D4E050632CCB470B0028D1CE /* MobileTheming */; }; + D4E050672CCB4A5D0028D1CE /* AppTheming in Frameworks */ = {isa = PBXBuildFile; productRef = D4E050662CCB4A5D0028D1CE /* AppTheming */; }; + D4E050692CCB4A5D0028D1CE /* CoreComponents in Frameworks */ = {isa = PBXBuildFile; productRef = D4E050682CCB4A5D0028D1CE /* CoreComponents */; }; + D4E0506B2CCB4A5D0028D1CE /* CoreUtility in Frameworks */ = {isa = PBXBuildFile; productRef = D4E0506A2CCB4A5D0028D1CE /* CoreUtility */; }; + D4E0506D2CCB4A5D0028D1CE /* MobileTheming in Frameworks */ = {isa = PBXBuildFile; productRef = D4E0506C2CCB4A5D0028D1CE /* MobileTheming */; }; + D4E0506F2CCB4A5D0028D1CE /* NetworkLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D4E0506E2CCB4A5D0028D1CE /* NetworkLayer */; }; DE2A6A3426A4199A001F1FF3 /* AppTheming in Frameworks */ = {isa = PBXBuildFile; productRef = DE2A6A3326A4199A001F1FF3 /* AppTheming */; }; DE2A6A3626A4199A001F1FF3 /* CoreComponents in Frameworks */ = {isa = PBXBuildFile; productRef = DE2A6A3526A4199A001F1FF3 /* CoreComponents */; }; DE2A6A3826A4199A001F1FF3 /* CoreUtility in Frameworks */ = {isa = PBXBuildFile; productRef = DE2A6A3726A4199A001F1FF3 /* CoreUtility */; }; @@ -78,9 +84,9 @@ DE9221D31EF28CDF000C8FC7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DE9221D61EF28CDF000C8FC7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; DE9221D81EF28CDF000C8FC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DE9FF335269F406D0020E855 /* workflows */ = {isa = PBXFileReference; lastKnownFileType = folder; name = workflows; path = ../.github/workflows; sourceTree = ""; }; DE9FF336269F413C0020E855 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; name = Gemfile; path = ../Gemfile; sourceTree = ""; }; DE9FF337269F414E0020E855 /* fastlane */ = {isa = PBXFileReference; lastKnownFileType = folder; name = fastlane; path = ../fastlane; sourceTree = ""; }; + DEB664C426B6763000A18E47 /* .github */ = {isa = PBXFileReference; lastKnownFileType = folder; name = .github; path = ../.github; sourceTree = ""; }; DEBDC8931EF4508900AC77A9 /* MobileCoreExample-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MobileCoreExample-Bridging-Header.h"; sourceTree = ""; }; DEBDC8A21EF4520100AC77A9 /* MobileCoreExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MobileCoreExample.entitlements; sourceTree = ""; }; DEF68499269AEE3A00215BFE /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; name = .swiftlint.yml; path = ../.swiftlint.yml; sourceTree = ""; }; @@ -98,12 +104,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D4E0506D2CCB4A5D0028D1CE /* MobileTheming in Frameworks */, + D4E0506B2CCB4A5D0028D1CE /* CoreUtility in Frameworks */, DE2A6A3826A4199A001F1FF3 /* CoreUtility in Frameworks */, + D4E0506F2CCB4A5D0028D1CE /* NetworkLayer in Frameworks */, + D4E050642CCB470B0028D1CE /* MobileTheming in Frameworks */, DE2A6A3C26A419A3001F1FF3 /* Foundation.framework in Frameworks */, + D4E050692CCB4A5D0028D1CE /* CoreComponents in Frameworks */, DE2A6A3626A4199A001F1FF3 /* CoreComponents in Frameworks */, DE2A6A3A26A4199A001F1FF3 /* NetworkLayer in Frameworks */, D44E56F92341CFAB00A0E56C /* UIKit.framework in Frameworks */, DE2A6A3426A4199A001F1FF3 /* AppTheming in Frameworks */, + D4E050672CCB4A5D0028D1CE /* AppTheming in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -134,11 +146,11 @@ D49FE30E2345D2A4002B3742 /* CI-CD Setup */ = { isa = PBXGroup; children = ( + DEB664C426B6763000A18E47 /* .github */, DE63A3A826A416BF003DE96D /* Package.swift */, D47590162317A68A00080D78 /* MobileCore.podspec */, DE9FF336269F413C0020E855 /* Gemfile */, DE9FF337269F414E0020E855 /* fastlane */, - DE9FF335269F406D0020E855 /* workflows */, D49FE3132345D2FC002B3742 /* .gitignore */, DEF68499269AEE3A00215BFE /* .swiftlint.yml */, D49FE30F2345D2F1002B3742 /* .codecov.yml */, @@ -273,6 +285,12 @@ DE2A6A3526A4199A001F1FF3 /* CoreComponents */, DE2A6A3726A4199A001F1FF3 /* CoreUtility */, DE2A6A3926A4199A001F1FF3 /* NetworkLayer */, + D4E050632CCB470B0028D1CE /* MobileTheming */, + D4E050662CCB4A5D0028D1CE /* AppTheming */, + D4E050682CCB4A5D0028D1CE /* CoreComponents */, + D4E0506A2CCB4A5D0028D1CE /* CoreUtility */, + D4E0506C2CCB4A5D0028D1CE /* MobileTheming */, + D4E0506E2CCB4A5D0028D1CE /* NetworkLayer */, ); productName = FTMobileCoreSample; productReference = DE9221C91EF28CDF000C8FC7 /* MobileCoreExample.app */; @@ -320,7 +338,7 @@ ); mainGroup = DE9221C01EF28CDF000C8FC7; packageReferences = ( - DE2A6A3226A41980001F1FF3 /* XCRemoteSwiftPackageReference "MobileCore" */, + D4E050652CCB4A5D0028D1CE /* XCLocalSwiftPackageReference "../../MobileCore" */, ); productRefGroup = DE9221CA1EF28CDF000C8FC7 /* Products */; projectDirPath = ""; @@ -359,6 +377,7 @@ /* Begin PBXShellScriptBuildPhase section */ D437653C230E8E5B004974CC /* swiftlint Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -440,7 +459,7 @@ DEVELOPMENT_TEAM = W6W48SVQX7; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = MobileCoreTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -471,7 +490,7 @@ DEVELOPMENT_TEAM = W6W48SVQX7; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = MobileCoreTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -540,7 +559,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-Onone"; @@ -548,7 +567,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -599,14 +618,14 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; MTL_ENABLE_DEBUG_INFO = NO; OTHER_SWIFT_FLAGS = ""; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -625,17 +644,18 @@ DEVELOPMENT_TEAM = W6W48SVQX7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.1.2; + MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.ft.$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG -warn-long-expression-type-checking=300"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "$(SWIFT_MODULE_NAME)/$(SWIFT_MODULE_NAME)-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; @@ -656,12 +676,13 @@ DEVELOPMENT_TEAM = W6W48SVQX7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.1.2; + MARKETING_VERSION = 1.0.0; OTHER_SWIFT_FLAGS = "$(inherited) -D"; PRODUCT_BUNDLE_IDENTIFIER = "com.ft.$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -704,18 +725,38 @@ }; /* End XCConfigurationList section */ -/* Begin XCRemoteSwiftPackageReference section */ - DE2A6A3226A41980001F1FF3 /* XCRemoteSwiftPackageReference "MobileCore" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ppraveentr/MobileCore.git"; - requirement = { - branch = master; - kind = branch; - }; +/* Begin XCLocalSwiftPackageReference section */ + D4E050652CCB4A5D0028D1CE /* XCLocalSwiftPackageReference "../../MobileCore" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../MobileCore; }; -/* End XCRemoteSwiftPackageReference section */ +/* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + D4E050632CCB470B0028D1CE /* MobileTheming */ = { + isa = XCSwiftPackageProductDependency; + productName = MobileTheming; + }; + D4E050662CCB4A5D0028D1CE /* AppTheming */ = { + isa = XCSwiftPackageProductDependency; + productName = AppTheming; + }; + D4E050682CCB4A5D0028D1CE /* CoreComponents */ = { + isa = XCSwiftPackageProductDependency; + productName = CoreComponents; + }; + D4E0506A2CCB4A5D0028D1CE /* CoreUtility */ = { + isa = XCSwiftPackageProductDependency; + productName = CoreUtility; + }; + D4E0506C2CCB4A5D0028D1CE /* MobileTheming */ = { + isa = XCSwiftPackageProductDependency; + productName = MobileTheming; + }; + D4E0506E2CCB4A5D0028D1CE /* NetworkLayer */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkLayer; + }; DE2A6A3326A4199A001F1FF3 /* AppTheming */ = { isa = XCSwiftPackageProductDependency; productName = AppTheming; diff --git a/Example/MobileCoreExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/MobileCoreExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d2ede1c..dc4d8d0 100644 --- a/Example/MobileCoreExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/MobileCoreExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -2,12 +2,12 @@ "object": { "pins": [ { - "package": "MobileCore", - "repositoryURL": "https://github.com/ppraveentr/MobileCore.git", + "package": "MobileTheme", + "repositoryURL": "https://github.com/ppraveentr/MobileTheme.git", "state": { - "branch": "feature/SPM-Setup", - "revision": "272f5ac227e18869e191ca2474e7da74394c26a2", - "version": null + "branch": null, + "revision": "8e321347fc520ea04d1fa6e49a0bd5765cc4f49b", + "version": "1.2.0" } } ] diff --git a/Example/MobileCoreExample/AppDelegate.swift b/Example/MobileCoreExample/AppDelegate.swift index 9426304..557a1cd 100644 --- a/Example/MobileCoreExample/AppDelegate.swift +++ b/Example/MobileCoreExample/AppDelegate.swift @@ -13,7 +13,7 @@ import NetworkLayer import AppTheming #endif -@UIApplicationMain +@main class AppDelegate: UIResponder, UIApplicationDelegate { var kBundle = Bundle(for: AppDelegate.self) var window: UIWindow? diff --git a/Example/MobileCoreExample/Classes/ViewController.swift b/Example/MobileCoreExample/Classes/ViewController.swift index 46204e3..5e398c2 100644 --- a/Example/MobileCoreExample/Classes/ViewController.swift +++ b/Example/MobileCoreExample/Classes/ViewController.swift @@ -5,6 +5,10 @@ // Copyright © 2017 Praveen Prabhakar. All rights reserved. // +#if canImport(AppTheming) +import AppTheming +import CoreComponents +#endif import UIKit import WebKit diff --git a/Example/MobileCoreExample/Classes/Views/SegmentCollectionView.swift b/Example/MobileCoreExample/Classes/Views/SegmentCollectionView.swift index 7bfe766..4801b64 100644 --- a/Example/MobileCoreExample/Classes/Views/SegmentCollectionView.swift +++ b/Example/MobileCoreExample/Classes/Views/SegmentCollectionView.swift @@ -38,8 +38,8 @@ class SegmentCollectionView: UIView { ftLog("Segment Selected: \(segment)", self.segmentedControl?.titleForSegment(at: segment) ?? "") } - self.theme = ThemeStyle.defaultStyle - segmentedControl?.theme = ThemeStyle.defaultStyle +// self.theme = ThemeStyle.defaultStyle +// segmentedControl?.theme = ThemeStyle.defaultStyle if let segment = segmentedControl { self.pin(view: segment, edgeOffsets: UIEdgeInsets(10)) diff --git a/MobileCore.podspec b/MobileCore.podspec index ef1f057..94844cc 100644 --- a/MobileCore.podspec +++ b/MobileCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'MobileCore' - s.version = '0.1.4' + s.version = '1.0.0' s.summary = 'MobileCore framework.' s.homepage = 'https://github.com/ppraveentr/MobileCore' s.license = { :type => 'MIT', :file => 'LICENSE' } @@ -8,14 +8,14 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/ppraveentr/MobileCore.git', :tag => s.version.to_s } s.weak_framework = 'UIKit' - s.ios.deployment_target = '10.0' - s.swift_version = '5.0' - s.default_subspecs = 'CoreUtility', 'NetworkLayer', 'CoreComponents', 'AppTheming' + s.ios.deployment_target = '16.0' + s.swift_version = '6.0' + s.default_subspecs = 'CoreUtility', 'NetworkLayer', 'CoreComponents', 'AppTheming', 'MobileTheming' s.subspec 'CoreUtility' do |utility| utility.source_files = 'Sources/CoreUtility/**/*.{h,m,swift}' utility.header_dir = "CoreUtility" - utility.dependency 'SwiftKeychainWrapper' + # utility.dependency 'SwiftKeychainWrapper' end s.subspec 'NetworkLayer' do |network| @@ -35,5 +35,10 @@ Pod::Spec.new do |s| coreComponents.dependency 'MobileCore/CoreUtility' coreComponents.header_dir = "CoreComponents" end - + + s.subspec 'MobileTheming' do |theme| + theme.source_files = 'Sources/MobileTheming/**/*.{h,m,swift}' + theme.header_dir = "MobileTheming" + end + end diff --git a/Package.swift b/Package.swift index 29e6120..4da31c9 100644 --- a/Package.swift +++ b/Package.swift @@ -8,16 +8,18 @@ private let resources: [Resource] = [ .process("Resources") ] let package = Package( name: "MobileCore", - platforms: [.iOS(.v10)], + platforms: [.iOS(.v14)], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library(name: "CoreUtility", targets: ["CoreUtility"]), .library(name: "CoreComponents", targets: ["CoreComponents"]), .library(name: "AppTheming", targets: ["AppTheming"]), - .library(name: "NetworkLayer", targets: ["NetworkLayer"]) + .library(name: "NetworkLayer", targets: ["NetworkLayer"]), + .library(name: "MobileTheming", targets: ["MobileTheming"]) ], dependencies: [ // .package(url: /* package url */, from: "1.0.0"), + .package(name: "MobileTheme", url: "https://github.com/ppraveentr/MobileTheme.git", from: "1.2.0") ], targets: [ // MARK: CoreUtility @@ -34,7 +36,10 @@ let package = Package( // MARK: NetworkLayer .target(name: "NetworkLayer", dependencies: dependencies), - .testTarget(name: "NetworkLayerTests", dependencies: dependencies + ["NetworkLayer"], resources: resources) + .testTarget(name: "NetworkLayerTests", dependencies: dependencies + ["NetworkLayer"], resources: resources), + + // MARK: SwiftUI Theming + .target(name: "MobileTheming", dependencies: ["MobileTheme"]) ], swiftLanguageVersions: [.v5] ) diff --git a/Sources/AppTheming/AppearanceManager.swift b/Sources/AppTheming/AppearanceManager.swift index c24b801..85fb48b 100644 --- a/Sources/AppTheming/AppearanceManager.swift +++ b/Sources/AppTheming/AppearanceManager.swift @@ -30,10 +30,10 @@ public enum AppearanceManager { static func setupApplicationTheme() { guard let app = ThemesManager.getAppearance() as? ThemeModel else { return } for theme in app where ((theme.value as? ThemeModel) != nil) { - if let themeObj: ThemeModel = theme.value as? ThemeModel { + if let themeObj = theme.value as? ThemeModel { let components = getComponentName(theme.key) // Setup 'appearance' from Theme - if let classTy: AppearanceManagerProtocol = Reflection.swiftClassFromString(components.0) as? AppearanceManagerProtocol { + if let classTy = Reflection.swiftClassFromString(components.0) as? AppearanceManagerProtocol { var appearanceContainer: [UIAppearanceContainer.Type]? if let className = components.1, let objecClass = Reflection.swiftClassTypeFromString(className) { @@ -47,24 +47,27 @@ public enum AppearanceManager { } extension UIView: AppearanceManagerProtocol { - public func setUpAppearance(theme: ThemeModel, containerClass: [UIAppearanceContainer.Type]?) -> UIAppearance { + public func setUpAppearance(theme: ThemeModel, + containerClass: [UIAppearanceContainer.Type]?) -> UIAppearance { type(of: self).setUpAppearance(theme: theme, containerClass: containerClass) } - @discardableResult - @objc public class func setUpAppearance(theme: ThemeModel, containerClass: [UIAppearanceContainer.Type]?) -> UIAppearance { - let appearance = (containerClass == nil) ? self.appearance() : self.appearance(whenContainedInInstancesOf: containerClass!) - if let tintColor = theme[ThemeKey.tintColor.rawValue] { + @discardableResult @objc + public class func setUpAppearance(theme: ThemeModel, + containerClass: [UIAppearanceContainer.Type]?) -> UIAppearance { + let appearance = (containerClass == nil) ? + self.appearance() : self.appearance(whenContainedInInstancesOf: containerClass!) + if let tintColor = theme[ThemeType.Key.tintColor] { appearance.tintColor = ThemesManager.getColor(tintColor as? String) } - if let backgroundImage = theme[ThemeKey.backgroundImage] { + if let backgroundImage = theme[ThemeType.Key.backgroundImage] { self.setBackgroundImage(backgroundImage) } return appearance } @objc public class func setBackgroundImage(_ imageTheme: Any) { - self.setBackgroundImage(imageType: ThemeStyle.defaultStyle, imageName: imageTheme) + self.setBackgroundImage(imageType: ThemeStyle.defaultStyle.rawValue, imageName: imageTheme) } public class func setBackgroundImage(imageType: String?, imageName: Any) { @@ -81,17 +84,12 @@ extension UIView: AppearanceManagerProtocol { } extension UISegmentedControl { - override public class func setUpAppearance(theme: ThemeModel, containerClass: [UIAppearanceContainer.Type]?) -> UIAppearance { - super.setUpAppearance(theme: theme, containerClass: containerClass) -// let appearance = (containerClass == nil) ? self.appearance() : self.appearance(whenContainedInInstancesOf: containerClass!) -// return appearance - } public class func setBackgroundImage(imageType: String, image: UIImage) { let image = image.withRenderingMode(.alwaysTemplate) .resizableImage(withCapInsets: UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)) let appearance = UISegmentedControl.appearance() - if imageType == ThemeStyle.selectedStyle { + if imageType == ThemeStyle.selectedStyle.rawValue { appearance.setBackgroundImage(image, for: .selected, barMetrics: .default) } else { @@ -102,11 +100,13 @@ extension UISegmentedControl { extension UINavigationBar { // swiftlint:disable cyclomatic_complexity - override public class func setUpAppearance(theme: ThemeModel, containerClass: [UIAppearanceContainer.Type]?) -> UIAppearance { + override public class func setUpAppearance( + theme: ThemeModel, containerClass: [UIAppearanceContainer.Type]?) -> UIAppearance { super.setUpAppearance(theme: theme, containerClass: containerClass) - let appearance = (containerClass == nil) ? self.appearance() : self.appearance(whenContainedInInstancesOf: containerClass!) + let appearance = (containerClass == nil) ? + self.appearance() : self.appearance(whenContainedInInstancesOf: containerClass!) - for type in ThemeKey.allCases { + for type in ThemeType.Key.allCases { guard let value = theme[type.rawValue] else { continue } switch type { case .tintColor: @@ -120,37 +120,32 @@ extension UINavigationBar { case .isTranslucent: if let value = value as? Bool { appearance.isTranslucent = value - // FIXIT: Not able to update statusbar color if not set to `blackTranslucent` - if value { - appearance.barStyle = .blackTranslucent - } } case .titleText: if let attributes = ThemesManager.getTextAttributes(value as? ThemeModel) { appearance.titleTextAttributes = attributes - if #available(iOS 13.0, *) { - appearance.largeTitleTextAttributes = attributes - } + appearance.largeTitleTextAttributes = attributes } default: break } } - if #available(iOS 13.0, *) { - let navAppe = navBarAppearance(theme: theme, tile: appearance.titleTextAttributes, largeTitle: appearance.largeTitleTextAttributes) - appearance.standardAppearance = navAppe - appearance.compactAppearance = navAppe - appearance.scrollEdgeAppearance = navAppe - } + let navAppe = navBarAppearance(theme: theme, tile: appearance.titleTextAttributes, + largeTitle: appearance.largeTitleTextAttributes) + appearance.standardAppearance = navAppe + appearance.compactAppearance = navAppe + appearance.scrollEdgeAppearance = navAppe return appearance } @available(iOS 13.0, *) - private class func navBarAppearance(theme: ThemeModel, tile: AttributedDictionary?, largeTitle: AttributedDictionary?) -> UINavigationBarAppearance { + private class func navBarAppearance(theme: ThemeModel, tile: AttributedDictionary?, + largeTitle: AttributedDictionary?) -> UINavigationBarAppearance { let navAppe = UINavigationBarAppearance() - if let imageTheme = theme[ThemeKey.backgroundImage] as? ThemeModel, - let defaultImage = ThemesManager.getImage(imageTheme[ThemeKey.defaultValue]), let bgColor = defaultImage.getColor() { + if let imageTheme = theme[ThemeType.Key.backgroundImage] as? ThemeModel, + let defaultImage = ThemesManager.getImage(imageTheme[ThemeType.Key.defaultValue]), + let bgColor = defaultImage.getColor() { navAppe.backgroundColor = bgColor } if let attributes = tile { @@ -169,12 +164,13 @@ extension UINavigationBar { var landScapeImage: UIImage? if let imageTheme = image as? ThemeModel { - defaultImage = ThemesManager.getImage(imageTheme[ThemeKey.defaultValue]) + defaultImage = ThemesManager.getImage(imageTheme[ThemeType.Key.defaultValue]) landScapeImage = ThemesManager.getImage(imageTheme["landScape"]) } if defaultImage != nil { - self.applyBackgroundImage(navigationBar: nil, defaultImage: defaultImage!, landScapeImage: landScapeImage) + self.applyBackgroundImage(navigationBar: nil, defaultImage: defaultImage!, + landScapeImage: landScapeImage) } } } diff --git a/Sources/AppTheming/ThemeComponents/UIButton+Theme.swift b/Sources/AppTheming/ThemeComponents/UIButton+Theme.swift index e7d7372..8cd1949 100644 --- a/Sources/AppTheming/ThemeComponents/UIButton+Theme.swift +++ b/Sources/AppTheming/ThemeComponents/UIButton+Theme.swift @@ -11,7 +11,7 @@ import UIKit extension UIButton: ControlThemeProtocol { // check view state, to update style - open func subStyleName() -> String? { + public func subStyleName() -> ThemeStyle? { if self.isEnabled { return nil } @@ -26,35 +26,35 @@ extension UIButton: ControlThemeProtocol { } // For custome key:value pairs - open func update(themeDic: ThemeModel, state: UIControl.State) { + public func update(themeDic: ThemeModel, state: UIControl.State) { let text = self.title(for: state) ?? "" let range = NSRange(location: 0, length: text.count) let attribute = NSMutableAttributedString(string: text) - if let color = ThemesManager.getColor(themeDic[ThemeKey.textcolor] as? String) { + if let color = ThemesManager.getColor(themeDic[ThemeType.Key.textcolor] as? String) { self.setTitleColor(color, for: state) // For attributed title attribute.addAttribute(.foregroundColor, value: color, range: range) } - if let text = themeDic[ThemeKey.textfont] as? String, + if let text = themeDic[ThemeType.Key.textfont] as? String, let font = ThemesManager.getFont(text) { self.titleLabel?.font = font // For attributed title attribute.addAttribute(.font, value: font, range: range) } - if let underline = themeDic[ThemeKey.underline] as? ThemeModel { - if let color = ThemesManager.getColor(underline[ThemesType.color] as? String) { + if let underline = themeDic[ThemeType.Key.underline] as? ThemeModel { + if let color = ThemesManager.getColor(underline[ThemeType.color] as? String) { attribute.addAttribute(.underlineColor, value: color, range: range) } - if let intValue = underline[ThemeKey.style] as? Int { + if let intValue = underline[ThemeType.Key.style] as? Int { let style = NSUnderlineStyle(rawValue: intValue).rawValue attribute.addAttribute(.underlineStyle, value: style, range: range) } } - if let image = ThemesManager.getImage(themeDic[ThemeKey.image]) { + if let image = ThemesManager.getImage(themeDic[ThemeType.Key.image]) { self.setImage(image, for: state) } diff --git a/Sources/AppTheming/ThemeComponents/UILabel+Theme.swift b/Sources/AppTheming/ThemeComponents/UILabel+Theme.swift index d107d1c..7c0cfc6 100644 --- a/Sources/AppTheming/ThemeComponents/UILabel+Theme.swift +++ b/Sources/AppTheming/ThemeComponents/UILabel+Theme.swift @@ -17,7 +17,7 @@ public protocol LabelThemeProtocol: ThemeProtocol { extension UILabel: ThemeProtocol { // If view is disabled, check for ".disabledStyle" style - public func subStyleName() -> String? { + public func subStyleName() -> ThemeStyle? { self.isEnabled ? nil : ThemeStyle.disabledStyle } @@ -25,15 +25,15 @@ extension UILabel: ThemeProtocol { public func updateTheme(_ theme: ThemeModel) { for (kind, value) in theme { switch kind { - case ThemeKey.isLinkUnderlineEnabled.rawValue: + case ThemeType.Key.isLinkUnderlineEnabled.rawValue: (self as? LabelThemeProtocol)?.isLinkUnderLineEnabled = value as? Bool ?? false - case ThemeKey.isLinkDetectionEnabled.rawValue: + case ThemeType.Key.isLinkDetectionEnabled.rawValue: (self as? LabelThemeProtocol)?.islinkDetectionEnabled = value as? Bool ?? false - case ThemeKey.textfont.rawValue: + case ThemeType.Key.textfont.rawValue: if let font = getFont(value as? String) { self.font = font } - case ThemeKey.textcolor.rawValue: + case ThemeType.Key.textcolor.rawValue: if let color = getColor(value as? String) { self.textColor = color } diff --git a/Sources/AppTheming/ThemeComponents/UINavigationBar+Theme.swift b/Sources/AppTheming/ThemeComponents/UINavigationBar+Theme.swift index 14eacc9..2afdddd 100644 --- a/Sources/AppTheming/ThemeComponents/UINavigationBar+Theme.swift +++ b/Sources/AppTheming/ThemeComponents/UINavigationBar+Theme.swift @@ -6,6 +6,7 @@ // Copyright © 2017 Praveen Prabhakar. All rights reserved. // +import CoreUtility import Foundation import UIKit @@ -105,5 +106,6 @@ public extension UINavigationBar { UINavigationBar.appearance(whenContainedInInstancesOf: [UINavigationController.self]) navigationBar.barTintColor = color + navigationBar.tintColor = color } } diff --git a/Sources/AppTheming/ThemeComponents/UISearchBar+Theme.swift b/Sources/AppTheming/ThemeComponents/UISearchBar+Theme.swift index a022750..d062d06 100644 --- a/Sources/AppTheming/ThemeComponents/UISearchBar+Theme.swift +++ b/Sources/AppTheming/ThemeComponents/UISearchBar+Theme.swift @@ -14,8 +14,8 @@ import UIKit // MARK: AssociatedKey private extension AssociatedKey { - static var searchUITextField = "searchUITextField" - static var searchBarAttributes = "searchBarAttributes" + static var searchUITextField = Int8(0) // "searchUITextField" + static var searchBarAttributes = Int8(1) // "searchBarAttributes" } // MARK: UISearchBar @@ -30,17 +30,17 @@ extension UISearchBar: ThemeProtocol { for (kind, value) in theme { switch kind { - case ThemeKey.barTintColor.rawValue: + case ThemeType.Key.barTintColor.rawValue: if let barTintColor = getColor(value as? String) { self.barTintColor = barTintColor self.backgroundImage = UIImage() self.backgroundColor = .clear } - case ThemeKey.tintColor.rawValue: + case ThemeType.Key.tintColor.rawValue: tintColor = getColor(value as? String) - case ThemeKey.textcolor.rawValue: + case ThemeType.Key.textcolor.rawValue: textcolor = getColor(value as? String) - case ThemeKey.textfont.rawValue: + case ThemeType.Key.textfont.rawValue: font = getFont(value as? String) default: break diff --git a/Sources/AppTheming/ThemeComponents/UISegmentedControl+Theme.swift b/Sources/AppTheming/ThemeComponents/UISegmentedControl+Theme.swift index 7eeb12d..2582b40 100644 --- a/Sources/AppTheming/ThemeComponents/UISegmentedControl+Theme.swift +++ b/Sources/AppTheming/ThemeComponents/UISegmentedControl+Theme.swift @@ -10,21 +10,21 @@ import UIKit extension UISegmentedControl: ControlThemeProtocol { // check view state, to update style - open func subStyleName() -> String? { nil } + public func subStyleName() -> ThemeStyle? { nil } public func update(themeDic: ThemeModel, state: UIControl.State) { - if let text = themeDic[ThemeKey.tintColor] as? String, let color: UIColor = ThemesManager.getColor(text) { + if let text = themeDic[ThemeType.Key.tintColor] as? String, let color: UIColor = ThemesManager.getColor(text) { self.tintColor = color } if #available(iOS 13, *), (themeDic["iOS12Style"] as? Bool) ?? false { // Update with iOS 12 style var textFont: UIFont = .systemFont(ofSize: 13, weight: .regular) - if let text = themeDic[ThemeKey.textfont] as? String, let font: UIFont = ThemesManager.getFont(text) { + if let text = themeDic[ThemeType.Key.textfont] as? String, let font: UIFont = ThemesManager.getFont(text) { textFont = font } var textcolor: UIColor = .white - if let text = themeDic[ThemeKey.textcolor] as? String, let color: UIColor = ThemesManager.getColor(text) { + if let text = themeDic[ThemeType.Key.textcolor] as? String, let color: UIColor = ThemesManager.getColor(text) { textcolor = color } ensureiOS12Style(font: textFont, textColor: textcolor) diff --git a/Sources/AppTheming/ThemeComponents/UIView+Themes.swift b/Sources/AppTheming/ThemeComponents/UIView+Themes.swift index 442d782..cd27a08 100644 --- a/Sources/AppTheming/ThemeComponents/UIView+Themes.swift +++ b/Sources/AppTheming/ThemeComponents/UIView+Themes.swift @@ -7,68 +7,18 @@ // #if canImport(CoreUtility) +import CoreComponents import CoreUtility #endif import Foundation import UIKit -public typealias ThemeModel = [String: Any] - -extension ThemeModel { - subscript(key: ThemeKey) -> Any? { - get { self[key.rawValue] } - set { self[key.rawValue] = newValue } - } - - subscript(theme: ThemesType) -> Any? { - get { self[theme.rawValue] } - set { self[theme.rawValue] = newValue } - } -} - -public enum ThemesType: String, CaseIterable { - case color, font, layer, appearance, link, components -} - -public enum ThemeKey: String, CaseIterable { - // Theme Super Components - case defaultValue = "default", superComponent = "_super" - // Image - case clear, image, backgroundImage, backIndicatorImage, backIndicatorTransitionMaskImage, shadowImage - // Appearanc - case titleText, isTranslucent - case tintColor, barTintColor, backgroundColor, foregroundColor - // Font - case system, boldSystem, italicSystem - // Label - case font, name, size, weight, textfont, textcolor, underline, style - case isLinkUnderlineEnabled, isLinkDetectionEnabled - // Layer - case masksToBounds, cornerRadius, borderWidth, borderColor - case shadowPath, shadowOffset, shadowColor, shadowRadius, shadowOpacity -} - -public extension NSNotification.Name { - static let kAppearanceWillRefreshWindow = NSNotification.Name(rawValue: "kAppearance.willRefreshWindow.Notofication") - static let kAppearanceDidRefreshWindow = NSNotification.Name(rawValue: "kAppearance.didRefreshWindow.Notofication") -} - -public struct ThemeStyle { - public static let defaultStyle = "default" - public static let highlightedStyle = "highlighted" - public static let selectedStyle = "selected" - public static let disabledStyle = "disabled" - - public static func allStyles() -> [String] { - [ - ThemeStyle.highlightedStyle, - ThemeStyle.selectedStyle, - ThemeStyle.disabledStyle - ] - } +// MARK: AssociatedKey +private extension AssociatedKey { + static var gradientLayer = Int8(0) // "gradientLayer" } -extension UIView { +extension UIView: ShadowPathProtocol { // Theme style-name for the view @IBInspectable public var theme: String? { @@ -84,11 +34,16 @@ extension UIView { self.needsThemesUpdate = true } + @objc public func updateShadowPathIfNeeded() { if self.layer.shadowPath != nil { let rect = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height) self.layer.shadowPath = UIBezierPath(rect: rect).cgPath } + + if let gardian: CAGradientLayer = AssociatedObject.getAssociated(self, key: &AssociatedKey.gradientLayer) { + gardian.frame = self.bounds + } } // To tigger view-Theme styling @@ -125,7 +80,7 @@ fileprivate extension UIView { // Checkout if view supports Theming protocol let delegate: ThemeProtocol? = self as? ThemeProtocol // Get Theme property of view based on its state - if let themeDic = ThemesManager.generateVisualThemes(forClass: className, styleName: themeName, subStyleName: delegate?.subStyleName()) { + if let themeDic = ThemesManager.generateVisualThemes(className, styleName: themeName, subStyleName: delegate?.subStyleName()) { // Step 1. Config view with new Theme-style self.configureTheme(themeDic) } @@ -139,9 +94,9 @@ fileprivate extension UIView { // For each style, get Theme value ThemeStyle.allStyles().forEach { style in if let baseName = baseName, - let styleThemeDic = ThemesManager.generateVisualThemes(forClass: className, styleName: baseName, subStyleName: style) { + let styleThemeDic = ThemesManager.generateVisualThemes(className, styleName: baseName, subStyleName: style) { // Create ThemeModel as, ['ThemeStyle.UIControlState' : 'ActualTheme for the state'] - styles[style] = styleThemeDic + styles[style.rawValue] = styleThemeDic } } // Setup visual component for each style @@ -152,9 +107,7 @@ fileprivate extension UIView { // Retruns ('classname', 'Theme-style-name') only if both are valid func getThemeName() -> (String, String)? { // Vadidate className and ThemeName - guard - let className = Reflection.classNameAsString(self), - let themeName = self.theme else { + guard let className = Reflection.classNameAsString(self), let themeName = self.theme else { return nil } @@ -164,7 +117,6 @@ fileprivate extension UIView { if let className = Reflection.classNameAsString(superClass), className.hasPrefix("UI") { return nil } - return superClass } @@ -174,7 +126,7 @@ fileprivate extension UIView { let superClass: AnyClass? = getSuperClass(type(of: self)) // If SuperClass becomes invalid, terminate loop if let superClass = superClass, !UIView.kTerminalBaseClass.contains(where: { $0 == superclass }) { - baseClassName = Reflection.classNameAsString(superClass) + baseClassName = Reflection.classNameAsString(superClass) } else { break @@ -196,21 +148,37 @@ fileprivate extension UIView { // Only needed for UIControl types, Eg. Button guard let self = self as? ControlThemeProtocol else { return } // Get all subTheme for all stats of the control - let themeDic = [self.subStyleName() ?? ThemeStyle.defaultStyle: theme] + let themeStyleName = self.subStyleName() ?? ThemeStyle.defaultStyle + let themeDic = [themeStyleName.rawValue: theme] self.setThemes(themeDic) } } // MARK: UIView: ThemeProtocol extension UIView { + /// Updates View's based on theme + /// 1) backgroundColor, + /// 2) Adds CAGradientLayer, + /// 3) Setup CALayer for shadowLayer + /// 4) Custom config the component based on ThemeProtocol func setupViewTheme(_ theme: ThemeModel) { // "backgroundColor" - if let colorName = theme[ThemeKey.backgroundColor], + if let colorName = theme[ThemeType.Key.backgroundColor], let color = ThemesManager.getColor(colorName as? String) { self.updateBackgroundColor(color) } - // "layer": To generate a layer and add it as subView - if let layerName = theme[ThemesType.layer] as? String, + /// 2) Adds CAGradientLayer, + if let model = theme[ThemeType.Key.gradientLayer] as? ThemeModel, + let layer = ThemesManager.getGradientLayer(model) { + // Remove any old layer + let oldGradian = AssociatedObject.getAssociated(self, key: &AssociatedKey.gradientLayer) + oldGradian?.removeFromSuperlayer() + // Set new layer + AssociatedObject.setAssociated(self, value: layer, key: &AssociatedKey.gradientLayer) + self.layer.insertSublayer(layer, at: 0) + } + /// Setup CALayer: Generate a layer and add it as subView + if let layerName = theme[ThemeType.layer] as? String, let layerValue = ThemesManager.getLayer(layerName) { ThemesManager.getBackgroundLayer(layerValue, toLayer: self.layer) } @@ -232,8 +200,8 @@ extension UIControl { public func setThemes(_ themes: ThemeModel) { guard let themeSelf = self as? ControlThemeProtocol else { return } for (kind, value) in themes { - guard let theme = value as? ThemeModel else { continue } - switch kind { + guard let theme = value as? ThemeModel, let style = ThemeStyle(rawValue: kind) else { continue } + switch style { case ThemeStyle.defaultStyle: themeSelf.update(themeDic: theme, state: .normal) case ThemeStyle.disabledStyle: @@ -242,35 +210,7 @@ extension UIControl { themeSelf.update(themeDic: theme, state: .highlighted) case ThemeStyle.selectedStyle: themeSelf.update(themeDic: theme, state: .selected) - default: - break } } } } - -// MARK: Window Refresh -public extension UIWindow { - @nonobjc private func refreshAppearance() { - let constraints = self.constraints - removeConstraints(constraints) - for subview in subviews { - subview.removeFromSuperview() - addSubview(subview) - } - addConstraints(constraints) - } - - /// Refreshes appearance for the window - /// - Parameter animated: if the refresh should be animated - func refreshAppearance(animated: Bool) { - NotificationCenter.default.post(name: .kAppearanceWillRefreshWindow, object: self) - UIView.animate( - withDuration: animated ? 0.25 : 0, - animations: { self.refreshAppearance() }, - completion: { _ in - NotificationCenter.default.post(name: .kAppearanceDidRefreshWindow, object: self) - } - ) - } -} diff --git a/Sources/AppTheming/ThemeComponents/UIWindow+Theme.swift b/Sources/AppTheming/ThemeComponents/UIWindow+Theme.swift new file mode 100644 index 0000000..ce21d75 --- /dev/null +++ b/Sources/AppTheming/ThemeComponents/UIWindow+Theme.swift @@ -0,0 +1,43 @@ +// +// UIWindow+Theme.swift +// MobileCore +// +// Created by Praveen Prabhakar on 23/07/21. +// + +#if canImport(CoreUtility) +import CoreUtility +#endif +import Foundation +import UIKit + +public extension NSNotification.Name { + static let kAppearanceWillRefreshWindow = NSNotification.Name(rawValue: "kAppearance.willRefreshWindow.Notofication") + static let kAppearanceDidRefreshWindow = NSNotification.Name(rawValue: "kAppearance.didRefreshWindow.Notofication") +} + +// MARK: Window Refresh +public extension UIWindow { + @nonobjc private func refreshAppearance() { + let constraints = self.constraints + removeConstraints(constraints) + for subview in subviews { + subview.removeFromSuperview() + addSubview(subview) + } + addConstraints(constraints) + } + + /// Refreshes appearance for the window + /// - Parameter animated: if the refresh should be animated + func refreshAppearance(animated: Bool) { + NotificationCenter.default.post(name: .kAppearanceWillRefreshWindow, object: self) + UIView.animate( + withDuration: animated ? 0.25 : 0, + animations: { self.refreshAppearance() }, + completion: { _ in + NotificationCenter.default.post(name: .kAppearanceDidRefreshWindow, object: self) + } + ) + } +} diff --git a/Sources/AppTheming/ThemeProtocol.swift b/Sources/AppTheming/ThemeProtocol.swift index 0177ed9..9a19f39 100644 --- a/Sources/AppTheming/ThemeProtocol.swift +++ b/Sources/AppTheming/ThemeProtocol.swift @@ -15,7 +15,7 @@ public protocol ThemeProtocol: AnyObject { // Retruns 'ThemeStyle' specific to current state of object. // Say if UIView is disabled, retrun "disabled", which can be clubed with main Theme style. // Eg, if currentTheme is 'viewB', then when disabled state, theme willbe : 'viewB:disabled' - func subStyleName() -> String? + func subStyleName() -> ThemeStyle? // Custom Subclass can implement, to config Custom component func updateTheme(_ theme: ThemeModel) @@ -25,7 +25,7 @@ public protocol ThemeProtocol: AnyObject { public extension ThemeProtocol where Self: UIView { // If view is disabled, check for ".disabledStyle" style - func subStyleName() -> String? { + func subStyleName() -> ThemeStyle? { self.isUserInteractionEnabled ? nil : ThemeStyle.disabledStyle } diff --git a/Sources/AppTheming/ThemesManager.swift b/Sources/AppTheming/ThemesManager.swift index a95ac2f..02e2f2b 100644 --- a/Sources/AppTheming/ThemesManager.swift +++ b/Sources/AppTheming/ThemesManager.swift @@ -12,6 +12,42 @@ import CoreUtility import Foundation import UIKit +public typealias ThemeModel = [String: Any] + +public enum ThemeType: String, CaseIterable { + case color, font, layer, appearance, link, components + + public enum Key: String, CaseIterable { + // Theme Super Components + case defaultValue = "default", superComponent = "_super" + // Image + case clear, image, backgroundImage, backIndicatorImage, backIndicatorTransitionMaskImage, shadowImage + // Appearanc + case titleText, isTranslucent + case tintColor, barTintColor, backgroundColor, foregroundColor + case gradientLayer, colors, locations + // Font + case system, boldSystem, italicSystem + // Label + case font, name, size, weight, textfont, textcolor, underline, style + case isLinkUnderlineEnabled, isLinkDetectionEnabled + // Layer + case masksToBounds, cornerRadius, borderWidth, borderColor + case shadowPath, shadowOffset, shadowColor, shadowRadius, shadowOpacity + } +} + +public enum ThemeStyle: String, CaseIterable { + case defaultStyle = "default" + case highlightedStyle = "highlighted" + case selectedStyle = "selected" + case disabledStyle = "disabled" + + public static func allStyles() -> [ThemeStyle] { + [ .highlightedStyle, .selectedStyle, .disabledStyle ] + } +} + open class ThemesManager { static var imageSourceBundle: [Bundle] = [] // Theme JSON file loaded from Main-App @@ -49,11 +85,11 @@ open class ThemesManager { } // MARK: Theme components - public static func generateVisualThemes(forClass name: String, styleName: String, subStyleName subStyle: String? = nil) -> ThemeModel? { + public static func generateVisualThemes(_ name: String, styleName: String, subStyleName subStyle: ThemeStyle? = nil) -> ThemeModel? { var styleName = styleName // If any subTheme is avaiable, say when button is Highlighted, or view is disabled if let subStyle = subStyle, !styleName.contains(":") { - styleName += ":" + subStyle + styleName += ":" + subStyle.rawValue } // Get theme component guard let currentTheme: ThemeModel = ThemesManager.getViewComponent(name, styleName: styleName) else { @@ -75,11 +111,10 @@ open class ThemesManager { } // MARK: UIColor - // TODO: gradian, rgb, alpha, ... public static func getColor(_ colorName: String?) -> UIColor? { guard let colorName = colorName else { return nil } // Check if its image coded string - if (colorName.hasPrefix("@")), let image = ThemesManager.getImage(colorName) { + if colorName.hasPrefix("@"), let image = ThemesManager.getImage(colorName) { return image.getColor() } // Get hex color @@ -91,31 +126,30 @@ open class ThemesManager { if let hexColor = UIColor.hexColor(color) { return hexColor } - if color == ThemeKey.clear.rawValue { + if color == ThemeType.Key.clear.rawValue { return UIColor.clear } return nil } // MARK: UIFont - // TODO: bold, thin, ... public static func getFont(_ fontName: String?) -> UIFont? { let font: ThemeModel = ThemesManager.getDefaults(type: .font, keyName: fontName) as? ThemeModel ?? [:] - if let name: String = font[ThemeKey.name] as? String, - let sizeValue: String = font[ThemeKey.size] as? String, + if let name: String = font[ThemeType.Key.name] as? String, + let sizeValue: String = font[ThemeType.Key.size] as? String, let size = NumberFormatter().number(from: sizeValue) { // Size Value let sizeValue = CGFloat(truncating: size) var weight: UIFont.Weight = .regular - if let value = font[ThemeKey.weight] as? CGFloat { + if let value = font[ThemeType.Key.weight] as? CGFloat { weight = UIFont.Weight(value) } switch name { - case ThemeKey.system.rawValue: + case ThemeType.Key.system.rawValue: return UIFont.systemFont(ofSize: sizeValue, weight: weight) - case ThemeKey.boldSystem.rawValue: + case ThemeType.Key.boldSystem.rawValue: return UIFont.boldSystemFont(ofSize: sizeValue) - case ThemeKey.italicSystem.rawValue: + case ThemeType.Key.italicSystem.rawValue: return UIFont.italicSystemFont(ofSize: sizeValue) default: return UIFont(name: name, size: sizeValue) @@ -145,17 +179,46 @@ open class ThemesManager { public static func getTextAttributes(_ theme: ThemeModel?) -> AttributedDictionary? { guard let theme = theme else { return nil } var attributes = AttributedDictionary() - if let value = theme[ThemeKey.foregroundColor] as? String { + if let value = theme[ThemeType.Key.foregroundColor] as? String { attributes[.foregroundColor] = self.getColor(value) } return attributes } + + // MARK: CAGradientLayer + public static func getGradientLayer(_ layer: ThemeModel?) -> CAGradientLayer? { + guard let layer = layer else { return nil } + let gradient = CAGradientLayer() + for (name, value) in layer { + switch name { + case ThemeType.Key.colors.rawValue: + if let colors = value as? [String] { + gradient.colors = colors.compactMap { getColor($0)?.cgColor } + } + case ThemeType.Key.locations.rawValue: + if let locations = value as? [Float] { + gradient.locations = locations.compactMap { NSNumber(value: $0) } + } + default: + break + } + } + return gradient + } // MARK: CALayer public static func getLayer(_ layerName: String? = nil) -> ThemeModel? { ThemesManager.getDefaults(type: .layer, keyName: layerName) as? ThemeModel ?? [:] } + // MARK: CGFloat value + private static let floatValue = { (value: Any) -> CGFloat in + if let i = value as? Float { + return CGFloat(i) + } + return 0.0 + } + // MARK: Size public static func getSize(_ value: Any? = nil) -> CGSize { guard var value = value as? String else { return .zero } @@ -171,36 +234,28 @@ open class ThemesManager { @discardableResult public static func getBackgroundLayer(_ layer: ThemeModel?, toLayer: CALayer? = nil) -> CALayer? { guard let layer = layer else { return nil } - - let floatValue = { (value: Any) -> CGFloat in - if let i = value as? Float { - return CGFloat(i) - } - return 0.0 - } - let caLayer = toLayer ?? CALayer() for (name, value) in layer { switch name { - case ThemeKey.cornerRadius.rawValue: + case ThemeType.Key.cornerRadius.rawValue: caLayer.cornerRadius = floatValue(value) - case ThemeKey.borderWidth.rawValue: + case ThemeType.Key.borderWidth.rawValue: caLayer.borderWidth = floatValue(value) - case ThemeKey.masksToBounds.rawValue: + case ThemeType.Key.masksToBounds.rawValue: caLayer.masksToBounds = (value as? Bool) ?? false - case ThemeKey.borderColor.rawValue: + case ThemeType.Key.borderColor.rawValue: caLayer.borderColor = ThemesManager.getColor(value as? String)?.cgColor - case ThemeKey.shadowOffset.rawValue: + case ThemeType.Key.shadowOffset.rawValue: caLayer.shadowOffset = ThemesManager.getSize(value) - case ThemeKey.shadowPath.rawValue: + case ThemeType.Key.shadowPath.rawValue: caLayer.shadowOffset = .zero let rect = CGRect(x: 0, y: 0, width: caLayer.bounds.width, height: caLayer.bounds.height) caLayer.shadowPath = UIBezierPath(rect: rect).cgPath - case ThemeKey.shadowColor.rawValue: + case ThemeType.Key.shadowColor.rawValue: caLayer.shadowColor = ThemesManager.getColor(value as? String)?.cgColor - case ThemeKey.shadowRadius.rawValue: + case ThemeType.Key.shadowRadius.rawValue: caLayer.shadowRadius = floatValue(value) - case ThemeKey.shadowOpacity.rawValue: + case ThemeType.Key.shadowOpacity.rawValue: if let value = value as? CGFloat { caLayer.shadowOpacity = Float(value) } @@ -218,18 +273,34 @@ open class ThemesManager { } } +extension ThemeModel { + subscript(key: ThemeType.Key) -> Any? { + get { self[key.rawValue] } + set { self[key.rawValue] = newValue } + } + + subscript(theme: ThemeType) -> Any? { + get { self[theme.rawValue] } + set { self[theme.rawValue] = newValue } + } +} + extension ThemesManager { - // MARK: Component - fileprivate class var themeComponent: ThemeModel? { - ThemesManager.themesJSON[ThemesType.components] as? ThemeModel + static subscript(key: ThemeType) -> ThemeModel? { + ThemesManager.themesJSON[key] as? ThemeModel } + + // MARK: Component + fileprivate class var themeComponent: ThemeModel? { ThemesManager[ThemeType.components] } + // Component - validity fileprivate static func isThemeComponentValid(_ component: String) -> Bool { // Get all the components of spefic type - guard self.themeComponent?[component] as? ThemeModel != nil else { return false } + guard self.themeComponent?[component] is ThemeModel else { return false } return true } + // Component - fileprivate static func getThemeComponent(_ component: String, styleName: String? = nil) -> ThemeModel? { // TODO: Merge all sub-styles into single JSON, for easy parsing. // Get all the components of spefic type @@ -242,7 +313,7 @@ extension ThemesManager { } // MARK: Color - fileprivate class var themeColor: ThemeModel? { ThemesManager.themesJSON[ThemesType.color] as? ThemeModel } + fileprivate class var themeColor: ThemeModel? { ThemesManager[ThemeType.color] } // Color - fileprivate static func themeColor(_ colorName: String) -> String? { @@ -250,9 +321,7 @@ extension ThemesManager { } // MARK: font - fileprivate class var themeFont: ThemeModel? { - ThemesManager.themesJSON[ThemesType.font] as? ThemeModel - } + fileprivate class var themeFont: ThemeModel? { ThemesManager[ThemeType.font] } // font - fileprivate static func themeFont(_ fontName: String) -> ThemeModel? { @@ -260,19 +329,7 @@ extension ThemesManager { } // MARK: Appearance - fileprivate class var themeAppearance: ThemeModel? { - ThemesManager.themesJSON[ThemesType.appearance] as? ThemeModel - } - - // MARK: Layer - fileprivate class var themeLayer: ThemeModel? { - ThemesManager.themesJSON[ThemesType.layer] as? ThemeModel - } - - // layer - - fileprivate static func themeLayer(_ layerName: String) -> ThemeModel? { - self.themeLayer?[layerName] as? ThemeModel - } + fileprivate class var themeAppearance: ThemeModel? { ThemesManager[ThemeType.appearance] } // Appearance - fileprivate static func themeAppearance(_ appearanceName: String? = nil) -> Any? { @@ -286,6 +343,14 @@ extension ThemesManager { return themeAppearance?.filter { $0.0.hasPrefix(appearanceName) } } + // MARK: Layer + fileprivate class var themeLayer: ThemeModel? { ThemesManager[ThemeType.layer] } + + // layer - + fileprivate static func themeLayer(_ layerName: String) -> ThemeModel? { + self.themeLayer?[layerName] as? ThemeModel + } + // Get updated ThemeModel based on device's Version /* "UISegmentedControl": { @@ -310,7 +375,7 @@ extension ThemesManager { data.removeValue(forKey: arg.key) let dataVersion = NSString(string: key).floatValue if dataVersion <= deviceVersion, let val = arg.value as? ThemeModel, - (baseModel == nil || (baseModel!.1 < dataVersion)) { + baseModel == nil || (baseModel!.1 < dataVersion) { baseModel = (val, dataVersion) } } @@ -322,7 +387,7 @@ extension ThemesManager { } // Defaults - fileprivate static func getDefaults(type: ThemesType, keyName: String? = nil, styleName: String? = nil) -> Any? { + fileprivate static func getDefaults(type: ThemeType, keyName: String? = nil, styleName: String? = nil) -> Any? { guard let key = keyName else { return nil } var superBlock: ((String) -> Any?)? switch type { @@ -332,11 +397,11 @@ extension ThemesManager { // TODO: iterative 'super' is still pending if let viewComponent = actualComponents, - let superType = viewComponent[ThemeKey.superComponent] as? String, + let superType = viewComponent[ThemeType.Key.superComponent] as? String, var superCom = getThemeComponent(key, styleName: superType) { // If view-component has super's style, use it as base component and merge its own style superCom += viewComponent - superCom.removeValue(forKey: ThemeKey.superComponent.rawValue) + superCom.removeValue(forKey: ThemeType.Key.superComponent.rawValue) // Merged result return getOSVersion(model: superCom) @@ -359,11 +424,11 @@ extension ThemesManager { var actualComponents: Any? // If component of specifc type is not found, search for "default" style - let components: Any? = superBlock?(key) ?? superBlock?(ThemeStyle.defaultStyle) + let components: Any? = superBlock?(key) ?? superBlock?(ThemeStyle.defaultStyle.rawValue) // TODO: iterative 'super' is still pending if let currentComponent = components as? ThemeModel, - let superType = currentComponent[ThemeKey.superComponent] as? String, + let superType = currentComponent[ThemeType.Key.superComponent] as? String, let superComponents = superBlock?(superType) as? ThemeModel { // Merge super's style with current theme diff --git a/Sources/CoreComponents/Contollers/CollectionViewControllerProtocol.swift b/Sources/CoreComponents/Contollers/CollectionViewControllerProtocol.swift index a767474..d1eae73 100644 --- a/Sources/CoreComponents/Contollers/CollectionViewControllerProtocol.swift +++ b/Sources/CoreComponents/Contollers/CollectionViewControllerProtocol.swift @@ -12,7 +12,11 @@ import CoreUtility import Foundation import UIKit -private var kAOCollectionVC = "k.FT.AO.CollectionViewController" +// MARK: AssociatedKey + +private extension AssociatedKey { + static var CollectionVC = Int8(0) // "k.FT.AO.CollectionViewController" +} public protocol CollectionViewControllerProtocol: ViewControllerProtocol { var flowLayout: UICollectionViewLayout { get } @@ -47,7 +51,7 @@ public extension CollectionViewControllerProtocol { } var collectionViewController: UICollectionViewController { - guard let collection = AssociatedObject.getAssociated(self, key: &kAOCollectionVC) else { + guard let collection = AssociatedObject.getAssociated(self, key: &AssociatedKey.CollectionVC) else { return setupCoreCollectionVC() } return collection @@ -58,7 +62,6 @@ private extension CollectionViewControllerProtocol { @discardableResult func setupCoreCollectionVC(_ collectionView: UICollectionView? = nil) -> UICollectionViewController { - // Load Base view setupCoreView() @@ -77,7 +80,7 @@ private extension CollectionViewControllerProtocol { self.addChild(collectionVC) self.mainView?.pin(view: collectionVC.collectionView, edgeOffsets: .zero) - AssociatedObject.setAssociated(self, value: collectionVC, key: &kAOCollectionVC) + AssociatedObject.setAssociated(self, value: collectionVC, key: &AssociatedKey.CollectionVC) return collectionVC } diff --git a/Sources/CoreComponents/Contollers/ScrollViewControllerProtocol.swift b/Sources/CoreComponents/Contollers/ScrollViewControllerProtocol.swift index ee0a3b9..4305d61 100644 --- a/Sources/CoreComponents/Contollers/ScrollViewControllerProtocol.swift +++ b/Sources/CoreComponents/Contollers/ScrollViewControllerProtocol.swift @@ -12,17 +12,21 @@ import CoreUtility import Foundation import UIKit -private var kAOScrollVC = "k.FT.AO.ScrollViewController" +// MARK: AssociatedKey + +private extension AssociatedKey { + static var ScrollVC = Int8(0) // "k.FT.AO.ScrollViewController" +} public protocol ScrollViewControllerProtocol: ViewControllerProtocol { var scrollView: UIScrollView { get } } public extension ScrollViewControllerProtocol { - + var scrollView: UIScrollView { get { - guard let scroll = AssociatedObject.getAssociated(self, key: &kAOScrollVC) else { + guard let scroll = AssociatedObject.getAssociated(self, key: &AssociatedKey.ScrollVC) else { return self.setupScrollView() } return scroll @@ -37,22 +41,22 @@ private extension ScrollViewControllerProtocol { @discardableResult func setupScrollView(_ local: UIScrollView = UIScrollView() ) -> UIScrollView { - + // Load Base view setupCoreView() - - if let scroll: UIScrollView = AssociatedObject.getAssociated(self, key: &kAOScrollVC) { + + if let scroll: UIScrollView = AssociatedObject.getAssociated(self, key: &AssociatedKey.ScrollVC) { scroll.removeSubviews() - AssociatedObject.resetAssociated(self, key: &kAOScrollVC) + AssociatedObject.resetAssociated(self, key: &AssociatedKey.ScrollVC) } - + if local.superview == nil { self.mainView?.pin(view: local, edgeOffsets: .zero) local.setupContentView(local.contentView) } - - AssociatedObject.setAssociated(self, value: local, key: &kAOScrollVC) - + + AssociatedObject.setAssociated(self, value: local, key: &AssociatedKey.ScrollVC) + return local } } diff --git a/Sources/CoreComponents/Contollers/TableViewControllerProtocol.swift b/Sources/CoreComponents/Contollers/TableViewControllerProtocol.swift index 9f9b586..08e3e5d 100644 --- a/Sources/CoreComponents/Contollers/TableViewControllerProtocol.swift +++ b/Sources/CoreComponents/Contollers/TableViewControllerProtocol.swift @@ -23,7 +23,7 @@ public protocol TableViewControllerProtocol: ViewControllerProtocol { // MARK: AssociatedKey private extension AssociatedKey { - static var kAOTableVC = "k.FT.AO.TableViewController" + static var kAOTableVC = Int8(0) // "k.FT.AO.TableViewController" } public extension TableViewControllerProtocol { diff --git a/Sources/CoreComponents/Contollers/UIViewController+Extension.swift b/Sources/CoreComponents/Contollers/UIViewController+Extension.swift index 8ea0138..1e459c6 100644 --- a/Sources/CoreComponents/Contollers/UIViewController+Extension.swift +++ b/Sources/CoreComponents/Contollers/UIViewController+Extension.swift @@ -14,7 +14,7 @@ extension UIViewController { var isBaseViewAdded: Bool { // If baseView is not added, then retun false - return (self.baseView?.superview != self.view && self.view != self.baseView) + (self.baseView?.superview != self.view && self.view != self.baseView) } // MARK: Utility diff --git a/Sources/CoreComponents/Contollers/ViewControllerProtocol.swift b/Sources/CoreComponents/Contollers/ViewControllerProtocol.swift index aef9ada..9dd2304 100644 --- a/Sources/CoreComponents/Contollers/ViewControllerProtocol.swift +++ b/Sources/CoreComponents/Contollers/ViewControllerProtocol.swift @@ -13,7 +13,7 @@ import UIKit public protocol ViewControllerProtocol where Self: UIViewController { var modelStack: AnyObject? { get set } - + // Setup View func setupCoreView() // MARK: Navigation Bar @@ -40,10 +40,10 @@ public protocol ViewControllerProtocol where Self: UIViewController { } private extension AssociatedKey { - static var baseView = "baseView" - static var screenIdentifier = "screenIdentifier" - static var modelStack = "modelStack" - static var completionBlock = "completionBlock" + static var baseView = Int8(0) // "baseView" + static var screenIdentifier = Int8(1) // "screenIdentifier" + static var modelStack = Int8(2) // "modelStack" + static var completionBlock = Int8(3) // "completionBlock" } extension UIViewController: ViewControllerProtocol { diff --git a/Sources/CoreComponents/Contollers/WebViewControllerProtocol.swift b/Sources/CoreComponents/Contollers/WebViewControllerProtocol.swift index 2cd6f65..e675b05 100644 --- a/Sources/CoreComponents/Contollers/WebViewControllerProtocol.swift +++ b/Sources/CoreComponents/Contollers/WebViewControllerProtocol.swift @@ -12,7 +12,9 @@ import CoreUtility import UIKit import WebKit -private var kContentVC = "k.FT.AO.ContentViewController" +private extension AssociatedKey { + static var contentVC = Int8(0) // "k.FT.AO.ContentViewController" +} public protocol WebViewControllerProtocol: ViewControllerProtocol { var contentView: WKWebView { get } @@ -20,7 +22,7 @@ public protocol WebViewControllerProtocol: ViewControllerProtocol { public extension WebViewControllerProtocol { var contentView: WKWebView { - get { AssociatedObject.getAssociated(self, key: &kContentVC) { self.setupContentView() }! } + get { AssociatedObject.getAssociated(self, key: &AssociatedKey.contentVC) { self.setupContentView() }! } set { setupContentView(newValue) } } } @@ -28,46 +30,19 @@ public extension WebViewControllerProtocol { private extension WebViewControllerProtocol { @discardableResult func setupContentView(_ local: WKWebView = WKWebView() ) -> WKWebView { - if local.scrollView.delegate == nil { - local.scrollView.delegate = WebViewControllerViewDelegate.shared + if shouldHideNavigationOnScroll() { + (self as? ScrollViewControllerProtocol)?.hideNavigationOnScroll(for: local.scrollView) } // Load Base view setupCoreView() - if let scroll: WKWebView = AssociatedObject.getAssociated(self, key: &kContentVC) { + if let scroll: WKWebView = AssociatedObject.getAssociated(self, key: &AssociatedKey.contentVC) { scroll.removeFromSuperview() - AssociatedObject.resetAssociated(self, key: &kContentVC) + AssociatedObject.resetAssociated(self, key: &AssociatedKey.contentVC) } if local.superview == nil { self.mainView?.pin(view: local, edgeOffsets: .zero) } - AssociatedObject.setAssociated(self, value: local, key: &kContentVC) + AssociatedObject.setAssociated(self, value: local, key: &AssociatedKey.contentVC) return local } } - -internal class WebViewControllerViewDelegate: NSObject, UIScrollViewDelegate { - // MARK: - Shared delegate - static var shared = WebViewControllerViewDelegate() - var shouldHideNav = false - - var navigationController: UINavigationController? { - UIWindow.topViewController?.navigationController - } - - // MARK: - UIScrollViewDelegate - func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { nil } - - func scrollViewWillEndDragging(_ scrollView: UIScrollView, - withVelocity velocity: CGPoint, - targetContentOffset: UnsafeMutablePointer) { - UIView.animate(withDuration: 0.01, delay: 0, options: UIView.AnimationOptions()) { - self.setBarStatus(hidden: velocity.y > 0) - } - } - - func setBarStatus(hidden: Bool) { - self.navigationController?.setNavigationBarHidden(hidden, animated: true) - guard self.navigationController?.toolbarItems?.isEmpty ?? false else { return } - self.navigationController?.setToolbarHidden(hidden, animated: true) - } -} diff --git a/Sources/CoreComponents/CoreComponents/WKWebView+Extension.swift b/Sources/CoreComponents/CoreComponents/WKWebView+Extension.swift index c220cfa..6207a27 100644 --- a/Sources/CoreComponents/CoreComponents/WKWebView+Extension.swift +++ b/Sources/CoreComponents/CoreComponents/WKWebView+Extension.swift @@ -51,10 +51,6 @@ extension WKWebView { } } - public func setHideNavigationOnScroll(hide: Bool) { - WebViewControllerViewDelegate.shared.shouldHideNav = hide - } - public func setScrollEnabled(enabled: Bool) { self.scrollView.isScrollEnabled = enabled self.scrollView.panGestureRecognizer.isEnabled = enabled diff --git a/Sources/CoreComponents/CustomComponents/FTView.swift b/Sources/CoreComponents/CustomComponents/FTView.swift index 500b8b6..cf4d084 100644 --- a/Sources/CoreComponents/CustomComponents/FTView.swift +++ b/Sources/CoreComponents/CustomComponents/FTView.swift @@ -12,6 +12,11 @@ import CoreUtility import Foundation import UIKit +@objc +public protocol ShadowPathProtocol { + func updateShadowPathIfNeeded() +} + open class FTView: UIView { // Set as BaseView, so that "pin'ning" subViews wont alter the base position @@ -106,6 +111,12 @@ open class FTView: UIView { // Pin : MainView to margin rootView.pin(view: self.mainPinnedView, edgeInsets: .horizontal ) } + + override open func layoutSubviews() { + // Send action to superView to update view + UIApplication.shared.sendAction(#selector(ShadowPathProtocol.updateShadowPathIfNeeded), to: nil, from: self, for: nil) + super.layoutSubviews() + } } extension FTView { diff --git a/Sources/CoreUtility/AppBundle/BundleURLScheme.swift b/Sources/CoreUtility/AppBundle/BundleURLScheme.swift index 92917bc..36d3b0c 100644 --- a/Sources/CoreUtility/AppBundle/BundleURLScheme.swift +++ b/Sources/CoreUtility/AppBundle/BundleURLScheme.swift @@ -20,8 +20,7 @@ open class BundleURLScheme { for urlType in bundleURLTypes { if let urlSchemes = (urlType as? NSDictionary)?.value(forKey: "CFBundleURLSchemes") as? NSArray, - urlSchemes.contains(scheme) - { + urlSchemes.contains(scheme) { return true } } diff --git a/Sources/CoreUtility/Extensions/ActionBlock.swift b/Sources/CoreUtility/Extensions/ActionBlock.swift index e4985f3..021dc27 100644 --- a/Sources/CoreUtility/Extensions/ActionBlock.swift +++ b/Sources/CoreUtility/Extensions/ActionBlock.swift @@ -14,7 +14,7 @@ public typealias ActionWithObjectBlock = (_ object: AnyObject?) -> Swift.Void public extension UIControl { private struct AssociatedKey { - static var actionBlockTapped = "actionBlockTapped" + static var actionBlockTapped = Int8(0) // "actionBlockTapped" } func addTapActionBlock(_ actionBlock: @escaping ActionBlock) { diff --git a/Sources/CoreUtility/Extensions/AttributedLabelProtocol.swift b/Sources/CoreUtility/Extensions/AttributedLabelProtocol.swift index 746cc09..14e477c 100644 --- a/Sources/CoreUtility/Extensions/AttributedLabelProtocol.swift +++ b/Sources/CoreUtility/Extensions/AttributedLabelProtocol.swift @@ -26,15 +26,15 @@ protocol AttributedLabelProtocol where Self: UILabel { } private extension AssociatedKey { - static var textContainer = "textContainer" - static var layoutManager = "layoutManager" - static var styleProperties = "styleProperties" + static var textContainer = Int8(0) // "textContainer" + static var layoutManager = Int8(1) // "layoutManager" + static var styleProperties = Int8(2) // "styleProperties" - static var linkRanges = "linkRanges" - static var islinkDetectionEnabled = "islinkDetectionEnabled" - static var isLinkUnderLineEnabled = "isLinkUnderLineEnabled" - static var linkHandler = "linkHandler" - static var tapGestureRecognizer = "tapGestureRecognizer" + static var linkRanges = Int8(3) // "linkRanges" + static var islinkDetectionEnabled = Int8(4) // "islinkDetectionEnabled" + static var isLinkUnderLineEnabled = Int8(5) // "isLinkUnderLineEnabled" + static var linkHandler = Int8(6) // "linkHandler" + static var tapGestureRecognizer = Int8(7) // "tapGestureRecognizer" } extension UILabel: AttributedLabelProtocol { @@ -117,8 +117,8 @@ extension UILabel: OptionalLayoutSubview { fileprivate var offsetXDivisor: CGFloat { switch self.textAlignment { case .center: return 0.5 - case .right: return 1.0 - default: return 0.0 + case .right: return 1.0 + default: return 0.0 } } @@ -133,7 +133,6 @@ extension UILabel: OptionalLayoutSubview { let paragrahStyle = NSMutableParagraphStyle() paragrahStyle.alignment = self.textAlignment paragrahStyle.lineBreakMode = self.lineBreakMode - var properties: AttributedDictionary = [ .paragraphStyle: paragrahStyle, .backgroundColor: self.backgroundColor ?? UIColor.clear diff --git a/Sources/CoreUtility/Extensions/ConfigurableCell+Extension.swift b/Sources/CoreUtility/Extensions/ConfigurableCell+Extension.swift index 86232de..46bd45c 100644 --- a/Sources/CoreUtility/Extensions/ConfigurableCell+Extension.swift +++ b/Sources/CoreUtility/Extensions/ConfigurableCell+Extension.swift @@ -32,15 +32,9 @@ fileprivate extension UIView { } public extension UIView { - static var defaultNibName: String { - String(describing: self) - } - + static var defaultNibName: String { String(describing: self) } // ReuseIdentifier - static var defaultReuseIdentifier: String { - "\(self)ID" - } - + static var defaultReuseIdentifier: String { "\(self)ID" } // Retruns true if nib file is avaialble static var hasNib: Bool { Bundle(for: self).path(forResource: defaultNibName, ofType: "nib") != nil @@ -53,9 +47,7 @@ public extension UIView { } // Retruns first view from the nib file - static func loadNibFromBundle(_ nibName: String? = nil, - bundle: Bundle? = nil, - owner: Any? = nil) throws -> T { + static func loadNibFromBundle(_ nibName: String? = nil, bundle: Bundle? = nil, owner: Any? = nil) throws -> T { let nibName = nibName ?? defaultNibName let bundle = bundle ?? Bundle(for: self) guard bundle.path(forResource: nibName, ofType: "nib") != nil, diff --git a/Sources/CoreUtility/Extensions/LinkDetectionProtocol.swift b/Sources/CoreUtility/Extensions/LinkDetectionProtocol.swift index d4abfb6..675a0ae 100644 --- a/Sources/CoreUtility/Extensions/LinkDetectionProtocol.swift +++ b/Sources/CoreUtility/Extensions/LinkDetectionProtocol.swift @@ -33,10 +33,8 @@ public protocol LinkDetectionProtocol { } public class LinkHandlerModel { - public enum LinkType { - case url - case hashTag + case url, hashTag } public var linkType: LinkType @@ -61,29 +59,22 @@ extension LinkHandlerModel: LinkDetectionProtocol { public extension LinkDetectionProtocol { // Get list of LinkDetection from the String static func getURLLinkRanges(_ text: String) -> [LinkHandlerModel] { - var rangeOfURL = [LinkHandlerModel]() - let types: NSTextCheckingResult.CheckingType = [ .link, .phoneNumber] let detector = try? NSDataDetector(types: types.rawValue) - let range = text.nsRange() detector?.enumerateMatches(in: text, options: [], range: range) { result, _, _ in - if - let url = result?.url, - let range = result?.range { - let dec = LinkHandlerModel(linkType: .url, linkRange: range, linkURL: url) - rangeOfURL.append(dec) + if let url = result?.url, let range = result?.range { + let dec = LinkHandlerModel(linkType: .url, linkRange: range, linkURL: url) + rangeOfURL.append(dec) } } - return rangeOfURL } // Get list of LinkDetection from the NSAttributedString static func getURLLinkRanges(_ text: NSAttributedString) -> [LinkHandlerModel] { let searchKey = NSAttributedString.Key("NSLink") - var rangeOfURL = [LinkHandlerModel]() let range = text.string.nsRange() text.enumerateAttributes(in: range, options: []) { obj, range, _ in @@ -103,21 +94,15 @@ public extension LinkDetectionProtocol { * "#wellcome" is the detected-link */ static func getHashTagRanges(_ text: String) -> [LinkHandlerModel] { - var rangeOfURL = [LinkHandlerModel]() - // Hi #wellcome thanks. // Here, "#wellcome" is retuned text.enumerate(pattern: "(? [LinkHandlerModel] { - var links = [LinkHandlerModel]() - // HTTP links let urlLinks = Self.getURLLinkRanges(attributedString) links.insert(contentsOf: urlLinks, at: 0) - links.forEach { link in let att = getStyleProperties(forLink: link) attributedString.addAttributes(att, range: link.linkRange) } - // Hash Tags let hashLinks = Self.getHashTagRanges(attributedString.string) links.insert(contentsOf: hashLinks, at: 0) - hashLinks.forEach { link in let att = getStyleProperties(forLink: link) attributedString.addAttributes(att, range: link.linkRange) } - return links } diff --git a/Sources/CoreUtility/Extensions/ScrollingNavBarProtocol.swift b/Sources/CoreUtility/Extensions/ScrollingNavBarProtocol.swift new file mode 100644 index 0000000..1722db4 --- /dev/null +++ b/Sources/CoreUtility/Extensions/ScrollingNavBarProtocol.swift @@ -0,0 +1,104 @@ +// +// ScrollAnimation.swift +// MobileCore +// +// Created by Praveen Prabhakar on 20/07/21. +// + +import Foundation +import UIKit + +public protocol ScrollingNavBarProtocol where Self: UIViewController { + func hideNavigationOnScroll(for scrollableView: UIView, delay: Double) + func navBarAnimation(velocity: CGPoint, animateDuration: TimeInterval, delay: TimeInterval) + func setBarStatus(hidden: Bool) + func shouldHideNavigationOnScroll() -> Bool +} + +private extension AssociatedKey { + static var navBarScrollableView = Int8(0) // "navBar.scrollableView" +} + +extension UIViewController: ScrollingNavBarProtocol { + // Will hide Navigation bar on scroll + @objc + open func shouldHideNavigationOnScroll() -> Bool { true } + + private var scrollableView: UIView? { + get { AssociatedObject.getAssociated(self, key: &AssociatedKey.navBarScrollableView)?.scrollableView } + set { + var scrollDelegate: ScrollingNavBar? + if let view = newValue { + scrollDelegate = ScrollingNavBar(scrollableView: view, delegate: self) + } + AssociatedObject.setAssociated(self, value: scrollDelegate, key: &AssociatedKey.navBarScrollableView) + } + } + + public func hideNavigationOnScroll(for scrollableView: UIView, delay: Double = 0) { + self.scrollableView = scrollableView + } + + public func navBarAnimation(velocity: CGPoint, animateDuration: TimeInterval = 0.01, delay: TimeInterval = 0) { + let isHidden = velocity.y <= 0 + guard shouldHideNavigationOnScroll(), navigationController?.isNavigationBarHidden != isHidden else { return } + UIView.animate(withDuration: animateDuration, delay: delay, options: UIView.AnimationOptions.beginFromCurrentState) { + self.setBarStatus(hidden: isHidden) + } + } + + public func setBarStatus(hidden: Bool) { + navigationController?.setNavigationBarHidden(hidden, animated: true) + guard self.navigationController?.toolbarItems?.isEmpty ?? false else { return } + self.navigationController?.setToolbarHidden(hidden, animated: true) + } +} + +private class ScrollingNavBar: NSObject, UIGestureRecognizerDelegate { + weak var scrollableView: UIView? + weak var delegate: ScrollingNavBarProtocol? + private lazy var gestureRecognizer: UIPanGestureRecognizer = { + let gestureRecognizer = UIPanGestureRecognizer() + gestureRecognizer.maximumNumberOfTouches = 1 + gestureRecognizer.delegate = self + gestureRecognizer.cancelsTouchesInView = false + return gestureRecognizer + }() + + deinit { + scrollableView?.removeGestureRecognizer(gestureRecognizer) + } + + init(scrollableView: UIView, delegate: ScrollingNavBarProtocol) { + super.init() + self.scrollableView = scrollableView + self.delegate = delegate + self.setupGessture() + } + + func setupGessture() { + scrollableView?.addGestureRecognizer(gestureRecognizer) + } + + /** + UIGestureRecognizerDelegate: Begin scrolling only if the direction is vertical (prevents conflicts with horizontal scroll views) + */ + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + // Default system behavior returns `true` + guard gestureRecognizer == self.gestureRecognizer, let gestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { return true } + let velocity = gestureRecognizer.velocity(in: gestureRecognizer.view) + // Show/Hide Navigation Bar based on velocity of scrolling + delegate?.navBarAnimation(velocity: velocity) + return true // abs(velocity.y) > abs(velocity.x) + } + + /** + UIGestureRecognizerDelegate: Enables the scrolling of both the content and the navigation bar + */ + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, + shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + // Default system behavior returns `false` + guard [gestureRecognizer, otherGestureRecognizer].contains(self.gestureRecognizer) else { return false } + return true + } +} diff --git a/Sources/CoreUtility/Extensions/String+Extension.swift b/Sources/CoreUtility/Extensions/String+Extension.swift index b643e55..c6c4173 100644 --- a/Sources/CoreUtility/Extensions/String+Extension.swift +++ b/Sources/CoreUtility/Extensions/String+Extension.swift @@ -137,29 +137,20 @@ public extension String { CGSize of text based. */ func textSize(font: UIFont, constrainedSize: CGSize, lineBreakMode: NSLineBreakMode) -> CGSize { - guard !self.isEmpty else { - return .zero - } - + guard !self.isEmpty else { return .zero } let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineBreakMode = lineBreakMode - - let attributes: AttributedDictionary = [ - .font: font, - .paragraphStyle: paragraphStyle - ] - + let attributes: AttributedDictionary = [ .font: font, .paragraphStyle: paragraphStyle ] let attributedString = NSAttributedString(string: self, attributes: attributes) - let size = attributedString.boundingRect( + let rect = attributedString.boundingRect( with: constrainedSize, options: [.usesDeviceMetrics, .usesLineFragmentOrigin, .usesFontLeading], context: nil - ).size - - return ceil(size: size) + ) + return ceil(size: rect.size) } -// MARK: JSON + // MARK: JSON // Loading Data from given Path func jsonContentAtPath() throws -> T? { try dataAtPath()?.jsonContent() as? T @@ -201,10 +192,8 @@ public extension String { } func bundle() -> Bundle? { - if let bundleURL = self.bundleURL() { - return Bundle(url: bundleURL) - } - return nil + guard let bundleURL = self.bundleURL() else { return nil } + return Bundle(url: bundleURL) } } @@ -215,16 +204,15 @@ public extension NSAttributedString { } func mutableString() -> NSMutableAttributedString { - if let value = self.mutableCopy() as? NSMutableAttributedString { - return value + guard let value = self.mutableCopy() as? NSMutableAttributedString else { + return NSMutableAttributedString() } - return NSMutableAttributedString() + return value } } public extension NSMutableAttributedString { func addParagraphStyle(style: NSMutableParagraphStyle) { - let range = self.nsRange() - self.addAttribute(.paragraphStyle, value: style, range: range) + self.addAttribute(.paragraphStyle, value: style, range: nsRange()) } } diff --git a/Sources/CoreUtility/Extensions/UIColor+Extension.swift b/Sources/CoreUtility/Extensions/UIColor+Extension.swift index 4cb7d7f..16a9411 100644 --- a/Sources/CoreUtility/Extensions/UIColor+Extension.swift +++ b/Sources/CoreUtility/Extensions/UIColor+Extension.swift @@ -10,31 +10,10 @@ import Foundation import UIKit /* - Here's a correct table of percentages to hex values. E.g. for 50% white you'd use #80FFFFFF. - 100% — FF - 95% — F2 - 90% — E6 - 85% — D9 - 80% — CC - 75% — BF - 70% — B3 - 65% — A6 - 60% — 99 - 55% — 8C - 50% — 80 - 45% — 73 - 40% — 66 - 35% — 59 - 30% — 4D - 25% — 40 - 20% — 33 - 15% — 26 - 10% — 1A - 5% — 0D - 0% — 00 - - Percentage to hex values: - https://stackoverflow.com/questions/15852122/hex-transparency-in-colors + Here's the table of percentages to hex values. E.g. for 50% white you'd use #FFFFFF80. + 100% — FF :: 95% — F2 :: 90% — E6 :: 85% — D9 :: 80% — CC :: 75% — BF :: 70% — B3 :: 65% — A6 + 60% — 99 :: 55% — 8C :: 50% — 80 :: 45% — 73 :: 40% — 66 :: 35% — 59 :: 30% — 4D :: 25% — 40 + 20% — 33 :: 15% — 26 :: 10% — 1A :: 5% — 0D :: 0% — 00 */ public extension UIColor { @@ -120,10 +99,8 @@ public extension UIColor { public extension UIImage { convenience init?(color: UIColor) { - if let cgImage = color.generateImage()?.cgImage { - self.init(cgImage: cgImage) - } - return nil + guard let cgImageValue = color.generateImage()?.cgImage else { return nil } + self.init(cgImage: cgImageValue) } func getColor(a: CGFloat = -10) -> UIColor? { diff --git a/Sources/CoreUtility/Extensions/UIView+Utiltiy.swift b/Sources/CoreUtility/Extensions/UIView+Utiltiy.swift index a864793..8f9e16c 100644 --- a/Sources/CoreUtility/Extensions/UIView+Utiltiy.swift +++ b/Sources/CoreUtility/Extensions/UIView+Utiltiy.swift @@ -42,7 +42,7 @@ public extension UIView { if val is T { return val as? T } - else if !val.subviews.isEmpty, let subType: T? = val.findInSubView() { + else if !val.subviews.isEmpty, let subType: T? = val.findInSubView() { return subType } } diff --git a/Sources/CoreUtility/Generic/AssociatedObject.swift b/Sources/CoreUtility/Generic/AssociatedObject.swift index 1fe6e1a..ca39035 100644 --- a/Sources/CoreUtility/Generic/AssociatedObject.swift +++ b/Sources/CoreUtility/Generic/AssociatedObject.swift @@ -10,7 +10,7 @@ import Foundation import UIKit public enum AssociatedKey { - public static var defaultKey = "AssociatedKey.defaultKey" + public static var defaultKey = Int8(0) // "AssociatedKey.defaultKey" } // Generic way of storing values on runtime diff --git a/Sources/CoreUtility/Generic/FlattenIterator.swift b/Sources/CoreUtility/Generic/FlattenIterator.swift index 6423821..e1a20e1 100644 --- a/Sources/CoreUtility/Generic/FlattenIterator.swift +++ b/Sources/CoreUtility/Generic/FlattenIterator.swift @@ -49,7 +49,7 @@ public extension FlattenIterator { } // Array else if var dicArray = self as? [Any?] { - dicArray = dicArray.filter({ $0 != nil }) + dicArray = dicArray.filter { $0 != nil } self = dicArray.map { val -> Any in var value = val return stripSubElements(&value!) diff --git a/Sources/MobileTheming/ThemesManager.swift b/Sources/MobileTheming/ThemesManager.swift new file mode 100644 index 0000000..d059a8d --- /dev/null +++ b/Sources/MobileTheming/ThemesManager.swift @@ -0,0 +1,15 @@ +// +// ThemesManager.swift +// MobileCore +// +// Created by Praveen Prabhakar on 10/24/24. +// + +#if canImport(MobileTheming) +// import CoreUtility +#endif +import Foundation +import SwiftUI + +open class ThemesManager { +} diff --git a/Sources/NetworkLayer/Cache/UserCacheManager.swift b/Sources/NetworkLayer/Cache/UserCacheManager.swift index 70bbf2b..208cd72 100644 --- a/Sources/NetworkLayer/Cache/UserCacheManager.swift +++ b/Sources/NetworkLayer/Cache/UserCacheManager.swift @@ -71,11 +71,11 @@ public class UserCacheManager: UserCacheProtocol { }() // Application level cache, reset when app relaunches - public fileprivate (set) var appCache = JSON() + public fileprivate(set) var appCache = JSON() // session cache, clears when user logout - public fileprivate (set) var userCache: JSON? + public fileprivate(set) var userCache: JSON? // Image cache, clears when user logout - public fileprivate (set) var imageCache = NSCache() + public fileprivate(set) var imageCache = NSCache() public func setupUserSession() { // Setup local cache diff --git a/Sources/NetworkLayer/NetworkLayer/NetworkMananger.swift b/Sources/NetworkLayer/NetworkLayer/NetworkMananger.swift index 66ed8e8..5fd2865 100644 --- a/Sources/NetworkLayer/NetworkLayer/NetworkMananger.swift +++ b/Sources/NetworkLayer/NetworkLayer/NetworkMananger.swift @@ -40,7 +40,7 @@ public class NetworkMananger { // Stub data bundle, used by ServiceClient static var mockBundle: Bundle? - public static var mockBundleResource: URL? = nil { + public static var mockBundleResource: URL? { didSet { if let bundle = mockBundleResource { mockBundle = Bundle(url: bundle) @@ -140,8 +140,7 @@ extension NetworkMananger { if let resourcePath = Bundle.main.path(forResource: self.serviceBindingRulesName, ofType: nil), let content: JSON = NSMutableDictionary(contentsOfFile: resourcePath) as? JSON, - JSONSerialization.isValidJSONObject(content) - { + JSONSerialization.isValidJSONObject(content) { self.sharedInstance.serviceRuels += content } } diff --git a/Sources/NetworkLayer/NetworkLayer/ServiceClient.swift b/Sources/NetworkLayer/NetworkLayer/ServiceClient.swift index 35f535d..46f0f4b 100644 --- a/Sources/NetworkLayer/NetworkLayer/ServiceClient.swift +++ b/Sources/NetworkLayer/NetworkLayer/ServiceClient.swift @@ -22,9 +22,9 @@ private enum LogConstants: String { // MARK: AssociatedKey private extension AssociatedKey { - static var ServiceRequest = "ServiceRequest" - static var ResponseData = "ResponseData" - static var ModelData = "ModelData" + static var ServiceRequest = Int8(0) // "ServiceRequest" + static var ResponseData = Int8(1) // "ResponseData" + static var ModelData = Int8(2) // "ModelData" } // MARK: Service Status @@ -324,7 +324,7 @@ extension ServiceClient { } } - let handler: URLSessionCompletionBlock = { (data: Data?, response: URLResponse?, error: Error?) -> Void in + let handler: URLSessionCompletionBlock = { (data: Data?, response: URLResponse?, error: Error?) in let request = self.serviceRequest // Log Resposne logError(request, error) @@ -373,8 +373,7 @@ extension ServiceClient { ftLog(self.serviceName, ": is data stubbed.") if let path: String = NetworkMananger.mockBundle?.path(forResource: self.serviceName, ofType: "json"), - let data = try? path.dataAtPath() - { + let data = try? path.dataAtPath() { let model = self.processResponseData(data: data) DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(2)) { completionHandler?(ServiceStatus.success(self, (model != nil) ? 200 : 500)) diff --git a/Tests/AppThemingTests/CollectionViewThemeTests.swift b/Tests/AppThemingTests/CollectionViewThemeTests.swift index e1063ca..ce4f3d3 100644 --- a/Tests/AppThemingTests/CollectionViewThemeTests.swift +++ b/Tests/AppThemingTests/CollectionViewThemeTests.swift @@ -48,10 +48,10 @@ final class CollectionViewThemeTests: XCTestCase { let value = ThemesManager.getColor("white") // when collectionView.backgroundView = tempView - collectionView.theme = ThemeStyle.defaultStyle + collectionView.theme = ThemeStyle.defaultStyle.rawValue // then XCTAssertNotNil(value, "Should have valid value") - XCTAssertEqual(tempView.theme, ThemeStyle.defaultStyle) + XCTAssertEqual(tempView.theme, ThemeStyle.defaultStyle.rawValue) XCTAssertEqual(tempView.backgroundColor, value) } } diff --git a/Tests/AppThemingTests/CustomUIElementsTests.swift b/Tests/AppThemingTests/CustomUIElementsTests.swift index fee5a30..86e1996 100644 --- a/Tests/AppThemingTests/CustomUIElementsTests.swift +++ b/Tests/AppThemingTests/CustomUIElementsTests.swift @@ -70,7 +70,7 @@ final class CustomUIElementsTests: XCTestCase { let white = ThemesManager.getColor("white") let bar = UISearchBar(frame: .zero) bar.text = titleString - bar.theme = ThemeStyle.defaultStyle + bar.theme = ThemeStyle.defaultStyle.rawValue XCTAssertEqual(bar.barTintColor, ThemesManager.getColor("navBarRed")) XCTAssertEqual(bar.tintColor, white) if #available(iOS 13.0, *) { @@ -83,7 +83,7 @@ final class CustomUIElementsTests: XCTestCase { // let let white = ThemesManager.getColor("navBarRed") let segment = UISegmentedControl(items: ["item1", "item2"]) - segment.theme = ThemeStyle.defaultStyle + segment.theme = ThemeStyle.defaultStyle.rawValue XCTAssertEqual(segment.tintColor, white) } } diff --git a/Tests/CoreComponentsTests/ScrollViewControllerProtocolTests.swift b/Tests/CoreComponentsTests/ScrollViewControllerProtocolTests.swift index 01f5af4..a313b3a 100644 --- a/Tests/CoreComponentsTests/ScrollViewControllerProtocolTests.swift +++ b/Tests/CoreComponentsTests/ScrollViewControllerProtocolTests.swift @@ -13,7 +13,7 @@ import CoreUtility import UIKit import XCTest -fileprivate final class MockScrollViewController: UIViewController, ScrollViewControllerProtocol { +private final class MockScrollViewController: UIViewController, ScrollViewControllerProtocol { // Optional Protocol implementation: intentionally empty } diff --git a/Tests/CoreComponentsTests/TableViewControllerProtocolTests.swift b/Tests/CoreComponentsTests/TableViewControllerProtocolTests.swift index 6b1ab17..2262283 100644 --- a/Tests/CoreComponentsTests/TableViewControllerProtocolTests.swift +++ b/Tests/CoreComponentsTests/TableViewControllerProtocolTests.swift @@ -12,15 +12,15 @@ import CoreUtility #endif import XCTest -fileprivate final class MockTableViewHeader: UIView { +private final class MockTableViewHeader: UIView { // Temp class extending Protocol for testing } -fileprivate final class MockTableViewController: UIViewController, TableViewControllerProtocol { +private final class MockTableViewController: UIViewController, TableViewControllerProtocol { // Temp class extending Protocol for testing } -fileprivate final class MockCustomTableViewController: UIViewController, TableViewControllerProtocol { +private final class MockCustomTableViewController: UIViewController, TableViewControllerProtocol { var tableStyle: UITableView.Style = .grouped var tableViewEdgeOffsets: UIEdgeInsets = .init(40, 40, 40, 40) } diff --git a/Tests/CoreComponentsTests/ViewControllerProtocolTests.swift b/Tests/CoreComponentsTests/ViewControllerProtocolTests.swift index f2c0b92..83bb113 100644 --- a/Tests/CoreComponentsTests/ViewControllerProtocolTests.swift +++ b/Tests/CoreComponentsTests/ViewControllerProtocolTests.swift @@ -28,9 +28,9 @@ extension MockModelStack: Equatable { } private final class MockViewContoller: UIViewController { - private (set) var isKeyboardWillShowCalled = false - private (set) var isKeyboardDidHideCalled = false - private (set) var isAlertViewPresented = false + private(set) var isKeyboardWillShowCalled = false + private(set) var isKeyboardDidHideCalled = false + private(set) var isAlertViewPresented = false override func keyboardWillShow(_ notification: Notification?) { isKeyboardWillShowCalled = true diff --git a/Tests/CoreUtilityTests/ConfigurableCellTests.swift b/Tests/CoreUtilityTests/ConfigurableCellTests.swift index 14f8efa..8260647 100644 --- a/Tests/CoreUtilityTests/ConfigurableCellTests.swift +++ b/Tests/CoreUtilityTests/ConfigurableCellTests.swift @@ -11,7 +11,7 @@ import CoreUtility #endif import XCTest -fileprivate final class MockViewCellWithoutNib: UIView { +private final class MockViewCellWithoutNib: UIView { } final class ConfigurableCellTests: XCTestCase { diff --git a/Tests/NetworkLayerTests/CollectionViewProtocolTests.swift b/Tests/NetworkLayerTests/CollectionViewProtocolTests.swift index 231d035..abee3a5 100644 --- a/Tests/NetworkLayerTests/CollectionViewProtocolTests.swift +++ b/Tests/NetworkLayerTests/CollectionViewProtocolTests.swift @@ -13,7 +13,7 @@ #endif import XCTest -fileprivate final class MockCollectionViewController: UIViewController, CollectionViewControllerProtocol { +private final class MockCollectionViewController: UIViewController, CollectionViewControllerProtocol { // Mock: object implementation for testing } diff --git a/Tests/NetworkLayerTests/WebViewControllerProtocolTests.swift b/Tests/NetworkLayerTests/WebViewControllerProtocolTests.swift index c445ab2..aa1d41e 100644 --- a/Tests/NetworkLayerTests/WebViewControllerProtocolTests.swift +++ b/Tests/NetworkLayerTests/WebViewControllerProtocolTests.swift @@ -14,7 +14,7 @@ import WebKit import XCTest -fileprivate final class MockKWebViewController: UIViewController, WebViewControllerProtocol { +private final class MockKWebViewController: UIViewController, WebViewControllerProtocol { // Mock: object implementation for testing } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a7c48be..7ca426d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -16,7 +16,8 @@ lane :lint do swiftlint( mode: :lint, output_file: 'reports/swiftlint.txt', - config_file: '.swiftlint.yml' + config_file: '.swiftlint.yml', + ignore_exit_status: true ) end diff --git a/fastlane/Package.swift b/fastlane/Package.swift deleted file mode 100644 index 8c37ad8..0000000 --- a/fastlane/Package.swift +++ /dev/null @@ -1,4 +0,0 @@ -// swift-tools-version:5.3 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -// To hide this folder from showing in Swift Package manger