Compare commits

..

257 Commits

Author SHA1 Message Date
Ivan Smolin 3d57f5ccc4 Merge pull request 'HolderViewSkeletonsConfiguration, CALayer support for DashedBoundsLayer' (#29) from feature/skeletons_holder_configuration into master
Reviewed-on: #29
2024-02-12 10:52:50 +03:00
Ivan Smolin e8780d0238 Update: `ViewSkeletonsConfiguration`. It's possible to enable or disable animation for specific skeletons now.
Added: `HolderViewSkeletonsConfiguration` for skeleton root view configuration
Added: `DashedBoundsLayer` can now be applied to `CALayer`
2024-02-09 16:14:32 +03:00
Ivan Smolin b7862cb891 Merge pull request 'fix: revert `TextSkeletonsConfiguration` line height calculation' (#28) from fix/skeleton_line_height_calculation into master
Reviewed-on: #28
2024-02-01 14:41:53 +03:00
Ivan Smolin e8ca5ad039 fix: revert `TextSkeletonsConfiguration` line height calculation 2024-02-01 14:35:27 +03:00
Ivan Smolin 8fe2bfd6e8 Merge pull request 'feature/update_table_kit_repo' (#27) from feature/update_table_kit_repo into master
Reviewed-on: #27
2024-02-01 10:45:47 +03:00
Ivan Smolin 18e5421e14 feat: Update: use TouchInstinct `TableKit` fork instead of original one
Update: remove default value from `BoolValueDefaultsStorage`
2024-02-01 10:14:21 +03:00
Ivan Smolin 5549854c75 feat: update tablekit repo 2024-01-31 12:18:40 +03:00
Ivan Smolin af7c5bc9fd Merge pull request 'fix: build and warnings' (#26) from fix/build_and_warnings into master
Reviewed-on: #26
2024-01-30 21:27:35 +03:00
Ivan Smolin 6a7af59ffc fix: build and warnings 2024-01-30 21:25:13 +03:00
Ivan Smolin 36b4f84306 Merge pull request 'Added: `xcprivacy` files' (#25) from fix/minor_fixes_and_xcprivacy into master
Reviewed-on: #25
2024-01-30 20:51:04 +03:00
Ivan Smolin 5c7df9373d Added: `xcprivacy` files
Update: Correctly detect app reinstall in `AppInstallLifetimeSingleValueStorage`
Update: use `xHeight` instead of `pointSize` for default skeleton line height calculation
Update: update `linkTextAttributes` in `UITextView` when setting interactive url parts
2024-01-30 20:40:14 +03:00
Ivan Smolin 5c44471dbb Merge pull request 'fix: change UIButton event propogation avoidance method' (#24) from fix/uibutton_event_propogation_fix into master
Reviewed-on: #24
2023-11-24 12:57:00 +03:00
Ivan Smolin 7a7987239d fix: change UIButton event propogation avoidance method 2023-11-23 21:16:36 +03:00
Ivan Smolin b65029056f Merge pull request 'fix: StatefulButton state configuration for iOS 15+' (#23) from fix/stateful_button_state into master
Reviewed-on: #23
2023-11-08 18:20:01 +03:00
Ivan Smolin 815d749bd9 fix: StatefulButton state configuration for iOS 15+ 2023-11-08 18:17:10 +03:00
Ivan Smolin 03059969b2 Merge pull request 'feat: Set reasonable defaults for SkeletonConfiguration' (#22) from feature/skeleton_configuration_defaults into master
Reviewed-on: #22
2023-11-07 16:39:55 +03:00
Ivan Smolin 1a9ed8353b feat: Set reasonable defaults for SkeletonConfiguration 2023-11-07 16:29:10 +03:00
Ivan Smolin 03aa25e529 Merge pull request 'fix: Changed access level from internal to public of title and subtitle view in BaseTitleSubtitleView' (#21) from fix/base_title_subtitle_view_access_level into master
Reviewed-on: #21
2023-11-03 22:24:57 +03:00
Ivan Smolin 48a2b47428 fix: Changed access level from internal to public of title and subtitle view in BaseTitleSubtitleView 2023-11-03 22:21:05 +03:00
Ivan Smolin d06c1c35d4 Merge pull request 'feat: Added: BaseTitleSubtitleView which can be inherited for fine-tuning skeletons and other behavior.' (#20) from feature/base_title_subtitle_view into master
Reviewed-on: #20
2023-11-03 19:38:19 +03:00
Ivan Smolin 9fab98b560 feat: Added: BaseTitleSubtitleView which can be inherited for fine-tuning skeletons and other behavior.
Update: Changed lines number calculation method in TextSkeletonsConfiguration.
2023-11-03 18:56:26 +03:00
Ivan Smolin 94cf900f7c Merge pull request 'feat: Added: maxWidth parameter to BaseViewSkeletonsConfiguration.' (#19) from feature/skeletons_configuration into master
Reviewed-on: #19
2023-11-02 21:55:21 +03:00
Ivan Smolin c1e96eee35 feat: Added: maxWidth parameter to BaseViewSkeletonsConfiguration.
Added: custom SkeletonConfigurations for nested SkeletonPresenters.
Update: Many fixes and improvements to TextSkeletonsConfiguration.
2023-11-02 21:40:01 +03:00
Ivan Smolin ed8a2113c4 Merge pull request 'feat: Skeletonable can now control custom geometry change notification.' (#18) from feature/skeletons_tuning into master
Reviewed-on: #18
2023-11-01 15:55:48 +03:00
Ivan Smolin 8652795ddb feat: Skeletonable can now control custom geometry change notification.
Filter hidden views from skeletonable views by default.
2023-11-01 14:45:57 +03:00
Ivan Smolin 38dc604cbc Merge pull request 'feat: DefaultTitleSubtitleView support for separated configuration of title and subtitle labels layout.' (#17) from feature/layout_improvenments into master
Reviewed-on: #17
2023-10-30 16:36:31 +03:00
Ivan Smolin abe9ad5dc1 feat: DefaultTitleSubtitleView support for separated configuration of title and subtitle labels layout.
BaseListItemView fixed trailing insets when trailing view is hidden.
2023-10-30 15:21:55 +03:00
Ivan Smolin abd18d848c Merge pull request 'fix: layout DSL heuristics' (#16) from feature/layout_dsl_heuristics into master
Reviewed-on: #16
2023-10-23 14:05:16 +03:00
Ivan Smolin 4a076b8865 fix: layout DSL heuristics 2023-10-20 20:40:21 +03:00
Ivan Smolin 767c19d17b Merge pull request 'feat: Custom string attributes to `BaseTextAttributes`' (#15) from feature/uiviewbackground into master
Reviewed-on: #15
2023-10-09 23:52:09 +03:00
Ivan Smolin 90cd941eff docs: update docs 2023-10-09 23:30:37 +03:00
Ivan Smolin 2bf1fc052a feat: Custom string attributes to `BaseTextAttributes`
- Customizeable `UIViewBackground` and `UIViewBorder` for `UIView.Appearance`
- Keychain single value storage for codable models -`CodableSingleValueKeychainStorage`
- Renamed methods `startAnimation` and `stopAnimation` of `SkeletonPresenter`, so it won't conflict with `Animatable` protocol anymore
2023-10-09 23:03:51 +03:00
Ivan Smolin a03fc1f7ee Merge pull request 'build: fix compile issue' (#14) from build/fix_compile_issue into master
Reviewed-on: #14
2023-09-06 12:07:18 +03:00
Ivan Smolin 7945aa3a62 build: fix compile issue 2023-09-06 11:51:16 +03:00
Ivan Smolin 93b0f61b00 Merge pull request 'feat: TIApplication module and other fixes and improvements' (#13) from feature/ti_application into master
Reviewed-on: #13
2023-09-06 11:31:36 +03:00
Ivan Smolin a0c7faa4a3 fix: code review notes 2023-09-05 16:49:05 +03:00
Ivan Smolin 60734996f6 feat: add universal DSL to TISwiftUtils, remove unused code 2023-08-24 12:07:43 +03:00
Ivan Smolin eaea4abd75 feat: TIApplication module and other fixes and improvements 2023-08-23 20:49:23 +03:00
Ivan Smolin b8611321fb Merge pull request 'feature/bottom-sheet' (#11) from feature/bottom-sheet into master
Reviewed-on: #11
2023-07-28 16:18:36 +03:00
Ivan Smolin 13a5925443 fix: review notes 2023-07-28 16:08:35 +03:00
Ivan Smolin c485434f51 build: add makefile for parallel execution of pod repo push action 2023-07-26 22:24:02 +03:00
Ivan Smolin 843a887ec7 fix: move presentation detents settings to modal view controller appearance, force use nan for undefined layout dimension, fix related layout issues 2023-07-26 17:31:28 +03:00
Ivan Smolin 8007532351 fix: keyboard overlapping for footer and content view of BaseModalViewController 2023-07-25 19:09:21 +03:00
Ivan Smolin 0ef1edfacb build: move included pan modal sources to separate dependency 2023-07-25 17:32:21 +03:00
Ivan Smolin 27d5a3a9ca fix: fix code review notes 2023-07-24 20:48:46 +03:00
Ivan Smolin 4e8f60543d Merge branch 'master' into feature/bottom-sheet 2023-07-24 11:23:40 +03:00
Ivan Smolin c2b31a90d6 Merge pull request 'feature/stateful_button_improvements' (#12) from feature/stateful_button_improvements into master
Reviewed-on: #12
2023-07-24 10:00:37 +03:00
Ivan Smolin 1a4c42fa46 fix: StatefulButton appearance configuration 2023-07-17 18:51:41 +03:00
Ivan Smolin 094c0c40d8 build: prevent compile-time failure in application extensions 2023-07-11 16:30:34 +03:00
Ivan Smolin b22fd239e9 fix: text size calculation in cluster icon renderer 2023-07-10 18:38:41 +03:00
Ivan Smolin 65ae079e62 fix: conform TemplateDrawingOperation to OrientationAwareDrawingOperation 2023-07-10 17:25:24 +03:00
Ivan Smolin 7c8a29a7f9 build: update changelog and podspec versions 2023-07-10 16:37:27 +03:00
Ivan Smolin df2faa4cd5 feat: MarkerIconFactory can now return optional image. In this case MapManagers will show the default marker icon 2023-07-10 16:17:29 +03:00
Ivan Smolin 36019f7429 feat: TICoreGraphicsUtils module for drawing operations and other CoreGraphics related functionality 2023-07-10 16:17:29 +03:00
Ivan Smolin 5245d48a8a feat: stateful button improvements 2023-07-10 16:17:28 +03:00
Nikita Semenov 83655d2bac Merge pull request 'feat: migrating storages' (#10) from feature/migrating_storage into master
Reviewed-on: #10
2023-07-09 22:33:51 +03:00
Nikita Semenov 6b7be340f5 fix: added default logger parameter 2023-07-09 22:15:36 +03:00
Nikita Semenov 6084dd5fec fix: move tifoundationlogger to it's own folder 2023-07-09 22:05:58 +03:00
Nikita Semenov 85b206bf18 fix: move tifoundationlogger to it's own folder 2023-07-09 21:51:41 +03:00
Nikita Semenov da527644a7 fix: move tifoundationlogger to it's own folder 2023-07-07 15:39:11 +03:00
Nikita Semenov 45c060403f fix: logic of getting value from storage 2023-07-07 15:17:54 +03:00
Nikita Semenov a79ff67a38 fix: logic of deletion 2023-07-07 14:00:43 +03:00
Nikita Semenov c631053131 fix: code review notes 2023-07-07 09:01:17 +03:00
Nikita Semenov c55b8f73a9 fix: code review notes 2023-07-06 18:42:18 +03:00
Nikita Semenov 25c0d04d11 feat: update tests, migration fixes, factory method for migration storage added 2023-07-06 12:34:48 +03:00
Nikita Semenov b97ea5bc8f docs: update appearance of view controller in documentation 2023-07-03 10:17:47 +03:00
Nikita Semenov 06a83190ab fix: refactoring with new layout configuration api 2023-07-03 08:46:16 +03:00
Nikita Semenov 3f112d2d26 Merge branch 'master' into feature/bottom-sheet
# Conflicts:
#	CHANGELOG.md
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeeplink/TIDeeplink.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITextProcessing/TITextProcessing.podspec
#	TIUIElements/Sources/Views/Helpers/WrappedViewLayout+Helpers.swift
#	TIUIElements/Sources/Views/Placeholder/Views/BasePlaceholderImageView.swift
#	TIUIElements/Sources/Views/Placeholder/Views/BasePlaceholderView.swift
#	TIUIElements/Sources/Wrappers/Constraints/CenterConstraints.swift
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIWebView/TIWebView.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2023-07-03 07:31:44 +03:00
Nikita Semenov 808d40eca5 feat: added bodal wrapper view controller 2023-07-03 01:47:52 +03:00
Nikita Semenov 46ecd6970f fix: migration api refactoring + test coverage 2023-07-02 18:00:38 +03:00
Nikita Semenov 0776c99e38 Merge branch 'master' into feature/migrating_storage
# Conflicts:
#	CHANGELOG.md
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeeplink/TIDeeplink.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITextProcessing/TITextProcessing.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIWebView/TIWebView.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
#	docs/tikeychainutils/singlevaluestorage.md
2023-06-30 22:03:35 +03:00
Nikita Semenov a06f4952b9 fix: reimagine migration storage api 2023-06-28 09:26:15 +03:00
Ivan Smolin 77559babdb Merge pull request 'feature/stack_appearance_layout' (#9) from feature/stack_appearance_layout into master
Reviewed-on: #9
2023-06-26 19:51:15 +03:00
Ivan Smolin 86413b3bb4 build: fix code review notes 2023-06-26 14:06:56 +03:00
Nikita Semenov 9056b2fe8c feat: migrating storages 2023-06-25 20:14:06 +03:00
Ivan Smolin 0e45bb462d build: update changelog and up version 2023-06-17 01:56:50 +03:00
Ivan Smolin e8b026302e BaseStackView with configurable items appearance
CollectionTableViewCell self-sizing
ViewAppearance.WrappedViewLayout support for all WrappedViewHolders
ViewCallbacks support for all BaseInitializeableViews
2023-06-17 01:42:14 +03:00
Ivan Smolin 818d4dbe8d Merge pull request 'feature/flat_map_async_operation_result_type_codable_storage' (#8) from feature/flat_map_async_operation_result_type_codable_storage into master
Reviewed-on: #8
2023-06-15 13:30:22 +03:00
Nikita Semenov 86fddafcdf docs: added playground for bottom sheet 2023-06-13 10:33:20 +03:00
Nikita Semenov 919423ecda feat: completed bottom sheet api 2023-06-13 09:29:53 +03:00
Nikita Semenov c06bb56964 fix: remove unused file 2023-06-13 05:59:22 +03:00
Nikita Semenov b141dc5a45 feat: pan modal changes 2023-06-12 22:05:03 +03:00
Ivan Smolin 1e3b986c83 fix: iOS 12 crash - failed to demangle superclass of UIClosureObserverOperation from mangled name '\M^? \^Hp\M-}\M^?' 2023-06-09 16:09:17 +03:00
Ivan Smolin 2ea88a94aa build: fix excluded files pattern in TIFoundationUtils 2023-06-09 12:42:55 +03:00
Ivan Smolin 1be28959bc docs: update playground pages for AsyncOperation and SingleValueExpirationStorage 2023-06-09 11:45:55 +03:00
Ivan Smolin bf613b99e8 build: update changelog and podspec version, fix playground compile issues 2023-06-09 10:14:07 +03:00
Ivan Smolin 23c17c9d85 feat: Added flatMap operator for AsyncOperation
CodableKeyValueStorage now returns Swift.Result with typed errors.
SingleValueExpirationStorage for time aware entries (expirable api tokens, etc.)
AsyncOperation variants of process methods in NetworkServices.
2023-06-09 10:00:12 +03:00
Ivan Smolin f50bb09ad8 Merge pull request 'feature/async_single_value_storage_map_ui_settings' (#7) from feature/async_single_value_storage_map_ui_settings into master
Reviewed-on: #7
2023-06-07 20:00:41 +03:00
Ivan Smolin 005d80c531 feat: Added UserLocationFetcher helper that requests authorization and subscribes to user location updates 2023-06-05 14:36:45 +03:00
Ivan Smolin 33cc31b957 docs: update podspecs and changelog 2023-05-31 18:19:43 +03:00
Ivan Smolin 19a319f03c build: fix playground and podspec issues 2023-05-31 17:33:22 +03:00
Ivan Smolin 193a060cff feat: add AsyncSingleValueStorage for TINetworkingCache 2023-05-31 17:33:22 +03:00
Ivan Smolin a5bc2dc8f0 feat: current location marker and other ui settings to supported maps 2023-05-31 17:33:22 +03:00
Ivan Smolin 5a74c342d9 Merge pull request 'feature/single_value_storages' (#6) from feature/single_value_storages into master
Reviewed-on: #6
2023-05-31 17:18:13 +03:00
Ivan Smolin 5bb3092726 build: podspec and misc fixes 2023-05-29 12:27:59 +03:00
Ivan Smolin 83a3e5b491 build: fix swiftlint issues 2023-05-26 16:07:40 +03:00
Ivan Smolin 7b9e8b0885 feat: add RecoverableErrorType typealias to DefaultRecoverableNetworkService 2023-05-26 10:49:17 +03:00
Ivan Smolin 529277d098 feat: use DecodingError instead of untyped error in TINetworking decoding 2023-05-25 18:17:49 +03:00
Ivan Smolin ecfb83bafa feat: add TILogging module and TINetworking error logging 2023-05-25 11:42:56 +03:00
Ivan Smolin c0189dc7ae fix: fingerprints update in DefaultFingerprintsProvider 2023-05-24 16:14:39 +03:00
Ivan Smolin a0401bc9fa build: bump podspec versions 2023-05-24 15:51:28 +03:00
Ivan Smolin 43a12e322f feat: add TIKeychainUtils playground with SingleValueStorage examples 2023-05-24 15:50:59 +03:00
Ivan Smolin 5ca564476a feat: `SingleValueStorage` implementations + `AppInstallLifetimeSingleValueStorage` for automatically removing keychain items on app reinstall.
`DefaultRecoverableJsonNetworkService` supports iOS 12.
2023-05-24 10:57:17 +03:00
Ivan Smolin 0e0a8d733e Merge pull request 'feature/http_status_codes_for_error_responses' (#5) from feature/http_status_codes_for_error_responses into master
Reviewed-on: #5
2023-05-24 10:04:07 +03:00
Ivan Smolin 7fd33b6157 build: update build scripts, disable autocorrection dy default 2023-05-24 09:28:20 +03:00
Ivan Smolin 4f0c9a8ed1 build: update min deployment target for Xcode 14 2023-05-22 13:35:04 +03:00
Ivan Smolin e7517c23f8 build: update podspec version 2023-05-22 11:57:27 +03:00
Ivan Smolin 975435bb90 build: update podspecs 2023-05-19 18:30:53 +03:00
Ivan Smolin fd0365a370 fix: code lint issues in TINetworking 2023-05-19 18:23:19 +03:00
Ivan Smolin 4ea76a8499 feat: add SwiftLint 2023-05-19 17:48:08 +03:00
Ivan Smolin 6358386303 feat: added HTTP status codes to `EndpointErrorResult.apiError` responses 2023-05-19 16:30:37 +03:00
Nikita Semenov dd4c9072a9 Merge pull request 'feat: updated gitmodules urls' (#4) from fix/git_modules into master
Reviewed-on: #4
2023-04-27 13:59:21 +03:00
Nikita Semenov 5aff5f99bb docs: bumbed version + changelog update 2023-04-27 13:50:52 +03:00
Nikita Semenov 4fe395d295 feat: updated gitmodules urls 2023-04-27 13:47:58 +03:00
Vladimir Makarov a925f70c04 Merge pull request 'fix: `Antlr4` dependency added to podspec' (#3) from fix/Antlr4_dependency into master
Reviewed-on: #3
2023-04-21 12:17:46 +03:00
Vladimir Makarov 41d18bf3a6 fix: `Antlr4` dependency added to podspec 2023-04-21 11:57:07 +03:00
Vladimir Makarov fc6a735d94 Merge pull request 'TITextProcessing' (#1) from feature/TITextProcessing into master
Reviewed-on: #1
2023-04-21 11:27:15 +03:00
Vladimir Makarov 77ce6a1c95 feat: `Private methods` MARK changed to `Public methods` 2023-04-21 11:23:15 +03:00
Vladimir Makarov cf54d8c798 feat: Protocols, default implementations, playground and markdown added 2023-04-20 12:31:09 +03:00
Vladimir Makarov 93a8ee68f3 `TITextProcessing` for regex and text formatting added 2023-04-19 14:22:05 +03:00
Vladimir Makarov 5a14c61a9a Merge pull request 'fix: Podspecs' source and homepage urls' (#2) from fix/pods_source_url into master
Reviewed-on: #2
2023-04-19 14:00:21 +03:00
Vladimir Makarov 4a9355fd80 fix: Podspecs' source and homepage urls 2023-04-19 13:20:18 +03:00
Nikita Semenov 45e2b9ff19 Merge branch 'feature/deeplink_api' into 'master'
Feature/deeplink api

See merge request touchinstinct/LeadKit!13
2023-04-14 09:56:53 +00:00
Nikita Semenov 26866427a9 fix: source path to TIDeeplink library 2023-04-14 12:55:37 +03:00
Nikita Semenov 26a7578dc8 refactor: minor code style refactoring 2023-04-14 12:09:12 +03:00
Nikita Semenov 0612370587 docs: docs refactoring + extensions reorganization for handler and mapper 2023-04-13 14:53:57 +03:00
Nikita Semenov fe80863656 fix: remove meta doc file 2023-04-12 13:46:35 +03:00
Nikita Semenov 3dd0c34d27 fix: code review notes 2023-04-12 13:35:04 +03:00
Nikita Semenov 56527b6dba fix: code review notes 2023-04-12 13:02:20 +03:00
Nikita Semenov af044aa591 feat: integrate new base navigation deeplink handler 2023-04-04 13:21:50 +03:00
Nikita Semenov c442ee2623 Merge branch 'deeplink_generics_experimential' into feature/deeplink_api
# Conflicts:
#	TIDeepLink/Sources/DeeplinkHandler/BaseNavigationStackDeeplinkHandler.swift
#	TIDeepLink/Sources/DeeplinkHandler/Helpers/UIViewController+DeeplinkHandler.swift
2023-04-04 12:10:21 +03:00
Nikita Semenov 3ccadd07b4 fix: remove tiuielements updates 2023-04-04 12:08:39 +03:00
Nikita Semenov 9a9b57df4b refactor: code refactoring 2023-04-04 12:04:45 +03:00
Ivan Smolin 06b687c47e feat: experiments with deeplink generics 2023-04-03 14:01:12 +03:00
Nikita Semenov a99c29ea73 feat: update findHandler method to findAction 2023-04-01 17:46:13 +03:00
Nikita Semenov 270ac1a4d5 fix: code review notes 2023-03-31 10:19:45 +03:00
Nikita Semenov 22b133648b feat: bumped podspecs versions 2023-03-30 15:19:57 +03:00
Nikita Semenov 2245765b27 Merge branch 'master' into feature/deeplink_api
# Conflicts:
#	CHANGELOG.md
2023-03-30 15:19:27 +03:00
Nikita Semenov 30479ae8b8 Merge branch 'feature/skeletons_status' into 'master'
feat: added callbacks for views when skeletons status changed

See merge request touchinstinct/LeadKit!14
2023-03-30 12:10:12 +00:00
Nikita Semenov 8d253d1458 docs: added documentation for new Skeletonable callbacks API 2023-03-30 15:07:43 +03:00
Nikita Semenov dcc9d23676 docs: small refactoring of documentation 2023-03-30 13:44:30 +03:00
Nikita Semenov d9e4ea5ae8 feat: updated deeplink model + documentation for deeplinks api added 2023-03-30 13:41:06 +03:00
Nikita Semenov 775f95a931 fix: podspecs file for TIUIElements 2023-03-29 15:28:38 +03:00
Nikita Semenov 39bad32c49 fix: changed names of views properties in SkeletonLayer 2023-03-29 11:50:48 +03:00
Nikita Semenov 05a6236425 fix: podfile for TIUIElements 2023-03-29 10:00:22 +03:00
Nikita Semenov 7f45ce0594 Merge branch 'master' into feature/skeletons_status
# Conflicts:
#	CHANGELOG.md
2023-03-29 09:58:28 +03:00
Nikita Semenov c407dabdf5 Merge branch 'master' into feature/deeplink_api
# Conflicts:
#	CHANGELOG.md
2023-03-28 17:42:10 +03:00
Nikita Semenov 29d7a6ca65 Merge branch 'feature/placeholder_api' into 'master'
feat: placeholder api

See merge request touchinstinct/LeadKit!11
2023-03-28 12:22:55 +00:00
Nikita Semenov 2d0819064a Merge branch 'feature/image_placeholder_api' into 'feature/placeholder_api'
feat: placeholder image view

See merge request touchinstinct/LeadKit!12
2023-03-28 12:06:50 +00:00
Nikita Semenov 76da4ab223 feat: added callbacks for views when skeletons status changed 2023-03-28 13:15:07 +03:00
Nikita Semenov 66508d505d feat: bump pods version + changelog updates 2023-03-27 10:36:09 +03:00
Nikita Semenov c8985cde1e Merge branch 'master' into feature/deeplink_api
# Conflicts:
#	LeadKit.podspec
#	Package.swift
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
#	project-scripts/push_to_podspecs.sh
2023-03-27 10:34:04 +03:00
Nikita Semenov 1bc200034c fix: typos 2023-03-20 13:47:55 +03:00
Nikita Semenov cde2420f7d fix: code review notes 2023-03-18 12:51:58 +03:00
Nikita Semenov 026700b4c7 feat: placeholder image view 2023-03-17 16:39:35 +03:00
Nikita Semenov 4ccebe8e8b fix: code review notes 2023-03-17 11:46:44 +03:00
Nikita Semenov f3ed27e83f fix: typo 2023-03-17 11:38:58 +03:00
Nikita Semenov a234943394 fix: code review notes 2023-03-17 11:34:21 +03:00
Nikita Semenov 1374c4df1f feat: update placeholders interactions with opend keyboard 2023-03-17 11:10:07 +03:00
Nikita Semenov 318fd40f0b docs: updated first example of usage PlaceholderFactory 2023-03-16 19:12:46 +03:00
Nikita Semenov 5ebb97de4c fix: applyBaseStyle method signature 2023-03-16 18:09:29 +03:00
Nikita Semenov 507fc8fa05 fix: defaults string names changed by code style 2023-03-16 15:49:55 +03:00
Nikita Semenov 1932262ad5 fix: code review notes 2023-03-16 14:25:42 +03:00
Nikita Semenov 2c23e86852 fix: Constaints usage 2023-03-15 17:24:20 +03:00
Nikita Semenov 164edf9a5d feat: placeholder api 2023-03-15 17:10:10 +03:00
Nikita Semenov 2f71b10dc0 Merge branch 'feature/new_appearance_configurations' into 'master'
feat: new appearance configurations

See merge request touchinstinct/LeadKit!10
2023-03-15 13:55:42 +00:00
Nikita Semenov 9d99d4e4e3 feat: moved corner configuration to UIViewBorder 2023-03-14 18:27:40 +03:00
Nikita Semenov 9cc412208c docs: updated documentation for skeletons 2023-03-14 16:20:18 +03:00
Nikita Semenov a8fc13ff1e fix: CHANGELOG last version number 2023-03-14 15:44:26 +03:00
Nikita Semenov 22fc660e56 fix: code review notes 2023-03-14 15:43:43 +03:00
Nikita Semenov 5d2bea19fb fix: access control for wrappedViewLayout changed to internal 2023-03-14 14:31:37 +03:00
Nikita Semenov e2c9c6c102 feat: new appearance configurations 2023-03-14 14:29:29 +03:00
Vladimir Makarov 332e895659 Merge branch 'feature/maps_placemark_icon_updating' into 'master'
feat: Placemark appearance updating added

See merge request touchinstinct/LeadKit!3
2023-03-13 09:55:44 +00:00
Vladimir Makarov 9dca21afd9 `BaseItemPlacemarkManager` for single placemark managers added 2023-03-13 10:54:37 +01:00
Vladimir Makarov 511c2b9653 Optional for `placemarkPosition` removed 2023-03-13 10:47:35 +01:00
Vladimir Makarov 2b1511657d `BaseClusterPlacemarkManager` inheritance updated, optional casts removed 2023-03-13 10:47:35 +01:00
Vladimir Makarov f91453a065 Libraries versions updated to 1.38.0, CHANGELOG updated 2023-03-13 10:47:35 +01:00
Vladimir Makarov 23b74ec3d5 `CacheKeyProvider` updated 2023-03-13 10:47:35 +01:00
Vladimir Makarov 4c973b393d `BaseClusterPlacemarkManager` added 2023-03-13 10:47:35 +01:00
Vladimir Makarov 0090c83f87 Libraries versions updated to 1.36.2 2023-03-13 10:47:35 +01:00
Vladimir Makarov 144ea7b703 feat: Placemark appearance updating added 2023-03-13 10:47:35 +01:00
Nikita Semenov 278e175f3a Merge branch 'feature/update_TIDeveloperUtils_dependencies' into 'master'
fix: dependencies

See merge request touchinstinct/LeadKit!9
2023-03-13 08:33:52 +00:00
Nikita Semenov 6af9a64135 fix: dependencies 2023-03-13 11:24:12 +03:00
Nikita Semenov efde6153a8 Merge branch 'feature/skeletons_api' into 'master'
Feature/skeletons api

See merge request touchinstinct/LeadKit!1
2023-03-13 07:46:08 +00:00
Nikita Semenov e3ae781f1d fix: typo + small refactoring 2023-03-13 10:42:17 +03:00
Nikita Semenov 23c2cbacea fix: code review notes 2023-03-11 22:07:06 +03:00
Nikita Semenov 40aa2876d1 fix: code review notes 2023-03-09 22:01:45 +03:00
Nikita Semenov 2250b1b4d9 fix: code review notes 2023-03-09 20:51:25 +03:00
Nikita Semenov 19bb08aa66 feat: added new sys of hidding views below the skeletons 2023-03-09 17:49:37 +03:00
Nikita Semenov 597755474c fix: url to source in podspecs 2023-03-09 12:49:56 +03:00
Nikita Semenov cb29a3e9ca fix: code review notes 2023-03-09 12:48:24 +03:00
Nikita Semenov eafb434c88 Merge branch 'feature/skeletons_docs' into 'feature/skeletons_api'
Feature/skeletons docs

See merge request touchinstinct/LeadKit!5
2023-03-07 17:51:06 +00:00
Nikita Semenov cfd5d5f2f8 fix: code review notes 2023-03-07 20:23:58 +03:00
Nikita Semenov 9c8510af14 feat: updated skeletons documentation for new api and code review comment 2023-03-07 18:42:03 +03:00
Nikita Semenov e942d08503 Merge branch 'feature/skeletons_api' into feature/skeletons_docs 2023-03-07 14:42:07 +03:00
Nikita Semenov 2c081d508f Merge branch 'master' into feature/skeletons_api 2023-03-07 14:39:22 +03:00
Nikita Semenov f01644b408 Merge branch 'feature/changed_skeletons_presenting_system' into 'feature/skeletons_api'
feat: removed SkeletonsPresenter and move all logic to extensions of UIView and UIViewController

See merge request touchinstinct/LeadKit!6
2023-03-07 10:08:20 +00:00
Nikita Semenov b533eaaae6 fix: remove useless baseView 2023-03-07 13:00:34 +03:00
Vladimir Makarov 9db353b360 Merge branch 'feature/yandex_min_deployment_target' into 'master'
TIYandexMapUtils min deployment target updated

See merge request touchinstinct/LeadKit!8
2023-03-07 09:55:09 +00:00
Vladimir Makarov e4c84ca511 `TIYandexMapUtils` min deployment target updated 2023-03-07 10:32:35 +01:00
Vladimir Makarov ce2f3ca064 Merge branch 'feature/yandex_maps' into 'master'
YandexMapsMobile version updated, map manager memory leak removed

See merge request touchinstinct/LeadKit!7
2023-03-07 09:13:08 +00:00
Vladimir Makarov cbd38b84e2 Podspec versions updated 2023-03-07 09:44:26 +01:00
Vladimir Makarov c8a4b0bd51 `YandexMapsMobile` version updated, map manager memory leak removed 2023-03-07 09:41:18 +01:00
Nikita Semenov 7a0747843a feat: removed computed @objc properties 2023-03-07 11:38:09 +03:00
Nikita Semenov 343d36cb85 feat: removed SkeletonsPresenter and move all logic to extensions of UIView and UIViewController 2023-03-07 10:11:56 +03:00
Nikita Semenov 29347d77e1 fix: code review notes 2023-03-06 19:27:56 +03:00
Nikita Semenov 64604bdce0 Merge branch 'master' into feature/skeletons_api
# Conflicts:
#	CHANGELOG.md
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIDeveloperUtils/TIDeveloperUtils.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIWebView/TIWebView.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2023-03-06 19:27:47 +03:00
Nikita Semenov e27c844f92 Merge branch 'fix/remove_logger_api' into 'master'
fix: removed custom logger wrapper

See merge request touchinstinct/LeadKit!4
2023-03-06 11:58:52 +00:00
Nikita Semenov 01d99cb246 docs: change release version 2023-03-06 14:57:52 +03:00
Nikita Semenov 48f6655efc fix: logic of font size determination 2023-03-06 14:55:36 +03:00
Nikita Semenov bc9ac01463 fix: compilation of the projects 2023-03-06 13:40:12 +03:00
Nikita Semenov dda06ece3c fix: dependencies of TIDeveloperUtils module 2023-03-06 10:59:55 +03:00
Nikita Semenov 5ea58f3746 feat: update TIDeveloperUtils module, removed TILogger module 2023-03-06 10:47:09 +03:00
Nikita Semenov 2061050c78 fix: removed custom logger wrapper 2023-03-05 17:52:00 +03:00
Nikita Semenov e199cd4220 docs: skeletons playground page 2023-03-05 17:42:45 +03:00
Nikita Semenov 6e30957fe5 docs: skeletons api playground page 2023-03-05 17:41:11 +03:00
Nikita Semenov 84e7093903 Merge branch 'fix/repo_urls' into 'master'
Fix/repo urls

See merge request touchinstinct/LeadKit!2
2023-03-03 16:30:51 +00:00
Nikita Semenov 1da2d4d501 fix: bump version back + update push to podspec script 2023-03-03 17:47:25 +03:00
Nikita Semenov 118bca1c9d feat: added default implementation of to protocol 2023-03-03 14:39:32 +03:00
Nikita Semenov fbab4a491b fix: code review notes 2023-03-03 14:35:46 +03:00
Nikita Semenov fd2fa45909 feat: padding configuration for skeleton layer 2023-03-03 14:01:48 +03:00
Nikita Semenov e471bae469 docs: bumps podspec version of pods 2023-03-02 16:54:47 +03:00
Nikita Semenov 698243ee39 fix: updated url to ti repositories 2023-03-02 16:48:17 +03:00
Nikita Semenov 59ef1093c7 fix: change default shape of UIImageViews 2023-03-01 20:40:33 +03:00
Nikita Semenov 7442884856 fix: minor changes 2023-03-01 20:39:04 +03:00
Nikita Semenov 6e506aa385 feat: updated exporting of environment variable 2023-03-01 19:16:33 +03:00
Nikita Semenov 9f5d7387d7 feat: skeletons api 2023-03-01 19:05:52 +03:00
Ivan Smolin e9b32ce326
Merge pull request #345 from TouchInstinct/fix/push_podspecs_fixups
build: push podspecs fixups
2023-03-01 09:54:44 +03:00
Ivan Smolin a9a8ddde9e build: push podspecs fixups 2023-02-28 16:59:57 +03:00
Ivan Smolin 893f6f191d
Merge pull request #344 from TouchInstinct/feature/documentation_generation
feat: Auto documentation generation
2023-02-28 15:04:08 +03:00
Ivan Smolin 56d1ee998d build: remove --use-cache flag due build failure 2023-02-28 14:56:36 +03:00
Ivan Smolin 55fe6b7126 docs: fix review notes 2023-02-28 14:56:36 +03:00
Ivan Smolin 5f7e0bf273 feat: Auto documentation generation for `TIFoundationUtils` playground and compile checks for playground before release
`AsyncOperation` fixed ordering of chain operations execution
2023-02-28 14:56:36 +03:00
Nikita Semenov 3a321a7fbf
Merge pull request #343 from TouchInstinct/feature/swiftui_previews
feat: extensions for SwiftUI previews
2023-02-21 09:04:31 +03:00
Nikita Semenov 275afb655f fix: push to podspec update 2023-02-15 12:46:29 +03:00
Nikita Semenov f3c5002f4e feat: added dashed layer support for UIViewController 2023-02-14 20:12:33 +03:00
Nikita Semenov 3406962d21 docs: updated release version in changelog 2023-02-14 19:54:14 +03:00
Nikita Semenov f1d5b27f3d feat: DashedBoundsLayer for debugging frames of the views visually 2023-02-14 19:53:14 +03:00
Nikita Semenov f4a516bf86 feat: rebase preview extensions to new framework TIDeveloperUtils 2023-02-14 17:52:20 +03:00
Nikita Semenov 46be3ce9de feat: extensions for SwiftUI previews 2023-02-14 16:34:46 +03:00
Nikita Semenov 30c5b72b26
Merge pull request #342 from TouchInstinct/feature/base_views
Feature/base views
2023-02-14 14:34:29 +03:00
Nikita Semenov f67005df71
Merge pull request #339 from TouchInstinct/feature/appearance_customization_model
feat: api for configuration Views' appearance and layout
2023-02-14 11:51:55 +03:00
Nikita Semenov 05fa3dad31
Merge pull request #340 from TouchInstinct/feature/view_containers
feat: container views
2023-02-14 11:39:28 +03:00
Nikita Semenov 0c3b987370 fix: code review notes 2023-02-13 21:19:35 +03:00
Nikita Semenov 7765c01074 fix: code review notes 2023-02-12 18:35:28 +03:00
Nikita Semenov f2c390f71a feat: updated method to hide and show subtitle of DefaultTitleSubtitleView 2023-02-12 18:15:31 +03:00
Nikita Semenov 7e41552521 feat: DefaultTitleSubtileView, BaseListItemView, StatefulButton appearance configuration update 2023-02-11 21:22:03 +03:00
Nikita Semenov bd7d31cf67 fix: code review notes 2023-02-10 17:53:21 +03:00
Nikita Semenov 4e1270205e docs: added change log information 2023-02-10 16:28:53 +03:00
Nikita Semenov c5209dc9f6 feat: container views 2023-02-10 15:27:35 +03:00
Nikita Semenov 407995db35 Merge branch 'master' into feature/deeplink_api
# Conflicts:
#	LeadKit.podspec
#	TIAppleMapUtils/TIAppleMapUtils.podspec
#	TIAuth/TIAuth.podspec
#	TIEcommerce/TIEcommerce.podspec
#	TIFoundationUtils/TIFoundationUtils.podspec
#	TIGoogleMapUtils/TIGoogleMapUtils.podspec
#	TIKeychainUtils/TIKeychainUtils.podspec
#	TILogging/TILogging.podspec
#	TIMapUtils/TIMapUtils.podspec
#	TIMoyaNetworking/TIMoyaNetworking.podspec
#	TINetworking/TINetworking.podspec
#	TINetworkingCache/TINetworkingCache.podspec
#	TIPagination/TIPagination.podspec
#	TISwiftUICore/TISwiftUICore.podspec
#	TISwiftUtils/TISwiftUtils.podspec
#	TITableKitUtils/TITableKitUtils.podspec
#	TITransitions/TITransitions.podspec
#	TIUIElements/TIUIElements.podspec
#	TIUIKitCore/TIUIKitCore.podspec
#	TIYandexMapUtils/TIYandexMapUtils.podspec
2023-01-22 20:11:32 +03:00
Nikita Semenov 63777fef99 fix: update an approach to handling operation in TIDeeplinkService 2023-01-22 20:09:24 +03:00
Nikita Semenov caeded9561 fix: searching deeplink handler in navigation stack 2023-01-11 13:25:20 +03:00
Nikita Semenov 3d5aa7a41d fix: remove default implementation of deeplink handler in service object 2023-01-11 12:02:07 +03:00
Nikita Semenov f885183499 fix: updated push to podspecs script 2023-01-10 19:27:53 +03:00
Nikita Semenov aff54859eb feat: complete deeplink api 2023-01-10 19:26:46 +03:00
Nikita Semenov a7e44a3d9a feat: initial deep link settings 2023-01-06 07:20:10 +07:00
506 changed files with 33225 additions and 1716 deletions

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "build-scripts"] [submodule "build-scripts"]
path = build-scripts path = build-scripts
url = https://github.com/TouchInstinct/BuildScripts.git url = https://git.svc.touchin.ru/TouchInstinct/BuildScripts.git

View File

@ -1,9 +1,208 @@
# Changelog # Changelog
### 1.56.0
- **Update**: `ViewSkeletonsConfiguration`. It's possible to enable or disable animation for specific skeletons now.
- **Added**: `HolderViewSkeletonsConfiguration` for skeleton root view configuration
- **Added**: `DashedBoundsLayer` can now be applied to `CALayer`
### 1.55.1
- **Update**: revert `TextSkeletonsConfiguration` line height calculation
### 1.55.0
- **Update**: use TouchInstinct `TableKit` fork instead of original one
- **Update**: remove default value from `BoolValueDefaultsStorage`
### 1.54.6
- **Added**: `xcprivacy` files
- **Update**: Correctly detect app reinstall in `AppInstallLifetimeSingleValueStorage`
- **Update**: use `xHeight` instead of `pointSize` for default skeleton line height calculation
- **Update**: update `linkTextAttributes` in `UITextView` when setting interactive url parts
### 1.54.5
- **Update**: Сhange `StatefulButton` event propogation avoidance method.
### 1.54.4
- **Update**: Fix `StatefulButton` state configuration for iOS 15+.
### 1.54.3
- **Update**: Set reasonable defaults for `SkeletonConfiguration`.
### 1.54.2
- **Update**: Changed access level from internal to public of title and subtitle view in `BaseTitleSubtitleView`.
### 1.54.1
- **Added**: `BaseTitleSubtitleView` which can be inherited for fine-tuning skeletons and other behavior.
- **Update**: Changed lines number calculation method in `TextSkeletonsConfiguration`.
### 1.54.0
- **Added**: `maxWidth` parameter to `BaseViewSkeletonsConfiguration`.
- **Added**: custom `SkeletonConfigurations` for nested `SkeletonPresenters`.
- **Update**: Many fixes and improvenments to `TextSkeletonsConfiguration`.
### 1.53.3
- **Update**: `Skeletonable` can now control custom geometry change notification.
- **Update**: Filter hidden views from skeletonable views by default.
### 1.53.2
- **Update**: `DefaultTitleSubtitleView` support for separated configuration of title and subtitle labels layout.
- **Update**: `BaseListItemView` fixed trailing insets when trailing view is hidden.
### 1.53.1
- **Update**: Insets layout heuristics for `WrappedViewHodler` implementations
### 1.53.0
- **Added**: Custom string attributes to `BaseTextAttributes`
- **Added**: Customizeable `UIViewBackground` and `UIViewBorder` for `UIView.Appearance`
- **Added**: Keychain single value storage for codable models -`CodableSingleValueKeychainStorage`
- **Update**: Renamed methods `startAnimation` and `stopAnimation` of `SkeletonPresenter`, so it won't conflict with `Animatable` protocol anymore
### 1.52.0
- **Added**: `TIApplication` module with core dependencies of main application and its extension targets
- **Added**: `DefaultHomogeneousItemsCollectionView` default collection view implementation with configurable identical-type cells
- **Update**: Changed implementation of `AppInstallLifetimeSingleValueStorage`. Now it uses `SingleValueStorage<Bool>` to be able to migrate stored UserDefaults values
- **Added**: `UserLocationFetcher.OnLocationFetchFailureCallback` and `ItemDistanceTo` in `TIMapUtils`
- **Added**: Tap handler closure to `DefaultConfigurableStatefulButton.ViewModel`
- **Added**: Universal DSL
### 1.51.0
- **Added**: `BaseModalViewController` implementing `PanModalPresentable` with additional functionality
- **Added**: `BaseModalWrapperViewController` for wrapping `UIViewController`s with `BaseModalViewController` functionality
### 1.50.0
- **Updated**: Fix activity indicator positioning for `StatefulButton` on iOS 15+ and disabled state touch handling
- **Added**: iOS 15+ activity indicator placement support in `StatefulButton`
- **Added**: `TICoreGraphicsUtils` module for drawing operations and other CoreGraphics related functionality
- **Update**: `MarkerIconFactory` can now return optional `UIImage`. In this case MapManagers will show the default marker icon.
### 1.49.0
- **Added**: `BaseMigratingSingleValueKeychainStorage` and `BaseMigratingSingleValueDefaultsStorage` implementations for migrating keys from one storage to another.
### 1.48.0
- **Added**: `BaseStackView` with configurable items appearance
- **Fixed**: `CollectionTableViewCell` self-sizing
- **Added**: `ViewAppearance.WrappedViewLayout` support for all `WrappedViewHolders`
- **Added**: `ViewCallbacks` support for all `BaseInitializeableViews`
### 1.47.0
- **Added**: `flatMap` operator for `AsyncOperation`
- **Update**: `CodableKeyValueStorage` now returns `Swift.Result` with typed errors.
- **Added**: `SingleValueExpirationStorage` for time aware entries (expirable api tokens, etc.)
- **Added**: `AsyncOperation` variants of process methods in NetworkServices.
### 1.46.0
- **Added**: `AsyncSingleValueStorage` and `SingleValueStorageAsyncWrapper<SingleValueStorage>` for async access to SingleValue storages wtih swift concurrency support
- **Added**: `BaseMapUISettings` used to configure map view of different providers + user location icon rendering for yandex maps
- **Added**: `UserLocationFetcher` helper that requests authorization and subscribes to user location updates
- **Update**: add `DEVELOPMENT_INSTALL` support for all podspecs and fix playground compilation issues
### 1.45.0
- **Added**: `SingleValueStorage` implementations + `AppInstallLifetimeSingleValueStorage` for automatically removing keychain items on app reinstall.
- **Added**: `TILogging` with error logging types
- **Update**: `DefaultRecoverableJsonNetworkService` supports iOS 12.
- **Update**: `DefaultFingerprintsProvider` now uses `SingleValueStorage`
### 1.44.0
- **Added**: HTTP status codes to `EndpointErrorResult.apiError` responses
- **Added**: SwiftLint pre-build SPM step to TINetworking module
### 1.43.1
- **Fixed**: build scripts submodule url
### 1.43.0
- **Added**: `TITextProcessing` for regex and text formatting added
### 1.42.1
- **Fixed**: Podspecs source and homepage urls
### 1.42.0
- **Added**: TIDeeplink to support deeplink API
### 1.41.0
- **Update**: added callbacks for views while skeletons change status to presented or hidden
### 1.40.0
- **Added**: `PlaceholderFactory` for creating `DefaultPlaceholderView` views
- **Added**: `DefaultPlaceholderImageView`
### 1.39.0
- **Added**: UIButton Appearance model
- **Added**: `SpacedWrappedViewLayout` for spacing configurations
- **Update**: UIView appearance model with border configurations
### 1.38.0
- **Added**: Placemarks states for icon updating
- **Added**: Selecting / deselecting markers through cluster manager
### 1.37.0
- **Added**: API for converting view hierarchy to skeletons
### 1.36.1
- **Update**: `YandexMapsMobile` version updated
- **Fix**: Map manager memory leak removed
### 1.36.0
- **Removed**: `TILogger`module
- **Updated**: moved `LoggingPresenter` to `TIDeveloperUtils` module.
### 1.35.1
- **Added**: Auto documentation generation for `TIFoundationUtils` playground and compile checks for playground before release
- **Updated**: `AsyncOperation` fixed ordering of chain operations execution
### 1.35.0
- **Added**: `TIDeveloperUtils` framework, that contains different utils for development
- **Added**: `UIView` and `UIViewController` extensions for showing SwiftUI previews
- **Added**: `DashedBoundsLayer` for debugging views' frames visually
### 1.34.0
- **Added**: `BaseListItemView` for displaying three views horizontally
- **Added**: `DefaultTitleSubtitleView` for displaying one or two labels vertically
- **Update**: `StatefulButton` now can be configured with `ViewAppearance` model for each state
### 1.33.0 ### 1.33.0
- **Added**: `ViewAppearance` and `ViewLayout` models for setting up Views' appearance and layout - **Added**: `ViewAppearance` and `ViewLayout` models for setting up Views' appearance and layout
- **Added**: `TableKit.Row` extension for configuration inner View's appearance and layout - **Added**: `TableKit.Row` extension for configuration inner View's appearance and layout
- **Added**: `WrappableView` with typealiases for creating wrapped in the container views
- **Added**: `CollectionTableViewCell` and `ContainerView`
- **Update**: Separator appearance configureation for table views
### 1.32.0 ### 1.32.0

View File

@ -89,9 +89,10 @@ GEM
PLATFORMS PLATFORMS
x86_64-darwin-20 x86_64-darwin-20
x86_64-darwin-21
DEPENDENCIES DEPENDENCIES
cocoapods (~> 1.11) cocoapods (~> 1.11)
BUNDLED WITH BUNDLED WITH
2.3.10 2.3.26

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = "LeadKit" s.name = "LeadKit"
s.version = "1.33.0" s.version = "1.35.0"
s.summary = "iOS framework with a bunch of tools for rapid development" s.summary = "iOS framework with a bunch of tools for rapid development"
s.homepage = "https://github.com/TouchInstinct/LeadKit" s.homepage = "https://git.svc.touchin.ru/TouchInstinct/LeadKit"
s.license = "Apache License, Version 2.0" s.license = "Apache License, Version 2.0"
s.author = "Touch Instinct" s.author = "Touch Instinct"
s.source = { :git => "https://github.com/TouchInstinct/LeadKit.git", :tag => s.version } s.source = { :git => "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", :tag => s.version }
s.platform = :ios, '10.0' s.platform = :ios, '10.0'
s.swift_versions = ['5.1'] s.swift_versions = ['5.1']

107
Makefile Normal file
View File

@ -0,0 +1,107 @@
export SRCROOT := $(shell pwd)
push_to_podspecs: TISwiftUtils.target TIFoundationUtils.target TICoreGraphicsUtils.target TIKeychainUtils.target TIUIKitCore.target TIUIElements.target TIWebView.target TIBottomSheet.target TISwiftUICore.target TITableKitUtils.target TIDeeplink.target TIDeveloperUtils.target TILogging.target TINetworking.target TIMoyaNetworking.target TINetworkingCache.target TIMapUtils.target TIAppleMapUtils.target TIGoogleMapUtils.target TIPagination.target TIAuth.target TIEcommerce.target TITextProcessing.target TIApplication.target
$(call clean)
TISwiftUtils.target:
MODULE_NAME="TISwiftUtils" ./project-scripts/push_to_podspecs.sh
touch TISwiftUtils.target
TIFoundationUtils.target: TISwiftUtils.target TILogging.target
MODULE_NAME="TIFoundationUtils" ./project-scripts/push_to_podspecs.sh
touch TIFoundationUtils.target
TICoreGraphicsUtils.target:
MODULE_NAME="TICoreGraphicsUtils" ./project-scripts/push_to_podspecs.sh
touch TICoreGraphicsUtils.target
TIKeychainUtils.target: TIFoundationUtils.target
MODULE_NAME="TIKeychainUtils" ./project-scripts/push_to_podspecs.sh
touch TIKeychainUtils.target
TIUIKitCore.target: TISwiftUtils.target
MODULE_NAME="TIUIKitCore" ./project-scripts/push_to_podspecs.sh
touch TIUIKitCore.target
TIUIElements.target: TIUIKitCore.target TILogging.target
MODULE_NAME="TIUIElements" ./project-scripts/push_to_podspecs.sh
touch TIUIElements.target
TIWebView.target: TIUIKitCore.target
MODULE_NAME="TIWebView" ./project-scripts/push_to_podspecs.sh
touch TIWebView.target
TIBottomSheet.target: TIUIElements.target
MODULE_NAME="TIBottomSheet" ./project-scripts/push_to_podspecs.sh
touch TIBottomSheet.target
TISwiftUICore.target: TIUIKitCore.target
MODULE_NAME="TISwiftUICore" ./project-scripts/push_to_podspecs.sh
touch TISwiftUICore.target
TITableKitUtils.target: TIUIElements.target
MODULE_NAME="TITableKitUtils" ./project-scripts/push_to_podspecs.sh
touch TITableKitUtils.target
TIDeeplink.target: TIFoundationUtils.target
MODULE_NAME="TIDeeplink" ./project-scripts/push_to_podspecs.sh
touch TIDeeplink.target
TIDeveloperUtils.target: TIUIElements.target
MODULE_NAME="TIDeveloperUtils" ./project-scripts/push_to_podspecs.sh
touch TIDeveloperUtils.target
TINetworking.target: TIFoundationUtils.target
MODULE_NAME="TINetworking" ./project-scripts/push_to_podspecs.sh
touch TINetworking.target
TILogging.target:
MODULE_NAME="TILogging" ./project-scripts/push_to_podspecs.sh
touch TILogging.target
TIMoyaNetworking.target: TINetworking.target
MODULE_NAME="TIMoyaNetworking" ./project-scripts/push_to_podspecs.sh
touch TIMoyaNetworking.target
TINetworkingCache.target: TINetworking.target
MODULE_NAME="TINetworkingCache" ./project-scripts/push_to_podspecs.sh
touch TINetworkingCache.target
TIMapUtils.target: TILogging TICoreGraphicsUtils.target
MODULE_NAME="TIMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIMapUtils.target
TIAppleMapUtils.target: TIMapUtils.target
MODULE_NAME="TIAppleMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIAppleMapUtils.target
TIGoogleMapUtils.target: TIMapUtils.target
MODULE_NAME="TIGoogleMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIGoogleMapUtils.target
TIYandexMapUtils.target: TIMapUtils.target
MODULE_NAME="TIYandexMapUtils" ./project-scripts/push_to_podspecs.sh
touch TIYandexMapUtils.target
TIPagination.target: TISwiftUtils.target
MODULE_NAME="TIPagination" ./project-scripts/push_to_podspecs.sh
touch TIPagination.target
TIAuth.target: TIUIKitCore.target TIKeychainUtils.target
MODULE_NAME="TIAuth" ./project-scripts/push_to_podspecs.sh
touch TIAuth.target
TIEcommerce.target: TINetworking.target TIUIElements.target
MODULE_NAME="TIEcommerce" ./project-scripts/push_to_podspecs.sh
touch TIEcommerce.target
TITextProcessing.target:
MODULE_NAME="TITextProcessing" ./project-scripts/push_to_podspecs.sh
touch TITextProcessing.target
TIApplication.target: TIFoundationUtils.target TILogging.target
MODULE_NAME="TIApplication" ./project-scripts/push_to_podspecs.sh
touch TIApplication.target
clean:
rm *.target

View File

@ -1,79 +1,95 @@
{ {
"object": { "pins" : [
"pins": [ {
{ "identity" : "alamofire",
"package": "Alamofire", "kind" : "remoteSourceControl",
"repositoryURL": "https://github.com/Alamofire/Alamofire.git", "location" : "https://github.com/Alamofire/Alamofire.git",
"state": { "state" : {
"branch": null, "revision" : "bc268c28fb170f494de9e9927c371b8342979ece",
"revision": "f96b619bcb2383b43d898402283924b80e2c4bae", "version" : "5.7.1"
"version": "5.4.3"
}
},
{
"package": "Cache",
"repositoryURL": "https://github.com/hyperoslo/Cache.git",
"state": {
"branch": null,
"revision": "c7f4d633049c3bd649a353bad36f6c17e9df085f",
"version": "6.0.0"
}
},
{
"package": "Cursors",
"repositoryURL": "https://github.com/petropavel13/Cursors",
"state": {
"branch": null,
"revision": "a1561869135e72832eff3b1e729075c56c2eebf6",
"version": "0.5.1"
}
},
{
"package": "KeychainAccess",
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state": {
"branch": null,
"revision": "84e546727d66f1adc5439debad16270d0fdd04e7",
"version": "4.2.2"
}
},
{
"package": "Moya",
"repositoryURL": "https://github.com/Moya/Moya.git",
"state": {
"branch": null,
"revision": "9b906860e3c3c09032879465c471e6375829593f",
"version": "15.0.0"
}
},
{
"package": "ReactiveSwift",
"repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state": {
"branch": null,
"revision": "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
"version": "6.7.0"
}
},
{
"package": "RxSwift",
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
"state": {
"branch": null,
"revision": "b4307ba0b6425c0ba4178e138799946c3da594f8",
"version": "6.5.0"
}
},
{
"package": "TableKit",
"repositoryURL": "https://github.com/maxsokolov/TableKit.git",
"state": {
"branch": null,
"revision": "8bf4840d9d0475a92352f02f368f88b74eced447",
"version": "2.11.0"
}
} }
] },
}, {
"version": 1 "identity" : "antlr4",
"kind" : "remoteSourceControl",
"location" : "https://github.com/antlr/antlr4",
"state" : {
"revision" : "44d87bc1d130c88aa452894aa5f7e2f710f68253",
"version" : "4.10.1"
}
},
{
"identity" : "cache",
"kind" : "remoteSourceControl",
"location" : "https://github.com/hyperoslo/Cache.git",
"state" : {
"revision" : "c7f4d633049c3bd649a353bad36f6c17e9df085f",
"version" : "6.0.0"
}
},
{
"identity" : "cursors",
"kind" : "remoteSourceControl",
"location" : "https://github.com/petropavel13/Cursors",
"state" : {
"revision" : "52f27b82cb1cbbc2b5fd09514c48b9c75e3b0300",
"version" : "0.6.0"
}
},
{
"identity" : "keychainaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state" : {
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
"version" : "4.2.2"
}
},
{
"identity" : "moya",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Moya/Moya.git",
"state" : {
"revision" : "c263811c1f3dbf002be9bd83107f7cdc38992b26",
"version" : "15.0.3"
}
},
{
"identity" : "panmodal",
"kind" : "remoteSourceControl",
"location" : "https://git.svc.touchin.ru/TouchInstinct/PanModal",
"state" : {
"revision" : "ced7c1703f90746df0224b6e0d33c146d9ae4284",
"version" : "1.3.1"
}
},
{
"identity" : "reactiveswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state" : {
"revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c",
"version" : "6.7.0"
}
},
{
"identity" : "rxswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveX/RxSwift.git",
"state" : {
"revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4",
"version" : "6.6.0"
}
},
{
"identity" : "tablekit",
"kind" : "remoteSourceControl",
"location" : "https://git.svc.touchin.ru/TouchInstinct/TableKit.git",
"state" : {
"revision" : "fec9537745799fab55df7477cb3ec2b4ea5c254d",
"version" : "2.12.0"
}
}
],
"version" : 2
} }

View File

@ -1,27 +1,39 @@
// swift-tools-version:5.1 // swift-tools-version:5.7
#if canImport(PackageDescription)
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "LeadKit", name: "LeadKit",
platforms: [ platforms: [
.iOS(.v11) .iOS(.v12)
], ],
products: [ products: [
// MARK: - Application
.library(name: "TIApplication", targets: ["TIApplication"]),
// MARK: - UIKit // MARK: - UIKit
.library(name: "TIUIKitCore", targets: ["TIUIKitCore"]), .library(name: "TIUIKitCore", targets: ["TIUIKitCore"]),
.library(name: "TIUIElements", targets: ["TIUIElements"]), .library(name: "TIUIElements", targets: ["TIUIElements"]),
.library(name: "TIWebView", targets: ["TIWebView"]), .library(name: "TIWebView", targets: ["TIWebView"]),
.library(name: "TIBottomSheet", targets: ["TIBottomSheet"]),
// MARK: - SwiftUI // MARK: - SwiftUI
.library(name: "TISwiftUICore", targets: ["TISwiftUICore"]), .library(name: "TISwiftUICore", targets: ["TISwiftUICore"]),
// MARK: - Utils // MARK: - Utils
.library(name: "TISwiftUtils", targets: ["TISwiftUtils"]), .library(name: "TISwiftUtils", targets: ["TISwiftUtils"]),
.library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]), .library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]),
.library(name: "TICoreGraphicsUtils", targets: ["TICoreGraphicsUtils"]),
.library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]), .library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]),
.library(name: "TITableKitUtils", targets: ["TITableKitUtils"]), .library(name: "TITableKitUtils", targets: ["TITableKitUtils"]),
.library(name: "TILogging", targets: ["TILogging"]), .library(name: "TIDeeplink", targets: ["TIDeeplink"]),
.library(name: "TIDeveloperUtils", targets: ["TIDeveloperUtils"]),
// MARK: - Networking // MARK: - Networking
@ -33,62 +45,154 @@ let package = Package(
.library(name: "TIMapUtils", targets: ["TIMapUtils"]), .library(name: "TIMapUtils", targets: ["TIMapUtils"]),
.library(name: "TIAppleMapUtils", targets: ["TIAppleMapUtils"]), .library(name: "TIAppleMapUtils", targets: ["TIAppleMapUtils"]),
// MARK: - Elements // MARK: - Elements
.library(name: "OTPSwiftView", targets: ["OTPSwiftView"]), .library(name: "OTPSwiftView", targets: ["OTPSwiftView"]),
.library(name: "TITransitions", targets: ["TITransitions"]), .library(name: "TITransitions", targets: ["TITransitions"]),
.library(name: "TIPagination", targets: ["TIPagination"]), .library(name: "TIPagination", targets: ["TIPagination"]),
.library(name: "TIAuth", targets: ["TIAuth"]), .library(name: "TIAuth", targets: ["TIAuth"]),
.library(name: "TIEcommerce", targets: ["TIEcommerce"]),
//MARK: - Skolkovo .library(name: "TITextProcessing", targets: ["TITextProcessing"])
.library(name: "TIEcommerce", targets: ["TIEcommerce"])
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/maxsokolov/TableKit.git", .upToNextMajor(from: "2.11.0")), .package(url: "https://git.svc.touchin.ru/TouchInstinct/TableKit.git", .upToNextMinor(from: "2.12.0")),
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMajor(from: "4.2.2")), .package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMajor(from: "4.2.2")),
.package(url: "https://github.com/petropavel13/Cursors", .upToNextMajor(from: "0.5.1")), .package(url: "https://github.com/petropavel13/Cursors", .upToNextMajor(from: "0.5.1")),
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.4.0")), .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.4.0")),
.package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0")), .package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.0")),
.package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.0")) .package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "6.0.0")),
.package(url: "https://github.com/antlr/antlr4", .upToNextMinor(from: "4.10.1")),
.package(url: "https://git.svc.touchin.ru/TouchInstinct/PanModal", .upToNextMinor(from: "1.3.0"))
], ],
targets: [ targets: [
// MARK: - Application architecture
.target(name: "TIApplication",
dependencies: ["TILogging", "TIFoundationUtils", "KeychainAccess"],
path: "TIApplication/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - UIKit // MARK: - UIKit
.target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"), .target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"),
.target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"),
.target(name: "TIUIElements",
dependencies: ["TIUIKitCore", "TILogging"],
path: "TIUIElements/Sources",
exclude: ["../TIUIElements.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIWebView/Sources"), .target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIWebView/Sources"),
.target(name: "TIBottomSheet",
dependencies: ["PanModal", "TIUIElements", "TIUIKitCore", "TISwiftUtils"],
path: "TIBottomSheet/Sources",
exclude: ["../TIBottomSheet.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - SwiftUI // MARK: - SwiftUI
.target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"),
.target(name: "TISwiftUICore",
dependencies: ["TIUIKitCore", "TISwiftUtils"],
path: "TISwiftUICore/Sources"),
// MARK: - Utils // MARK: - Utils
.target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"),
.target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils"), .target(name: "TISwiftUtils",
.target(name: "TIKeychainUtils", dependencies: ["TIFoundationUtils", "KeychainAccess"], path: "TIKeychainUtils/Sources"), path: "TISwiftUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIFoundationUtils",
dependencies: ["TISwiftUtils", "TILogging"],
path: "TIFoundationUtils",
exclude: ["TIFoundationUtils.app"],
resources: [
.copy("PrivacyInfo.xcprivacy"),
],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TICoreGraphicsUtils",
dependencies: [],
path: "TICoreGraphicsUtils/Sources",
exclude: ["../TICoreGraphicsUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIKeychainUtils",
dependencies: ["TIFoundationUtils", "KeychainAccess"],
path: "TIKeychainUtils/Sources",
exclude: ["../TIKeychainUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"), .target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"),
.target(name: "TILogging", dependencies: ["TIUIElements", "TISwiftUtils", "TIUIKitCore"], path: "TILogging/Sources"), .target(name: "TIDeeplink", dependencies: ["TIFoundationUtils"], path: "TIDeeplink", exclude: ["TIDeeplink.app"]),
.target(name: "TIDeveloperUtils", dependencies: ["TISwiftUtils", "TIUIKitCore", "TIUIElements"], path: "TIDeveloperUtils/Sources"),
.target(name: "TILogging", path: "TILogging/Sources", plugins: ["TISwiftLintPlugin"]),
// MARK: - Networking // MARK: - Networking
.target(name: "TINetworking", dependencies: ["TIFoundationUtils", "Alamofire"], path: "TINetworking/Sources"),
.target(name: "TIMoyaNetworking", dependencies: ["TINetworking", "TIFoundationUtils", "Moya"], path: "TIMoyaNetworking"), .target(name: "TINetworking",
.target(name: "TINetworkingCache", dependencies: ["TIFoundationUtils", "TINetworking", "Cache"], path: "TINetworkingCache/Sources"), dependencies: ["TIFoundationUtils", "Alamofire", "TILogging"],
path: "TINetworking/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIMoyaNetworking",
dependencies: ["TINetworking", "Moya"],
path: "TIMoyaNetworking/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TINetworkingCache",
dependencies: ["TINetworking", "Cache"],
path: "TINetworkingCache/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - Maps // MARK: - Maps
.target(name: "TIMapUtils", dependencies: [], path: "TIMapUtils/Sources"),
.target(name: "TIAppleMapUtils", dependencies: ["TIMapUtils"], path: "TIAppleMapUtils/Sources"), .target(name: "TIMapUtils",
dependencies: ["TILogging", "TICoreGraphicsUtils"],
path: "TIMapUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIAppleMapUtils",
dependencies: ["TIMapUtils"],
path: "TIAppleMapUtils/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),
// MARK: - Elements // MARK: - Elements
.target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"), .target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources"),
.target(name: "TITransitions", path: "TITransitions/Sources"), .target(name: "TITransitions", path: "TITransitions/Sources"),
.target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"), .target(name: "TIPagination", dependencies: ["Cursors", "TISwiftUtils"], path: "TIPagination/Sources"),
.target(name: "TIAuth", dependencies: ["TIFoundationUtils", "TIUIKitCore", "KeychainAccess"], path: "TIAuth/Sources"), .target(name: "TIAuth", dependencies: ["TIUIKitCore", "TIKeychainUtils"], path: "TIAuth/Sources"),
.target(name: "TIEcommerce", dependencies: ["TIFoundationUtils", "TISwiftUtils", "TINetworking", "TIUIKitCore", "TIUIElements"], path: "TIEcommerce/Sources"), .target(name: "TIEcommerce", dependencies: ["TIFoundationUtils", "TISwiftUtils", "TINetworking", "TIUIKitCore", "TIUIElements"], path: "TIEcommerce/Sources"),
.target(name: "TITextProcessing",
dependencies: [.product(name: "Antlr4", package: "antlr4")],
path: "TITextProcessing/Sources",
exclude: ["../TITextProcessing.app"]),
.binaryTarget(name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.52.2/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "89651e1c87fb62faf076ef785a5b1af7f43570b2b74c6773526e0d5114e0578e"),
.plugin(name: "TISwiftLintPlugin",
capability: .buildTool(),
dependencies: ["SwiftLintBinary"]),
// MARK: - Tests // MARK: - Tests
.testTarget( .testTarget(
name: "TITimerTests", name: "TITimerTests",
dependencies: ["TIFoundationUtils"], dependencies: ["TIFoundationUtils"],
path: "Tests/TITimerTests"), path: "Tests/TITimerTests"),
.testTarget(
name: "TITextProcessingTests",
dependencies: ["TITextProcessing"],
path: "Tests/TITextProcessingTests"),
.testTarget(
name: "TIFoundationUtilsTests",
dependencies: ["TIFoundationUtils", "TISwiftUtils", "TILogging"],
path: "Tests/TIFoundationUtilsTests")
] ]
) )
#endif

View File

@ -0,0 +1,57 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import PackagePlugin
import Foundation
@main
struct SwiftLintPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
let swiftlintScriptPath = context.package.directory.appending(["build-scripts", "xcode", "build_phases", "swiftlint.sh"])
let swiftlintExecutablePath = try context.tool(named: "swiftlint").path
let srcRoot = context.package.directory.string
let targetDir = target.directory.string
let relativeTargetDir = targetDir.replacingOccurrences(of: srcRoot, with: "")
let clearRelativeTargetDir = relativeTargetDir[relativeTargetDir.index(after: relativeTargetDir.startIndex)...] // trim leading /
return [
.prebuildCommand(displayName: "SwiftLint linting \(target.name)...",
executable: swiftlintScriptPath,
arguments: [
swiftlintExecutablePath,
context.package.directory.appending(subpath: "swiftlint_base.yml")
],
environment: [
"SCRIPT_DIR": swiftlintScriptPath.removingLastComponent().string,
"SRCROOT": srcRoot,
"SCRIPT_INPUT_FILE_COUNT": "1",
"SCRIPT_INPUT_FILE_0": clearRelativeTargetDir,
// "FORCE_LINT": "1", // Lint all files in target (not only modified)
// "AUTOCORRECT": "1"
],
outputFilesDirectory: context.package.directory)
]
}
}

View File

@ -20,8 +20,86 @@ This repository contains the following frameworks:
- [TIYandexMapUtils](TIYandexMapUtils) - set of helpers for map objects clustering and interacting using Yandex Maps SDK. - [TIYandexMapUtils](TIYandexMapUtils) - set of helpers for map objects clustering and interacting using Yandex Maps SDK.
- [TIAuth](TIAuth) - login, registration, confirmation and other related actions - [TIAuth](TIAuth) - login, registration, confirmation and other related actions
Useful docs: ## Playgrounds
### Create new Playground
```sh
$ cd TIModuleName
$ touch PlaygroundPodfile
$ echo "ENV[\"DEVELOPMENT_INSTALL\"] = \"true\"
target 'TIModuleName' do
platform :ios, IOS_VERSION_NUMBER
use_frameworks!
pod 'TIDependencyModuleName', :path => '../../../../TIDependencyModuleName/TIDependencyModuleName.podspec'
pod 'TIModuleName', :path => '../../../../TIModuleName/TIModuleName.podspec'
end" > PlaygroundPodfile
$ nef playground --name TIModuleName --cocoapods --custom-podfile PlaygroundPodfile
```
See example of `PlaygroundPodfile` in `TIFoundationUtils`
### Rename/add pages to Playground
For every new feature in module create new Playground page with documentation in comments. See [nef markdown documentation](https://github.com/bow-swift/nef#-generating-a-markdown-project).
### Create symlink to nef playground
```sh
$ cd TIModuleName
$ ln -s TIModuleName.app/Contents/MacOS/TIModuleName.playground TIModuleName.playground
```
### Add nef files to TIModuleName.app/.gitignore
```
# gitignore nef files
**/build/
**/nef/
LICENSE
```
### Exclude .app bundles from package sources
#### SPM
```swift
.target(name: "TIModuleName", dependencies: ..., path: ..., exclude: ["TIModuleName.app"]),
```
#### Podspec
```ruby
sources = 'your_sources_expression'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
```
## Docs:
- [TIFoundationUtils](docs/tifoundationutils)
* [AsyncOperation](docs/tifoundationutils/asyncoperation.md)
- [TICoreGraphicsUtils](docs/ticoregraphicsutils)
* [DrawingOperations](docs/ticoregraphicsutils/drawingoperations.md)
- [TIKeychainUtils](docs/tikeychainutils)
* [SingleValueStorage](docs/tikeychainutils/singlevaluestorage.md)
- [TIUIElements](docs/tiuielements)
* [Skeletons](docs/tiuielements/skeletons.md)
* [Placeholders](docs/tiuielements/placeholder.md)
- [TITextProcessing](docs/titextprocessing)
* [TITextProcessing](docs/titextprocessing/titextprocessing.md)
- [TIDeeplink](docs/tideeplink/deeplinks.md)
- [TIBottomSheet](docs/tibottomsheet/tibottomsheet.md)
- [Semantic Commit Messages](docs/semantic-commit-messages.md) - commit message codestyle. - [Semantic Commit Messages](docs/semantic-commit-messages.md) - commit message codestyle.
- [Snippets](docs/snippets.md) - useful commands and scripts for development. - [Snippets](docs/snippets.md) - useful commands and scripts for development.
@ -32,7 +110,7 @@ Useful docs:
./setup ./setup
``` ```
- If legacy [Source](https://github.com/TouchInstinct/LeadKit/tree/master/Sources) folder needed, [build dependencies for LeadKit.xcodeproj](https://github.com/TouchInstinct/LeadKit/blob/master/docs/snippets.md#build-dependencies-for-LeadKit.xcodeproj). - If legacy [Source](https://git.svc.touchin.ru/TouchInstinct/LeadKit/tree/master/Sources) folder needed, [build dependencies for LeadKit.xcodeproj](https://git.svc.touchin.ru/TouchInstinct/LeadKit/blob/master/docs/snippets.md#build-dependencies-for-LeadKit.xcodeproj).
- Make sure the commit message codestyle is followed. More about [Semantic Commit Messages](docs/semantic-commit-messages.md). - Make sure the commit message codestyle is followed. More about [Semantic Commit Messages](docs/semantic-commit-messages.md).
@ -42,14 +120,14 @@ Useful docs:
```swift ```swift
dependencies: [ dependencies: [
.package(url: "https://github.com/TouchInstinct/LeadKit.git", from: "x.y.z"), .package(url: "https://git.svc.touchin.ru/TouchInstinct/LeadKit.git", from: "x.y.z"),
], ],
``` ```
### Cocoapods ### Cocoapods
```ruby ```ruby
source 'https://github.com/TouchInstinct/Podspecs.git' source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
pod 'TISwiftUtils', 'x.y.z' pod 'TISwiftUtils', 'x.y.z'
pod 'TIFoundationUtils', 'x.y.z' pod 'TIFoundationUtils', 'x.y.z'
@ -58,4 +136,4 @@ pod 'TIFoundationUtils', 'x.y.z'
## Legacy ## Legacy
Code located in root `Sources` folder and `LeadKit.podspec` should be treated as legacy and shouldn't be used in newly created projects. Please use TI* modules via SPM or CocoaPods. Code located in root `Sources` folder and `LeadKit.podspec` should be treated as legacy and shouldn't be used in newly created projects. Please use TI* modules via SPM or CocoaPods.

View File

@ -24,7 +24,11 @@ import TIMapUtils
import MapKit import MapKit
import UIKit import UIKit
open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView, [ApplePlacemarkManager<Model>], MKMapRect>, MKMapViewDelegate { open class AppleClusterPlacemarkManager<Model>: BaseClusterPlacemarkManager<MKAnnotationView,
ApplePlacemarkManager<Model>,
MKMapRect>,
MKMapViewDelegate {
public weak var mapViewDelegate: MKMapViewDelegate? public weak var mapViewDelegate: MKMapViewDelegate?
private let mapDelegateSelectors = NSObject.instanceMethodSelectors(of: MKMapViewDelegate.self) private let mapDelegateSelectors = NSObject.instanceMethodSelectors(of: MKMapViewDelegate.self)
@ -36,7 +40,8 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
self.mapViewDelegate = mapViewDelegate self.mapViewDelegate = mapViewDelegate
super.init(dataModel: placemarkManagers, super.init(placemarkPosition: .from(coordinates: placemarkManagers.map(\.placemarkPosition)),
dataModel: placemarkManagers,
iconFactory: iconFactory?.asAnyMarkerIconFactory { $0.map { $0.dataModel } }, iconFactory: iconFactory?.asAnyMarkerIconFactory { $0.map { $0.dataModel } },
tapHandler: tapHandler) tapHandler: tapHandler)
} }
@ -55,10 +60,10 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
override open func configure(placemark: MKAnnotationView) { override open func configure(placemark: MKAnnotationView) {
guard let clusterAnnotation = placemark.annotation as? MKClusterAnnotation, guard let clusterAnnotation = placemark.annotation as? MKClusterAnnotation,
let placemarkManagers = clusterAnnotation.memberAnnotations as? [ApplePlacemarkManager<Model>] else { let placemarkManagers = clusterAnnotation.memberAnnotations as? [ApplePlacemarkManager<Model>] else {
return return
} }
placemark.image = iconFactory?.markerIcon(for: placemarkManagers) placemark.image = iconFactory?.markerIcon(for: placemarkManagers, state: .default)
} }
// MARK: - MKMapViewDelegate // MARK: - MKMapViewDelegate
@ -79,6 +84,7 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
configure(placemark: defaultAnnotationView) configure(placemark: defaultAnnotationView)
return defaultAnnotationView return defaultAnnotationView
case let placemarkManager as ApplePlacemarkManager<Model>: case let placemarkManager as ApplePlacemarkManager<Model>:
let defaultAnnotationView = placemarkManager.iconFactory != nil let defaultAnnotationView = placemarkManager.iconFactory != nil
? MKAnnotationView(annotation: annotation, ? MKAnnotationView(annotation: annotation,
@ -89,6 +95,7 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
placemarkManager.configure(placemark: defaultAnnotationView) placemarkManager.configure(placemark: defaultAnnotationView)
return defaultAnnotationView return defaultAnnotationView
default: default:
return nil return nil
} }
@ -107,8 +114,29 @@ open class AppleClusterPlacemarkManager<Model>: BasePlacemarkManager<MKAnnotatio
} }
_ = tapHandler?(placemarkManagers, .from(coordinates: placemarkManagers.map { $0.coordinate })) _ = tapHandler?(placemarkManagers, .from(coordinates: placemarkManagers.map { $0.coordinate }))
case let placemarkManager as ApplePlacemarkManager<Model>: case let placemarkManager as ApplePlacemarkManager<Model>:
_ = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate) let isTapHandled = placemarkManager.tapHandler?(placemarkManager.dataModel, placemarkManager.coordinate) ?? false
if isTapHandled {
placemarkManager.state = .selected
}
default:
return
}
}
open func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
guard !(mapViewDelegate?.responds(to: #selector(mapView(_:didDeselect:))) ?? false) else {
mapViewDelegate?.mapView?(mapView, didDeselect: view)
return
}
switch view.annotation {
case let placemarkManager as ApplePlacemarkManager<Model>:
placemarkManager.state = .default
default: default:
return return
} }

View File

@ -24,9 +24,10 @@ import TIMapUtils
import MapKit import MapKit
open class AppleMapManager<DataModel>: BaseMapManager<MKMapView, open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
ApplePlacemarkManager<DataModel>, ApplePlacemarkManager<DataModel>,
AppleClusterPlacemarkManager<DataModel>, AppleClusterPlacemarkManager<DataModel>,
MKCameraUpdate> { MKCameraUpdate,
AppleMapUISettings> {
public typealias ClusteringIdentifier = String public typealias ClusteringIdentifier = String
@ -44,7 +45,8 @@ open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
return nil return nil
} }
return ApplePlacemarkManager(dataModel: $0, return ApplePlacemarkManager(map: map,
dataModel: $0,
position: position, position: position,
clusteringIdentifier: clusteringIdentifierGetter($0), clusteringIdentifier: clusteringIdentifierGetter($0),
iconFactory: iconFactory?.asAnyMarkerIconFactory(), iconFactory: iconFactory?.asAnyMarkerIconFactory(),
@ -65,27 +67,18 @@ open class AppleMapManager<DataModel>: BaseMapManager<MKMapView,
} }
public convenience init(map: MKMapView, public convenience init(map: MKMapView,
positionGetter: @escaping PositionGetter, iconFactory: DefaultMarkerIconFactory<DataModel>? = nil,
clusteringIdentifierGetter: @escaping (DataModel) -> ClusteringIdentifier, clusterIconFactory: DefaultClusterMarkerIconFactory<DataModel>? = nil,
mapViewDelegate: MKMapViewDelegate? = nil, mapViewDelegate: MKMapViewDelegate? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) { selectPlacemarkHandler: @escaping SelectPlacemarkHandler)
where DataModel: MapLocatable, DataModel.Position == CLLocationCoordinate2D,
self.init(map: map, DataModel: Clusterable, DataModel.ClusterIdentifier == ClusteringIdentifier {
positionGetter: positionGetter,
clusteringIdentifierGetter: clusteringIdentifierGetter,
iconFactory: nil as DefaultMarkerIconFactory<DataModel>?,
clusterIconFactory: nil as DefaultClusterMarkerIconFactory<DataModel>?,
mapViewDelegate: mapViewDelegate,
selectPlacemarkHandler: selectPlacemarkHandler)
}
public convenience init(map: MKMapView,
mapViewDelegate: MKMapViewDelegate? = nil,
selectPlacemarkHandler: @escaping SelectPlacemarkHandler) where DataModel: MapLocatable & Clusterable, DataModel.ClusterIdentifier == ClusteringIdentifier, DataModel.Position == CLLocationCoordinate2D {
self.init(map: map, self.init(map: map,
positionGetter: { $0.position }, positionGetter: { $0.position },
clusteringIdentifierGetter: { $0.clusterIdentifier }, clusteringIdentifierGetter: { $0.clusterIdentifier },
iconFactory: iconFactory,
clusterIconFactory: clusterIconFactory,
mapViewDelegate: mapViewDelegate, mapViewDelegate: mapViewDelegate,
selectPlacemarkHandler: selectPlacemarkHandler) selectPlacemarkHandler: selectPlacemarkHandler)
} }

View File

@ -0,0 +1,58 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIMapUtils
import MapKit
open class AppleMapUISettings: BaseMapUISettings<MKMapView> {
open class Defaults: BaseMapUISettings<MKMapView>.Defaults {
public static var showCompassButton: Bool {
false
}
}
public var showCompassButton = false
public init(showUserLocation: Bool = Defaults.showUserLocation,
isZoomEnabled: Bool = Defaults.isZoomEnabled,
isTiltEnabled: Bool = Defaults.isTiltEnabled,
isRotationEnabled: Bool = Defaults.isRotationEnabled,
showCompassButton: Bool = Defaults.showCompassButton) {
self.showCompassButton = showCompassButton
super.init(showUserLocation: showUserLocation,
isZoomEnabled: isZoomEnabled,
isTiltEnabled: isTiltEnabled,
isRotationEnabled: isRotationEnabled)
}
override open func apply(to mapView: MKMapView) {
super.apply(to: mapView)
mapView.showsUserLocation = showUserLocation
mapView.isZoomEnabled = isZoomEnabled
mapView.isPitchEnabled = isTiltEnabled
mapView.isRotateEnabled = isRotationEnabled
mapView.showsCompass = showCompassButton
}
}

View File

@ -23,23 +23,58 @@
import TIMapUtils import TIMapUtils
import MapKit import MapKit
open class ApplePlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView, Model, CLLocationCoordinate2D>, MKAnnotation { open class ApplePlacemarkManager<Model>: BaseItemPlacemarkManager<MKAnnotationView, Model, CLLocationCoordinate2D>,
MKAnnotation {
// MARK: - MKAnnotation // MARK: - MKAnnotation
/// A map where all placemarks are placed
public let map: MKMapView
public let coordinate: CLLocationCoordinate2D /// Identifier required for correct cluster placement
public var clusteringIdentifier: String? public var clusteringIdentifier: String?
/// Point (coordinates) itself of the current placemark manager
public var coordinate: CLLocationCoordinate2D {
placemarkPosition
}
/// The current state of a manager's placemark
override public var state: MarkerState {
didSet {
guard let placemark = placemark else {
return
}
/*
Although the icon is being updated, it is necessary to manually deselect
the annotation of the current placemark if it is currently selected.
*/
if let annotation = placemark.annotation {
switch state {
case .default:
map.deselectAnnotation(annotation, animated: true)
case .selected:
map.selectAnnotation(annotation, animated: true)
}
}
placemark.image = iconFactory?.markerIcon(for: dataModel, state: state)
}
}
public init(dataModel: Model, public init(map: MKMapView,
dataModel: Model,
position: CLLocationCoordinate2D, position: CLLocationCoordinate2D,
clusteringIdentifier: String?, clusteringIdentifier: String?,
iconFactory: AnyMarkerIconFactory<DataModel>?, iconFactory: AnyMarkerIconFactory<DataModel>?,
tapHandler: TapHandlerClosure?) { tapHandler: TapHandlerClosure?) {
self.coordinate = position self.map = map
self.clusteringIdentifier = clusteringIdentifier self.clusteringIdentifier = clusteringIdentifier
super.init(dataModel: dataModel, super.init(placemarkPosition: position,
dataModel: dataModel,
iconFactory: iconFactory, iconFactory: iconFactory,
tapHandler: tapHandler) tapHandler: tapHandler)
} }
@ -47,7 +82,10 @@ open class ApplePlacemarkManager<Model>: BasePlacemarkManager<MKAnnotationView,
// MARK: - PlacemarkManager // MARK: - PlacemarkManager
override open func configure(placemark: MKAnnotationView) { override open func configure(placemark: MKAnnotationView) {
placemark.image = iconFactory?.markerIcon(for: dataModel) super.configure(placemark: placemark)
placemark.clusteringIdentifier = clusteringIdentifier
// Setting required values of the current manager and placemark respectively
self.placemark?.clusteringIdentifier = clusteringIdentifier
self.state = .default
} }
} }

View File

@ -1,16 +1,23 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'TIAppleMapUtils' s.name = 'TIAppleMapUtils'
s.version = '1.33.0' s.version = '1.56.0'
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.' s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' } s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '11.0' s.ios.deployment_target = '12.0'
s.swift_versions = ['5.3'] s.swift_versions = ['5.7']
s.source_files = s.name + '/Sources/**/*' sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TIMapUtils', s.version.to_s s.dependency 'TIMapUtils', s.version.to_s
end end

View File

@ -0,0 +1,54 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public struct BundleIdentifier {
public let appPrefix: String
public let appIdentifier: String
public let fullIdentifier: String
public let defaultAppGroupIdenfier: String
static let bundleIdentifierSeparator = "."
public init(appPrefix: String, appIdentifier: String) {
self.appPrefix = appPrefix
self.appIdentifier = appIdentifier
self.fullIdentifier = appPrefix + Self.bundleIdentifierSeparator + appIdentifier
self.defaultAppGroupIdenfier = "group." + fullIdentifier
}
public init?(bundle: Bundle = .main) {
guard let fullIdenfifier = bundle.bundleIdentifier,
var components = bundle.bundleIdentifier?
.components(separatedBy: Self.bundleIdentifierSeparator),
let lastComponent = components.popLast() else {
return nil
}
self.fullIdentifier = fullIdenfifier
self.appIdentifier = lastComponent
self.appPrefix = components.joined(separator: Self.bundleIdentifierSeparator)
self.defaultAppGroupIdenfier = "group." + fullIdentifier
}
}

View File

@ -0,0 +1,116 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import TIFoundationUtils
import KeychainAccess
import TILogging
import UIKit
public struct CoreDependencies {
public var dateFormattersResusePool = DateFormattersReusePool()
public var iso8601DateFormattersReusePool = ISO8601DateFormattersReusePool()
public var jsonCodingConfigurator: JsonCodingConfigurator
public var jsonKeyValueDecoder: JSONKeyValueDecoder
public var jsonKeyValueEncoder: JSONKeyValueEncoder
public var device: UIDevice = .current
public var bundle: Bundle = .main
public var fileManager: FileManager = .default
public var logger: DefaultOSLogErrorLogger
public var keychain: Keychain
public var defaults: UserDefaults
public var appGroupDefaults: UserDefaults?
public var appGroupKeychain: Keychain?
public var appGroupCacheDirectory: URL?
public var networkCallbackQueue: DispatchQueue
public init(bundleIdentifier: BundleIdentifier,
customAppGroupIdentifier: String? = nil) {
jsonCodingConfigurator = JsonCodingConfigurator(dateFormattersReusePool: dateFormattersResusePool,
iso8601DateFormattersReusePool: iso8601DateFormattersReusePool)
jsonKeyValueDecoder = JSONKeyValueDecoder(jsonDecoder: jsonCodingConfigurator.jsonDecoder)
jsonKeyValueEncoder = JSONKeyValueEncoder(jsonEncoder: jsonCodingConfigurator.jsonEncoder)
logger = DefaultOSLogErrorLogger(subsystem: bundleIdentifier.fullIdentifier, category: "general")
keychain = Keychain(service: bundleIdentifier.fullIdentifier).accessibility(.whenUnlockedThisDeviceOnly)
defaults = .standard
let appGroupIdentifier = customAppGroupIdentifier ?? bundleIdentifier.defaultAppGroupIdenfier
if let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) {
var appGroupCacheURL: URL
if #available(iOS 16.0, *) {
appGroupCacheURL = containerURL.appending(path: "Caches", directoryHint: .isDirectory)
} else {
appGroupCacheURL = containerURL.appendingPathComponent("Caches", isDirectory: true)
}
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
do {
try fileManager.createDirectory(at: appGroupCacheURL,
withIntermediateDirectories: true)
try appGroupCacheURL.setResourceValues(resourceValues)
appGroupCacheDirectory = appGroupCacheURL
} catch {
logger.log(error: error, file: #file, line: #line)
}
appGroupDefaults = UserDefaults(suiteName: appGroupIdentifier)
appGroupKeychain = Keychain(service: bundleIdentifier.appPrefix,
accessGroup: appGroupIdentifier)
.accessibility(.whenUnlockedThisDeviceOnly)
} else {
appGroupCacheDirectory = nil
appGroupDefaults = nil
appGroupKeychain = nil
}
networkCallbackQueue = DispatchQueue(label: bundleIdentifier.fullIdentifier + ".network-callback-queue",
attributes: .concurrent)
}
public init?(bundle: Bundle = .main,
customAppGroupIdentifier: String? = nil) {
guard let bundleIdentifier = BundleIdentifier(bundle: bundle) else {
return nil
}
self.init(bundleIdentifier: bundleIdentifier,
customAppGroupIdentifier: customAppGroupIdentifier)
}
}

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2022 Touch Instinct // Copyright (c) 2023 Touch Instinct
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal // of this software and associated documentation files (the Software), to deal
@ -20,7 +20,7 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
public protocol FingerprintsSettingsStorage { public protocol TargetDependencies {
/// Should be true by default (on app first run) static func assemble() -> Self
var shouldResetFingerprints: Bool { get set } static func assembleForPreview() -> Self
} }

View File

@ -0,0 +1,25 @@
Pod::Spec.new do |s|
s.name = 'TIApplication'
s.version = '1.56.0'
s.summary = 'Application architecture.'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TIFoundationUtils', s.version.to_s
s.dependency 'TILogging', s.version.to_s
s.dependency 'KeychainAccess', "~> 4.2"
end

View File

@ -22,12 +22,13 @@
import KeychainAccess import KeychainAccess
import Foundation import Foundation
import TIFoundationUtils
import LocalAuthentication import LocalAuthentication
open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data> { open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data> {
open class Defaults: SingleValueAuthKeychainStorage<StringEncryptionResult>.Defaults { open class Defaults: SingleValueAuthKeychainStorage<StringEncryptionResult>.Defaults {
public static var encryptedTokenKeyStorageKey: String { public static var encryptedTokenKeyStorageKey: StorageKey<Data> {
keychainServiceIdentifier + ".encryptedTokenKey" .init(rawValue: keychainServiceIdentifier + ".encryptedTokenKey")
} }
public static var reusableLAContext: LAContext { public static var reusableLAContext: LAContext {
@ -39,12 +40,12 @@ open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data>
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier), public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
localAuthContext: LAContext = Defaults.reusableLAContext, localAuthContext: LAContext = Defaults.reusableLAContext,
settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(), encryptedTokenKeyStorageKey: StorageKey<Data> = Defaults.encryptedTokenKeyStorageKey,
encryptedTokenKeyStorageKey: String = Defaults.encryptedTokenKeyStorageKey) { appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
let getValueClosure: GetValueClosure = { keychain, storageKey in let getValueClosure: GetValueClosure = { keychain, storageKey in
do { do {
guard let value = try keychain.getData(storageKey) else { guard let value = try keychain.getData(storageKey.rawValue) else {
return .failure(.valueNotFound) return .failure(.valueNotFound)
} }
@ -54,18 +55,18 @@ open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data>
} }
} }
let setValueClosure: SetValueClosure = { keychain, value, storageKey in let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
do { do {
return .success(try keychain.set(value, key: storageKey)) return .success(try keychain.set(value, key: storageKey.rawValue))
} catch { } catch {
return .failure(.unableToWriteData(underlyingError: error)) return .failure(.unableToWriteData(underlyingError: error))
} }
} }
super.init(keychain: keychain.authenticationContext(localAuthContext), super.init(keychain: keychain.authenticationContext(localAuthContext),
settingsStorage: settingsStorage,
storageKey: encryptedTokenKeyStorageKey, storageKey: encryptedTokenKeyStorageKey,
getValueClosure: getValueClosure, getValueClosure: getValueClosure,
setValueClosure: setValueClosure) storeValueClosure: storeValueClosure,
appFirstRunCheckStorage: appFirstRunCheckStorage)
} }
} }

View File

@ -21,22 +21,23 @@
// //
import Foundation import Foundation
import TIFoundationUtils
import KeychainAccess import KeychainAccess
open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage<StringEncryptionResult> { open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage<StringEncryptionResult> {
open class Defaults: SingleValueAuthKeychainStorage<StringEncryptionResult>.Defaults { open class Defaults: SingleValueAuthKeychainStorage<StringEncryptionResult>.Defaults {
public static var encryptedTokenStorageKey: String { public static var encryptedTokenStorageKey: StorageKey<StringEncryptionResult> {
keychainServiceIdentifier + ".encryptedToken" .init(rawValue: keychainServiceIdentifier + ".encryptedToken")
} }
} }
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier), public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(), encryptedTokenStorageKey: StorageKey<StringEncryptionResult> = Defaults.encryptedTokenStorageKey,
encryptedTokenStorageKey: String = Defaults.encryptedTokenStorageKey) { appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
let getValueClosure: GetValueClosure = { keychain, storageKey in let getValueClosure: GetValueClosure = { keychain, storageKey in
do { do {
guard let value = try keychain.getData(storageKey) else { guard let value = try keychain.getData(storageKey.rawValue) else {
return .failure(.valueNotFound) return .failure(.valueNotFound)
} }
@ -50,18 +51,18 @@ open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage<StringEn
} }
} }
let setValueClosure: SetValueClosure = { keychain, value, storageKey in let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
do { do {
return .success(try keychain.set(value.asStorableData(), key: storageKey)) return .success(try keychain.set(value.asStorableData(), key: storageKey.rawValue))
} catch { } catch {
return .failure(.unableToWriteData(underlyingError: error)) return .failure(.unableToWriteData(underlyingError: error))
} }
} }
super.init(keychain: keychain, super.init(keychain: keychain,
settingsStorage: settingsStorage,
storageKey: encryptedTokenStorageKey, storageKey: encryptedTokenStorageKey,
getValueClosure: getValueClosure, getValueClosure: getValueClosure,
setValueClosure: setValueClosure) storeValueClosure: storeValueClosure,
appFirstRunCheckStorage: appFirstRunCheckStorage)
} }
} }

View File

@ -0,0 +1,38 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIFoundationUtils
import Foundation
open class DefaultResetAuthSettingsStorage: DefaultAppFirstRunCheckStorage {
public enum Defaults {
public static var shouldResetAuthDataKey: StorageKey<Bool> {
.init(rawValue: "shouldResetAuthData")
}
}
public override init(defaults: UserDefaults = .standard,
storageKey: StorageKey<Bool> = Defaults.shouldResetAuthDataKey) {
super.init(defaults: defaults, storageKey: storageKey)
}
}

View File

@ -23,62 +23,31 @@
import TIFoundationUtils import TIFoundationUtils
import KeychainAccess import KeychainAccess
import Foundation import Foundation
import TIKeychainUtils
open class SingleValueAuthKeychainStorage<ValueType>:
AppInstallLifetimeSingleValueStorage<BaseSingleValueKeychainStorage<ValueType>, BoolValueDefaultsStorage> {
public typealias GetValueClosure = BaseSingleValueKeychainStorage<ValueType>.GetValueClosure
public typealias StoreValueClosure = BaseSingleValueKeychainStorage<ValueType>.StoreValueClosure
open class SingleValueAuthKeychainStorage<ValueType>: SingleValueStorage {
open class Defaults { open class Defaults {
public static var keychainServiceIdentifier: String { public static var keychainServiceIdentifier: String {
Bundle.main.bundleIdentifier ?? "ru.touchin.TIAuth" Bundle.main.bundleIdentifier ?? "ru.touchin.TIAuth"
} }
} }
public typealias GetValueClosure = (Keychain, String) -> Result<ValueType, StorageError>
public typealias SetValueClosure = (Keychain, ValueType, String) -> Result<Void, StorageError>
public let keychain: Keychain
public let settingsStorage: AuthSettingsStorage
public let storageKey: String
public let getValueClosure: GetValueClosure
public let setValueClosure: SetValueClosure
public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier), public init(keychain: Keychain = Keychain(service: Defaults.keychainServiceIdentifier),
settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(), storageKey: StorageKey<ValueType>,
storageKey: String,
getValueClosure: @escaping GetValueClosure, getValueClosure: @escaping GetValueClosure,
setValueClosure: @escaping SetValueClosure) { storeValueClosure: @escaping StoreValueClosure,
appFirstRunCheckStorage: BoolValueDefaultsStorage = DefaultResetAuthSettingsStorage()) {
self.keychain = keychain let keychainStorage = BaseSingleValueKeychainStorage(keychain: keychain,
self.settingsStorage = settingsStorage storageKey: storageKey,
self.storageKey = storageKey getValueClosure: getValueClosure,
self.getValueClosure = getValueClosure storeValueClosure: storeValueClosure)
self.setValueClosure = setValueClosure
}
// MARK: - SingleValueStorage super.init(storage: keychainStorage, appFirstRunCheckStorage: appFirstRunCheckStorage)
open func hasStoredValue() -> Bool {
!settingsStorage.shouldResetStoredAuthData && ((try? keychain.contains(storageKey)) ?? false)
}
open func store(value: ValueType) -> Result<Void, StorageError> {
return setValueClosure(keychain, value, storageKey)
}
open func getValue() -> Result<ValueType, StorageError> {
guard !settingsStorage.shouldResetStoredAuthData else {
let result: Result<ValueType, StorageError>
do {
try keychain.remove(storageKey)
settingsStorage.shouldResetStoredAuthData = false
result = .failure(.valueNotFound)
} catch {
result = .failure(.unableToWriteData(underlyingError: error))
}
return result
}
return getValueClosure(keychain, storageKey)
} }
} }

View File

@ -1,18 +1,25 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'TIAuth' s.name = 'TIAuth'
s.version = '1.33.0' s.version = '1.56.0'
s.summary = 'Login, registration, confirmation and other related actions' s.summary = 'Login, registration, confirmation and other related actions'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' } s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '13.0' s.ios.deployment_target = '13.0'
s.swift_versions = ['5.3'] s.swift_versions = ['5.7']
s.source_files = s.name + '/Sources/**/*' sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TIFoundationUtils', s.version.to_s s.dependency 'TIFoundationUtils', s.version.to_s
s.dependency 'TIKeychainUtils', s.version.to_s
s.dependency 'TIUIKitCore', s.version.to_s s.dependency 'TIUIKitCore', s.version.to_s
s.dependency 'KeychainAccess', "~> 4.2"
end end

View File

@ -0,0 +1,14 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
target 'TIBottomSheet' do
platform :ios, 11
use_frameworks!
pod 'TIUIElements', :path => '../../../../TIUIElements/TIUIElements.podspec'
pod 'TIUIKitCore', :path => '../../../../TIUIKitCore/TIUIKitCore.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIBottomSheet', :path => '../../../../TIBottomSheet/TIBottomSheet.podspec'
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
end

View File

@ -0,0 +1,62 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIElements
import TIUIKitCore
import UIKit
extension BaseModalViewController {
open class BaseAppearance: UIView.BaseAppearance<UIView.DefaultWrappedLayout> {
public var presentationDetents: [ModalViewPresentationDetent]
public var dragViewState: DragView.State
public var headerViewState: ModalHeaderView.State
public var footerViewState: ModalFooterView<FooterContentView>.State
public init(layout: UIView.DefaultWrappedLayout = .defaultLayout,
background: UIViewBackground = UIViewColorBackground(color: .clear),
border: UIViewBorder = BaseUIViewBorder(),
shadow: UIViewShadow? = nil,
presentationDetents: [ModalViewPresentationDetent] = [.maxHeight],
dragViewState: DragView.State = .hidden,
headerViewState: ModalHeaderView.State = .hidden,
footerViewState: ModalFooterView<FooterContentView>.State = .hidden) {
self.presentationDetents = presentationDetents
self.dragViewState = dragViewState
self.headerViewState = headerViewState
self.footerViewState = footerViewState
super.init(layout: layout,
background: background,
border: border,
shadow: shadow)
}
}
public final class DefaultAppearance: BaseAppearance, ViewAppearance {
public static var defaultAppearance: Self {
Self()
}
}
}

View File

@ -0,0 +1,473 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TISwiftUtils
import TIUIElements
import TIUIKitCore
import UIKit
import PanModal
open class BaseModalViewController<ContentView: UIView,
FooterContentView: UIView>: BaseInitializableViewController, PanModalPresentable {
// MARK: - Public Properties
public let dragView = DragView()
public let headerView = ModalHeaderView()
private(set) public lazy var contentView = createContentView()
private(set) public lazy var footerView = createFooterView()
public private(set) lazy var dragViewBottomToHeaderViewTopConstraint: NSLayoutConstraint = {
dragView.bottomAnchor.constraint(equalTo: headerView.topAnchor)
}()
public private(set) lazy var dragViewBottomToContentViewTopConstraint: NSLayoutConstraint = {
dragView.bottomAnchor.constraint(equalTo: contentView.topAnchor)
}()
public private(set) lazy var dragViewConstraints: SubviewConstraints = {
let trailingConstraint = dragView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: dragView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
trailingConstraint: trailingConstraint,
topConstraint: dragView.topAnchor.constraint(equalTo: view.topAnchor),
bottomConstraint: dragViewBottomToHeaderViewTopConstraint)
let centerXConstraint = dragView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = dragView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: dragView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: dragView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var headerViewToSuperviewTopConstraint: NSLayoutConstraint = {
headerView.topAnchor.constraint(equalTo: view.topAnchor)
}()
public private(set) lazy var headerBottomToContentTopConstraint: NSLayoutConstraint = {
headerView.bottomAnchor.constraint(equalTo: contentView.topAnchor)
}()
public private(set) lazy var headerViewConstraints: SubviewConstraints = {
let leadingConstraint = headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: dragViewBottomToHeaderViewTopConstraint,
bottomConstraint: headerBottomToContentTopConstraint)
let centerXConstraint = headerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = headerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: headerView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: headerView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var contentViewTopToSuperviewConstraint: NSLayoutConstraint = {
contentView.topAnchor.constraint(equalTo: view.topAnchor)
}()
public private(set) lazy var contentViewBottomToSuperviewConstraint: NSLayoutConstraint = {
contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
}()
public private(set) lazy var contentViewConstraints: SubviewConstraints = {
let leadingConstraint = contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: headerBottomToContentTopConstraint,
bottomConstraint: contentViewBottomToSuperviewConstraint)
let centerXConstraint = contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: contentView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: contentView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var footerViewConstraints: SubviewConstraints = {
let leadingConstraint = footerView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
let trailingConstraint = footerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: footerView.topAnchor.constraint(equalTo: contentView.bottomAnchor),
bottomConstraint: footerView.bottomAnchor.constraint(equalTo: view.bottomAnchor))
let centerXConstraint = footerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let centerYConstraint = footerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: footerView.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: footerView.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
// MARK: - Modal View Controller Configuration
public var viewControllerAppearance: BaseAppearance = .init(background: UIViewColorBackground(color: .white))
open var panScrollable: UIScrollView? {
contentView as? UIScrollView
}
public var panScrollableInsets: UIEdgeInsets = .zero {
didSet {
panScrollable?.contentInset = panScrollableInsets
}
}
public var dimmedView = DimmedView()
public var showDragIndicator = true
open var headerViewHeight: CGFloat {
let dragViewHeight = getHeight(of: dragView)
let headerViewHeight = getHeight(of: headerView)
let dragViewVerticalInsets = getDragViewVerticalInsets()
let headerViewVerticalInsets = getHeaderViewVerticalInsets()
return dragViewHeight + headerViewHeight + dragViewVerticalInsets + headerViewVerticalInsets
}
open var longFormHeight: PanModalHeight {
let detents = getSortedDetents()
return detents.max()?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
open var mediumFormHeight: PanModalHeight {
let detents = getSortedDetents()
if detents.count > 1 {
return detents[1].panModalHeight(headerHeight: headerViewHeight)
}
return detents.first?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
open var shortFormHeight: PanModalHeight {
let detents = getSortedDetents()
return detents.min()?.panModalHeight(headerHeight: headerViewHeight) ?? .maxHeight
}
private var keyboardDidShownObserver: NSObjectProtocol?
private var keyboardDidHiddenObserver: NSObjectProtocol?
// MARK: - Life Cycle
deinit {
let notificationCenter = NotificationCenter.default
if let keyboardDidShownObserver {
notificationCenter.removeObserver(keyboardDidShownObserver)
}
if let keyboardDidHiddenObserver {
notificationCenter.removeObserver(keyboardDidHiddenObserver)
}
}
// MARK: - BaseInitializableViewController
open override func addViews() {
super.addViews()
view.addSubviews(dragView, headerView, contentView, footerView)
}
open override func configureLayout() {
super.configureLayout()
for view in [dragView, headerView, contentView, footerView] {
view.translatesAutoresizingMaskIntoConstraints = false
}
configureDragViewLayout()
configureHeaderViewLayout()
configureContentViewLayout()
configureFooterViewLayout()
}
open override func bindViews() {
super.bindViews()
keyboardDidShownObserver = NotificationCenter.default
.addObserver(forName: UIResponder.keyboardDidShowNotification,
object: nil,
queue: .main) { [weak self] notification in
self?.configureLayoutForKeyboard(notification, isKeyboardHidden: false)
}
keyboardDidHiddenObserver = NotificationCenter.default
.addObserver(forName: UIResponder.keyboardDidHideNotification,
object: nil,
queue: .main) { [weak self] notification in
self?.configureLayoutForKeyboard(notification, isKeyboardHidden: true)
}
}
open override func configureAppearance() {
super.configureAppearance()
view.configureUIView(appearance: viewControllerAppearance)
configureDragViewAppearance()
configureHeaderViewAppearance()
configureFooterViewAppearance()
}
// MARK: - Open Methods
open func createContentView() -> ContentView {
ContentView()
}
open func createFooterView() -> ModalFooterView<FooterContentView> {
ModalFooterView<FooterContentView>()
}
open func configureLayoutForKeyboard(_ notification: Notification, isKeyboardHidden: Bool) {
guard let keyboardHeight = getKeyboardHeight(notification) else {
return
}
if case let .presented(footerViewAppearance) = viewControllerAppearance.footerViewState {
let bottomInset = footerViewAppearance.layout.insets.add(\.bottom,
to: \.bottom,
of: .vertical(bottom: keyboardHeight))
footerViewConstraints.edgeConstraints.bottomConstraint.constant = bottomInset
} else if let panScrollable {
var insets = panScrollableInsets
if isKeyboardHidden {
panScrollable.contentInset = insets
} else {
insets.bottom += keyboardHeight
panScrollable.contentInset = insets
}
}
}
// MARK: - Private Methods
private func configureDragViewLayout() {
guard case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState else {
return
}
let bottomConstraint: NSLayoutConstraint
let bottomConstant: CGFloat
if case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
bottomConstraint = dragViewBottomToHeaderViewTopConstraint
bottomConstant = dragViewAppearance.layout.insets.add(\.bottom,
to: \.top,
of: headerViewAppearance.layout.insets)
} else {
dragViewBottomToHeaderViewTopConstraint.isActive = false
bottomConstraint = dragViewBottomToContentViewTopConstraint
bottomConstant = dragViewAppearance.layout.insets.bottom
}
dragViewConstraints.edgeConstraints.bottomConstraint = bottomConstraint
dragViewConstraints.update(from: dragViewAppearance.layout)
bottomConstraint.setActiveConstantOrDeactivate(constant: bottomConstant)
}
private func configureHeaderViewLayout() {
guard case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState else {
return
}
let topConstraint: NSLayoutConstraint
let topConstant: CGFloat
if case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
topConstraint = dragViewBottomToHeaderViewTopConstraint
topConstant = dragViewAppearance.layout.insets.add(\.bottom,
to: \.top,
of: headerViewAppearance.layout.insets)
} else {
dragViewBottomToHeaderViewTopConstraint.isActive = false
topConstraint = headerViewToSuperviewTopConstraint
let topInset = headerViewAppearance.layout.insets.top
topConstant = topInset.isFinite ? topInset : .zero
}
headerViewConstraints.edgeConstraints.topConstraint = topConstraint
headerViewConstraints.update(from: headerViewAppearance.layout)
topConstraint.setActiveConstantOrDeactivate(constant: topConstant)
}
private func configureContentViewLayout() {
let topConstraint: NSLayoutConstraint
let topConstant: CGFloat
if case let .presented(headerViewAppearance) = viewControllerAppearance.headerViewState {
dragViewBottomToContentViewTopConstraint.isActive = false
contentViewTopToSuperviewConstraint.isActive = false
topConstraint = headerBottomToContentTopConstraint
topConstant = headerViewAppearance.layout.insets.bottom
} else if case let .presented(dragViewAppearance) = viewControllerAppearance.dragViewState {
contentViewTopToSuperviewConstraint.isActive = false
headerBottomToContentTopConstraint.isActive = false
topConstraint = dragViewBottomToContentViewTopConstraint
topConstant = dragViewAppearance.layout.insets.bottom
} else {
headerBottomToContentTopConstraint.isActive = false
dragViewBottomToContentViewTopConstraint.isActive = false
topConstraint = contentViewTopToSuperviewConstraint
topConstant = .zero
}
contentViewConstraints.edgeConstraints.topConstraint = topConstraint
let layout = UIView.DefaultWrappedLayout(insets: .horizontal(.zero)
.vertical(top: topConstant)
.replacingNan(with: .zero))
contentViewConstraints.update(from: layout)
}
private func configureFooterViewLayout() {
guard case let .presented(footerViewAppearance) = viewControllerAppearance.footerViewState else {
return
}
contentViewConstraints.edgeConstraints.bottomConstraint.isActive = false
contentViewConstraints.edgeConstraints.bottomConstraint = footerViewConstraints.edgeConstraints.topConstraint
footerViewConstraints.update(from: footerViewAppearance.layout)
}
private func configureDragViewAppearance() {
switch viewControllerAppearance.dragViewState {
case .hidden:
dragView.isHidden = true
case let .presented(appearance):
dragView.configure(appearance: appearance)
}
}
private func configureHeaderViewAppearance() {
switch viewControllerAppearance.headerViewState {
case .hidden:
headerView.isHidden = true
case let .presented(appearance):
headerView.configure(appearance: appearance)
}
}
private func configureFooterViewAppearance() {
switch viewControllerAppearance.footerViewState {
case .hidden:
footerView.isHidden = true
case let .presented(appearance):
footerView.configureBaseWrappedViewHolder(appearance: appearance)
}
}
private func getSortedDetents() -> [ModalViewPresentationDetent] {
viewControllerAppearance.presentationDetents.uniqued().sorted()
}
private func getHeight(of view: UIView) -> CGFloat {
guard !view.isHidden else {
return .zero
}
return getFittingSize(forView: view).height
}
private func getDragViewVerticalInsets() -> CGFloat {
guard case let .presented(appearance) = viewControllerAppearance.dragViewState else {
return .zero
}
return appearance.layout.insets.vertical(onNan: .zero)
}
private func getHeaderViewVerticalInsets() -> CGFloat {
guard case let .presented(appearance) = viewControllerAppearance.headerViewState else {
return .zero
}
return appearance.layout.insets.vertical(onNan: .zero)
}
private func getFittingSize(forView view: UIView) -> CGSize {
let targetSize = CGSize(width: UIScreen.main.bounds.width,
height: UIView.layoutFittingCompressedSize.height)
return view.systemLayoutSizeFitting(targetSize)
}
private func getKeyboardHeight(_ notification: Notification) -> CGFloat? {
guard let userInfo = notification.userInfo else {
return nil
}
return (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height
}
}

View File

@ -0,0 +1,54 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
open class DefaultModalWrapperViewController<ContentController: UIViewController>: BaseModalViewController<UIView, UIView> {
private(set) public var contentViewController: ContentController
public init(contentViewController: ContentController) {
self.contentViewController = contentViewController
super.init(nibName: nil, bundle: nil)
addChild(contentViewController)
contentViewController.didMove(toParent: self)
}
@available(*, unavailable)
required public init?(coder: NSCoder) {
contentViewController = ContentController()
super.init(coder: coder)
}
open override func createContentView() -> UIView {
contentViewController.view
}
}
public extension UIViewController {
func wrappedInBottomSheetController() -> DefaultModalWrapperViewController<UIViewController> {
DefaultModalWrapperViewController(contentViewController: self)
}
}

View File

@ -0,0 +1,34 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIKitCore
extension UIViewShadow {
public static var defaultModalViewShadow: Self {
.init {
Color(.black)
Opacity(0.5)
Offset(0, -5)
Radius(10)
}
}
}

View File

@ -0,0 +1,67 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import PanModal
public struct ModalViewPresentationDetent: Hashable {
// MARK: - Default Values
public static var headerOnly: Self {
Self(height: -.greatestFiniteMagnitude)
}
public static func height(_ height: CGFloat) -> Self {
Self(height: height)
}
public static var maxHeight: Self {
Self(height: .greatestFiniteMagnitude)
}
// MARK: - Public Properties
public var height: CGFloat
// MARK: - Internal Methods
func panModalHeight(headerHeight: CGFloat = .zero) -> PanModalHeight {
if self == .headerOnly {
return .contentHeight(headerHeight)
}
if self == .maxHeight {
return .maxHeight
}
return .contentHeight(height)
}
}
// MARK: - Comparable
extension ModalViewPresentationDetent: Comparable {
public static func < (lhs: ModalViewPresentationDetent, rhs: ModalViewPresentationDetent) -> Bool {
lhs.height < rhs.height
}
}

View File

@ -0,0 +1,70 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIElements
import TIUIKitCore
import UIKit
public final class DragView: BaseInitializableView, AppearanceConfigurable {
// MARK: - Nested Types
private enum Constants {
static var dragViewTopInset: CGFloat {
8
}
static var dragViewBorder: UIViewBorder {
UIViewRoundedBorder(cornerRadius: dragViewSize.height / 2)
}
static var dragViewBackground: UIViewColorBackground {
UIViewColorBackground(color: .lightGray)
}
static var dragViewSize: CGSize {
CGSize(width: 52, height: 7)
}
}
public enum State {
case hidden
case presented(Appearance)
}
public final class Appearance: UIView.BaseWrappedAppearance<UIView.DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self(layout: DefaultWrappedLayout(insets: .vertical(top: Constants.dragViewTopInset),
size: Constants.dragViewSize,
centerOffset: .centerHorizontal()),
background: Constants.dragViewBackground,
border: Constants.dragViewBorder)
}
}
// MARK: - AppearanceConfigurable
public func configure(appearance: Appearance) {
configureUIView(appearance: appearance)
}
}

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2020 Touch Instinct // Copyright (c) 2023 Touch Instinct
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal // of this software and associated documentation files (the Software), to deal
@ -20,37 +20,26 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
import TIUIElements
import TIUIKitCore
import UIKit import UIKit
open class RoundedStatefulButton: StatefulButton { open class ModalFooterView<ContentView: UIView>: ContainerView<ContentView> {
// MARK: - Nested Types
// UIView override public enum State {
case hidden
override public init(frame: CGRect) { case presented(Appearance)
super.init(frame: frame) }
}
configureAppearance()
} public extension ModalFooterView {
required public init?(coder: NSCoder) { final class Appearance: BaseWrappedViewHolderAppearance<DefaultWrappedAppearance, DefaultWrappedLayout>,
super.init(coder: coder) WrappedViewHolderAppearance {
public static var defaultAppearance: Self {
configureAppearance() Self(layout: DefaultWrappedLayout(insets: .horizontal(.zero).vertical(bottom: .zero),
} size: .fixedHeight(44)))
}
open override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = calculateCornerRadius(for: bounds)
}
// MARK: - Open methods
open func configureAppearance() {
layer.round(corners: .allCorners)
}
open func calculateCornerRadius(for bounds: CGRect) -> CGFloat {
min(bounds.width, bounds.height) / 2
} }
} }

View File

@ -0,0 +1,235 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIElements
import TIUIKitCore
import UIKit
open class ModalHeaderView: BaseInitializableView, AppearanceConfigurable {
// MARK: - Nested Types
public enum State {
case hidden
case presented(Appearance)
}
public enum ContentViewState {
case leadingButton(BaseButtonStyle)
case trailingButton(BaseButtonStyle)
case buttons(leading: BaseButtonStyle, trailing: BaseButtonStyle)
case custom(view: UIView, appearance: UIView.BaseWrappedAppearance<UIView.DefaultWrappedLayout>)
}
// MARK: - Public properties
public let leadingButton = StatefulButton()
public let trailingButton = StatefulButton()
public private(set) lazy var leftTrailingToRightLeadingConstraint: NSLayoutConstraint = {
leadingButton.trailingAnchor.constraint(equalTo: trailingButton.leadingAnchor)
}()
public private(set) lazy var leftTrailingToSuperviewTrailing: NSLayoutConstraint = {
leadingButton.trailingAnchor.constraint(equalTo: trailingAnchor)
}()
public private(set) lazy var leadingButtonConstraints: SubviewConstraints = {
let edgeConstraints = EdgeConstraints(leadingConstraint: leadingButton.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingConstraint: leftTrailingToRightLeadingConstraint,
topConstraint: leadingButton.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: leadingButton.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = leadingButton.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = leadingButton.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: leadingButton.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: leadingButton.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public private(set) lazy var rightLeadingToSuperviewLeadingConstraint: NSLayoutConstraint = {
trailingButton.leadingAnchor.constraint(equalTo: leadingAnchor)
}()
public private(set) lazy var trailingButtonConstraints: SubviewConstraints = {
let trailingConstraint = trailingButton.trailingAnchor.constraint(equalTo: trailingAnchor)
let edgeConstraints = EdgeConstraints(leadingConstraint: leftTrailingToRightLeadingConstraint,
trailingConstraint: trailingConstraint,
topConstraint: trailingButton.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: trailingButton.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerXConstraint = trailingButton.centerXAnchor.constraint(equalTo: centerXAnchor)
let centerYConstraint = trailingButton.centerYAnchor.constraint(equalTo: centerYAnchor)
let centerConstraints = CenterConstraints(centerXConstraint: centerXConstraint,
centerYConstraint: centerYConstraint)
let sizeConstraints = SizeConstraints(widthConstraint: trailingButton.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: trailingButton.heightAnchor.constraint(equalToConstant: .zero))
return SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
}()
public var customViewConstraints: SubviewConstraints?
// MARK: - BaseInitializableView
open override func addViews() {
super.addViews()
addSubviews(leadingButton, trailingButton)
}
open override func configureLayout() {
super.configureLayout()
for view in [leadingButton, trailingButton] {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
// MARK: - AppearanceConfigurable
open func configure(appearance: Appearance) {
configureUIView(appearance: appearance)
configureContentView(state: appearance.contentViewState)
}
open func configureContentView(state: ContentViewState) {
var leadingButtonStyle: BaseButtonStyle?
var trailingButtonStyle: BaseButtonStyle?
switch state {
case let .leadingButton(style):
leadingButtonStyle = style
leadingButtonConstraints.edgeConstraints.trailingConstraint = leftTrailingToSuperviewTrailing
case let .trailingButton(style):
trailingButtonStyle = style
trailingButtonConstraints.edgeConstraints.leadingConstraint = rightLeadingToSuperviewLeadingConstraint
case let .buttons(leading, trailing):
leadingButtonStyle = leading
trailingButtonStyle = trailing
case let .custom(view, appearance):
configureCustomView(view, withLayout: appearance.layout)
view.configureUIView(appearance: appearance)
}
configure(buttonStyle: leadingButtonStyle, forButton: leadingButton, constraints: leadingButtonConstraints)
configure(buttonStyle: trailingButtonStyle, forButton: trailingButton, constraints: trailingButtonConstraints)
if let leadingButtonStyle, let trailingButtonStyle {
leadingButtonConstraints.edgeConstraints.trailingConstraint = leftTrailingToRightLeadingConstraint
trailingButtonConstraints.edgeConstraints.leadingConstraint = leftTrailingToRightLeadingConstraint
let spacing = leadingButtonStyle.appearance.layout.insets.add(\.right,
to: \.left,
of: trailingButtonStyle.appearance.layout.insets)
leftTrailingToRightLeadingConstraint.setActiveConstantOrDeactivate(constant: spacing)
}
}
// MARK: - Private methods
private func configureCustomView(_ view: UIView, withLayout layout: WrappedViewLayout) {
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
let edgeConstraints = EdgeConstraints(leadingConstraint: view.leadingAnchor.constraint(equalTo: leadingAnchor),
trailingConstraint: view.trailingAnchor.constraint(equalTo: trailingAnchor),
topConstraint: view.topAnchor.constraint(equalTo: topAnchor),
bottomConstraint: view.bottomAnchor.constraint(equalTo: bottomAnchor))
let centerConstraints = CenterConstraints(centerXConstraint: view.centerXAnchor.constraint(equalTo: centerXAnchor),
centerYConstraint: view.centerYAnchor.constraint(equalTo: centerYAnchor))
let sizeConstraints = SizeConstraints(widthConstraint: view.widthAnchor.constraint(equalToConstant: .zero),
heightConstraint: view.heightAnchor.constraint(equalToConstant: .zero))
let customViewConstraints = SubviewConstraints(edgeConstraints: edgeConstraints,
centerConstraints: centerConstraints,
sizeConstraints: sizeConstraints)
self.customViewConstraints = customViewConstraints
customViewConstraints.update(from: layout)
}
private func configure(buttonStyle: BaseButtonStyle?,
forButton button: StatefulButton,
constraints: SubviewConstraints?) {
guard let buttonStyle else {
button.isHidden = true
return
}
button.isHidden = false
constraints?.update(from: buttonStyle.appearance.layout)
button.apply(style: buttonStyle)
}
}
// MARK: - Appearance
public extension ModalHeaderView {
final class Appearance: BaseWrappedAppearance<DefaultWrappedLayout>, WrappedViewAppearance {
public static var defaultAppearance: Self {
Self(layout: DefaultWrappedLayout(insets: .horizontal(.zero).vertical(top: .zero),
size: .fixedHeight(44)))
}
public var contentViewState: ContentViewState
public init(layout: DefaultWrappedLayout = .defaultLayout,
background: UIViewBackground = UIViewColorBackground(color: .clear),
border: UIViewBorder = BaseUIViewBorder(),
shadow: UIViewShadow? = nil,
contentViewState: ContentViewState = .leadingButton(.init())) {
self.contentViewState = contentViewState
super.init(layout: layout,
background: background,
border: border,
shadow: shadow)
}
}
}

View File

@ -0,0 +1,50 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
import PanModal
open class PassthroughDimmedView: DimmedView {
public weak var hitTestHandlerView: UIView?
public var isTransparent = false
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let hitTestHandlerView else {
return super.hitTest(point, with: event)
}
return hitTestHandlerView.hitTest(point, with: event)
}
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
hitTestHandlerView == nil || super.point(inside: point, with: event)
}
open override func onChange(dimState: DimmedView.DimState) {
super.onChange(dimState: dimState)
if isTransparent {
alpha = .zero
}
}
}

View File

@ -0,0 +1,3 @@
**/build/
**/nef/
LICENSE

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>launcher</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.fortysevendeg.nef</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 The nef Authors. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
## gitignore nef files
**/build/
**/nef/
LICENSE
## User data
**/xcuserdata/
podfile.lock
**.DS_Store
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## CocoaPods
**Pods**
## Carthage
**Carthage**
## SPM
.build
.swiftpm
swiftpm

View File

@ -0,0 +1,14 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
source 'https://git.svc.touchin.ru/TouchInstinct/Podspecs.git'
target 'TIBottomSheet' do
platform :ios, 12
use_frameworks!
pod 'TIUIElements', :path => '../../../../TIUIElements/TIUIElements.podspec'
pod 'TIUIKitCore', :path => '../../../../TIUIKitCore/TIUIKitCore.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIBottomSheet', :path => '../../../../TIBottomSheet/TIBottomSheet.podspec'
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
end

View File

@ -0,0 +1,110 @@
/*:
# TIBottomSheet
TIBottomSheet содержить базовую реализацию модального котроллера и немного видоизмененную библиотеку PanModal.
## Базовый контроллер
Для создания модального котроллера можно унаследоваться от `BaseModalViewController`. Данный клас принимает два generic типа: тип основного контента, тип контента футера.
*/
import TIBottomSheet
import UIKit
class EmptyViewController: BaseModalViewController<UIView, UIView> { }
/*:
## Обертка вокруг существующего контроллера
Может быть такое, что из уже существующего контроллера нужно сделать модальное окно. С этим может помочь обертка `DefaultModalWrapperViewController`. Данный контроллер является наследником BaseModalViewController, что позволяет его настраивать так же, как и базовый модальный котроллер
*/
import TIUIKitCore
final class OldMassiveViewController: BaseInitializableViewController {
// some implementation
}
typealias ModalOldMassiveViewController = DefaultModalWrapperViewController<OldMassiveViewController>
class PresentingViewController: BaseInitializableViewController {
// some implementation
@objc private func onButtonTapped() {
presentPanModal(ModalOldMassiveViewController(contentViewController: OldMassiveViewController()))
}
}
/*:
## Контент модального контроллера
Модальный котроллер может содержать следующие элементы: `DragView`, `HeaderView`, `FooterView`. Каждый из них является опциональным и без дополнительных настроек не будет показываться.
DragView - небольшая view, за которую пользователь "держит" модальный контроллер
HeaderView - контейнер, содержащий в себе кнопки назад/закрыть или какие-то другие элементы управления
FooterView - view, располагающаяся внизу контроллера, поверх всего контента (модальный контроллер уже настроен так, чтобы при скролле в самый низ, футер не перекрывал последнюю ячейку)
Для настройки каждого у котроллера есть свойство `viewControllerAppearance`. Через него будет настраиваться весь контроллер. Однако стоит заметить, что котроллер не будет настраивать передаваимую вью, содержащую основной контент. Стандартно котроллер будет пытаться расположить контент так, чтобы он заполнил все пространство.
Вот пример настройки внешнего вида так, чтобы был видет dragView и headerView с левой кнопкой:
*/
import TIUIElements
let customViewController = BaseModalViewController<UIView, UIView>()
customViewController.viewControllerAppearance = BaseModalViewController.DefaultAppearance.make {
$0.dragViewState = .presented(.defaultAppearance)
$0.headerViewState = .presented(.make {
$0.layout.size = .fixedHeight(52)
$0.backgroundColor = .white
$0.contentViewState = .leadingButton(.init(titles: [.normal: "Close"],
appearance: .init(stateAppearances: [
.normal: .init(background: UIViewColorBackground(color: .blue))
])))
})
}
/*:
## "Якори" контроллера
Раньше для настройки высоты контроллера необходимо было пользоваться свойствами `longFormHeight`, `shortFormHeight`. В базовом контроллере можно лишь передать список точек на которых контроллер должен будет задержаться:
*/
let detentsViewController = BaseModalViewController<UIView, UIView>()
detentsViewController.viewControllerAppearance.presentationDetents = [.headerOnly, .height(300), .maxHeight]
/*:
- headerOnly будет сам пытаться вычеслить высоту хедера и dragView, показывая только их
- height(_) будет показывать контроллер на переданной высоте
- maxHeight - вся высота экрана (до safeArea)
В данный массив не рекомендуется передавать больше 3 значений, т.к. модальное окно все равно сможет занять только 3 положения на экране.
## DimmedView и PassthroughDimmedView
Для контроля `DimmedView` (затемняющей view) есть отдельное свойство `dimmedView`. Эти классы позволяют настраивать поведение при тапе в затемнённую область и кастомизировать затемнение под ваши нужды.
*/
let shadowViewController = BaseModalViewController<UIView, UIView>()
let dimmedView = PassthroughDimmedView()
dimmedView.hitTestHandlerView = shadowViewController.view
dimmedView.configureUIView(appearance: UIView.DefaultAppearance(shadow: UIViewShadow(radius: 8,
color: .black,
opacity: 0.3)))
shadowViewController.dimmedView = dimmedView
/*:
## Контроль закрытия
`PanModalPresentable` не умеет в настройку закрытия контроллера, делая это самостоятельно через `dismiss(animated:completion:)`. Теперь можно настроить закрытие самостоятельно через свойства: `onTapToDismiss` и `onDragToDismiss`.
# Взаимодействие с PanModal
Если нет необходимости или возможности использовать `BaseModalViewController`, вы все так же можете пользоваться протоколом `PanModalRepresentable`. Вот список изменений протокола:
- Открытие/закрытие модального окна теперь можно настроить с помощью свойств `onTapToDismiss` и `onDragToDismiss`
- Можно настроить промежуточное состояние модального окна с `mediumFormHeight`
- `DimmedView` открыт для наследования и может создаваться в `dimmedView` у
> Для `BaseModalViewController` все свойства из `PanModalPresentable` все также работают, т.е. вы можете их переопределять, добавлять и изменять по необходимости.
*/

View File

@ -0,0 +1,30 @@
import UIKit
public protocol NefPlaygroundLiveViewable {}
extension UIView: NefPlaygroundLiveViewable {}
extension UIViewController: NefPlaygroundLiveViewable {}
#if NOT_IN_PLAYGROUND
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {}
public static func needsIndefiniteExecution(_ state: Bool) {}
}
}
#else
import PlaygroundSupport
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {
PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable)
}
public static func needsIndefiniteExecution(_ state: Bool) {
PlaygroundPage.current.needsIndefiniteExecution = state
}
}
}
#endif

View File

@ -0,0 +1,47 @@
import Foundation
import XCTest
public extension Nef {
static func run<T: XCTestCase>(testCase class: T.Type) {
startTestObserver()
T.defaultTestSuite.run()
}
static private func startTestObserver() {
_ = testObserverInstalled
}
static private var testObserverInstalled = { () -> NefTestFailObserver in
let testObserver = NefTestFailObserver()
XCTestObservationCenter.shared.addTestObserver(testObserver)
return testObserver
}()
}
// MARK: enrich the output for XCTest
fileprivate class NefTestFailObserver: NSObject, XCTestObservation {
private var numberOfFailedTests = 0
func testSuiteWillStart(_ testSuite: XCTestSuite) {
numberOfFailedTests = 0
}
func testSuiteDidFinish(_ testSuite: XCTestSuite) {
if numberOfFailedTests > 0 {
print("💢 Test Suite '\(testSuite.name)' finished with \(numberOfFailedTests) failed \(numberOfFailedTests > 1 ? "tests" : "test").")
} else {
print("🔅 Test Suite '\(testSuite.name)' finished successfully.")
}
}
func testCase(_ testCase: XCTestCase,
didFailWithDescription description: String,
inFile filePath: String?,
atLine lineNumber: Int) {
numberOfFailedTests += 1
print("Test Fail '\(testCase.name)':\(UInt(lineNumber)): \(description.description)")
}
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true'/>

View File

@ -0,0 +1,396 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
83C08983988F66570478C40D /* Pods_TIBottomSheet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIBottomSheet.debug.xcconfig"; path = "Target Support Files/Pods-TIBottomSheet/Pods-TIBottomSheet.debug.xcconfig"; sourceTree = "<group>"; };
8BACBE8322576CAD00266845 /* TIBottomSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TIBottomSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TIBottomSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIBottomSheet.release.xcconfig"; path = "Target Support Files/Pods-TIBottomSheet/Pods-TIBottomSheet.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8BACBE8022576CAD00266845 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
83C08983988F66570478C40D /* Pods_TIBottomSheet.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
11F06D2789C6CF40767861CF /* Frameworks */ = {
isa = PBXGroup;
children = (
8EF3488B86B483233C2CC631 /* Pods_TIBottomSheet.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
1F7782E3A7AD7291B7C09F56 /* Pods */ = {
isa = PBXGroup;
children = (
7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */,
AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
8B39A26221D40F8700DE2643 = {
isa = PBXGroup;
children = (
8BACBE8422576CAD00266845 /* TIBottomSheet */,
8B39A26C21D40F8700DE2643 /* Products */,
1F7782E3A7AD7291B7C09F56 /* Pods */,
11F06D2789C6CF40767861CF /* Frameworks */,
);
sourceTree = "<group>";
};
8B39A26C21D40F8700DE2643 /* Products */ = {
isa = PBXGroup;
children = (
8BACBE8322576CAD00266845 /* TIBottomSheet.framework */,
);
name = Products;
sourceTree = "<group>";
};
8BACBE8422576CAD00266845 /* TIBottomSheet */ = {
isa = PBXGroup;
children = (
8BACBE8622576CAD00266845 /* Info.plist */,
);
path = TIBottomSheet;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8BACBE7E22576CAD00266845 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8BACBE8222576CAD00266845 /* TIBottomSheet */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIBottomSheet" */;
buildPhases = (
4E98D4C60DCD00EB801E579E /* [CP] Check Pods Manifest.lock */,
8BACBE7E22576CAD00266845 /* Headers */,
8BACBE7F22576CAD00266845 /* Sources */,
8BACBE8022576CAD00266845 /* Frameworks */,
8BACBE8122576CAD00266845 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TIBottomSheet;
productName = TIBottomSheet2;
productReference = 8BACBE8322576CAD00266845 /* TIBottomSheet.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8B39A26321D40F8700DE2643 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = "47 Degrees";
TargetAttributes = {
8BACBE8222576CAD00266845 = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIBottomSheet" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8B39A26221D40F8700DE2643;
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8BACBE8222576CAD00266845 /* TIBottomSheet */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8BACBE8122576CAD00266845 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
4E98D4C60DCD00EB801E579E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-TIBottomSheet-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8BACBE7F22576CAD00266845 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
8B39A27721D40F8800DE2643 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8B39A27821D40F8800DE2643 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8BACBE8822576CAD00266845 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7B6955D74676A5427AC42234 /* Pods-TIBottomSheet.debug.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIBottomSheet_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIBottomSheet/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIBottomSheet;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
8BACBE8922576CAD00266845 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA57D8210790AD14BCC54A7E /* Pods-TIBottomSheet.release.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIBottomSheet_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIBottomSheet/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIBottomSheet;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIBottomSheet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8B39A27721D40F8800DE2643 /* Debug */,
8B39A27821D40F8800DE2643 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIBottomSheet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8BACBE8822576CAD00266845 /* Debug */,
8BACBE8922576CAD00266845 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TIBottomSheet.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIBottomSheet.framework"
BlueprintName = "TIBottomSheet"
ReferencedContainer = "container:TIBottomSheet.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef location = "group:TIBottomSheet.playground"></FileRef>
<FileRef
location = "group:TIBottomSheet.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019. The nef authors.</string>
</dict>
</plist>

View File

@ -0,0 +1,6 @@
#!/bin/bash
workspace="TIBottomSheet.xcworkspace"
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
open "`pwd`/$workspacePath/$workspace"

View File

@ -0,0 +1 @@
TIBottomSheet.app/Contents/MacOS/TIBottomSheet.playground

View File

@ -0,0 +1,29 @@
Pod::Spec.new do |s|
s.name = 'TIBottomSheet'
s.version = '1.56.0'
s.summary = 'Base models for creating bottom sheet view controllers'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'castlele' => 'nikita.semenov@touchin.ru',
'petropavel13' => 'ivan.smolin@touchin.ru'}
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.dependency 'TIUIElements', s.version.to_s
s.dependency 'TIUIKitCore', s.version.to_s
s.dependency 'TISwiftUtils', s.version.to_s
s.dependency 'PanModal', '~> 1.3.0'
end

View File

@ -0,0 +1,8 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TICoreGraphicsUtils' do
platform :ios, 11.0
use_frameworks!
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
end

View File

@ -22,11 +22,9 @@
import CoreGraphics.CGGeometry import CoreGraphics.CGGeometry
public typealias CGContextSize = (width: Int, height: Int)
public extension CGSize { public extension CGSize {
var ceiledContextSize: CGContextSize { var ceiledContextSize: CGSize {
(width: Int(ceil(width)), height: Int(ceil(height))) CGSize(width: ceil(width), height: ceil(height))
} }
} }

View File

@ -23,19 +23,19 @@
import UIKit import UIKit
public struct BorderDrawingOperation: DrawingOperation { public struct BorderDrawingOperation: DrawingOperation {
public var frameableContentSize: CGSize public var frameableContentRect: CGRect
public var border: CGFloat public var border: CGFloat
public var color: CGColor public var color: CGColor
public var radius: CGFloat public var radius: CGFloat
public var exteriorBorder: Bool public var exteriorBorder: Bool
public init(frameableContentSize: CGSize, public init(frameableContentRect: CGRect,
border: CGFloat, border: CGFloat,
color: CGColor, color: CGColor,
radius: CGFloat, radius: CGFloat,
exteriorBorder: Bool) { exteriorBorder: Bool) {
self.frameableContentSize = frameableContentSize self.frameableContentRect = frameableContentRect
self.border = border self.border = border
self.color = color self.color = color
self.radius = radius self.radius = radius
@ -47,10 +47,10 @@ public struct BorderDrawingOperation: DrawingOperation {
public func affectedArea(in context: CGContext?) -> CGRect { public func affectedArea(in context: CGContext?) -> CGRect {
let margin = exteriorBorder ? border : 0 let margin = exteriorBorder ? border : 0
let width = frameableContentSize.width + margin * 2 let width = frameableContentRect.width + margin * 2
let height = frameableContentSize.height + margin * 2 let height = frameableContentRect.height + margin * 2
return CGRect(origin: .zero, return CGRect(origin: frameableContentRect.origin,
size: CGSize(width: width, height: height)) size: CGSize(width: width, height: height))
} }
@ -59,7 +59,8 @@ public struct BorderDrawingOperation: DrawingOperation {
let ctxSize = drawArea.size.ceiledContextSize let ctxSize = drawArea.size.ceiledContextSize
let ctxRect = CGRect(origin: .zero, size: CGSize(width: ctxSize.width, height: ctxSize.height)) let ctxRect = CGRect(origin: frameableContentRect.origin,
size: CGSize(width: ctxSize.width, height: ctxSize.height))
let widthDiff = CGFloat(ctxSize.width) - drawArea.width // difference between context width and real width let widthDiff = CGFloat(ctxSize.width) - drawArea.width // difference between context width and real width
let heightDiff = CGFloat(ctxSize.height) - drawArea.height // difference between context height and real height let heightDiff = CGFloat(ctxSize.height) - drawArea.height // difference between context height and real height

View File

@ -45,6 +45,6 @@ public struct CALayerDrawingOperation: DrawingOperation {
context.concatenate(offsetTransform) context.concatenate(offsetTransform)
layer.render(in: context) layer.render(in: context)
offsetTransform.concatenating(offsetTransform.inverted()) context.concatenate(offsetTransform.inverted())
} }
} }

View File

@ -55,8 +55,10 @@ public struct SolidFillDrawingOperation: DrawingOperation {
switch shape { switch shape {
case let .rect(rect): case let .rect(rect):
return rect return rect
case let .ellipse(rect): case let .ellipse(rect):
return rect return rect
case let .path(path): case let .path(path):
return path.boundingBox return path.boundingBox
} }
@ -68,8 +70,10 @@ public struct SolidFillDrawingOperation: DrawingOperation {
switch shape { switch shape {
case let .rect(rect): case let .rect(rect):
context.fill(rect) context.fill(rect)
case let .ellipse(rect): case let .ellipse(rect):
context.fillEllipse(in: rect) context.fillEllipse(in: rect)
case let .path(path): case let .path(path):
context.addPath(path) context.addPath(path)
context.fillPath() context.fillPath()

View File

@ -0,0 +1,57 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public struct TemplateDrawingOperation: OrientationAwareDrawingOperation {
public var image: CGImage
public var imageSize: CGSize
public var color: CGColor
public var flipHorizontallyDuringDrawing: Bool
public init(image: CGImage,
imageSize: CGSize,
color: CGColor,
flipHorizontallyDuringDrawing: Bool = true) {
self.image = image
self.imageSize = imageSize
self.color = color
self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing
}
public func affectedArea(in context: CGContext?) -> CGRect {
CGRect(origin: .zero, size: imageSize)
}
public func apply(in context: CGContext) {
let imageRect = CGRect(origin: .zero, size: imageSize)
apply(in: context) {
$0.setFillColor(color)
$0.clip(to: imageRect, mask: image)
$0.fill(imageRect)
$0.setBlendMode(.multiply)
}
}
}

View File

@ -26,48 +26,45 @@ import CoreText
public struct TextDrawingOperation: OrientationAwareDrawingOperation { public struct TextDrawingOperation: OrientationAwareDrawingOperation {
public var text: String public var text: String
public var font: CTFont public var textAttributes: [NSAttributedString.Key: Any]
public var textColor: CGColor
public var flipHorizontallyDuringDrawing: Bool public var flipHorizontallyDuringDrawing: Bool
public var desiredOffset: CGPoint public var desiredOffset: CGPoint
private var line: CTLine { public var attributedString: NSAttributedString {
let textAttributes: [NSAttributedString.Key : Any] = [ NSAttributedString(string: text, attributes: textAttributes)
.font: font, }
.foregroundColor: textColor
]
let attributedString = NSAttributedString(string: text, attributes: textAttributes) public var line: CTLine {
CTLineCreateWithAttributedString(attributedString)
}
return CTLineCreateWithAttributedString(attributedString) public var desiredContextSize: CGSize {
let imageBounds = CTLineGetImageBounds(line, nil)
return CGSize(width: max(imageBounds.width, imageBounds.maxX) + desiredOffset.x,
height: max(imageBounds.height, imageBounds.maxY) + desiredOffset.y)
} }
public init(text: String, public init(text: String,
font: CTFont, textAttributes: [NSAttributedString.Key: Any],
textColor: CGColor,
flipHorizontallyDuringDrawing: Bool = true, flipHorizontallyDuringDrawing: Bool = true,
desiredOffset: CGPoint = .zero) { desiredOffset: CGPoint = .zero) {
self.text = text self.text = text
self.font = font self.textAttributes = textAttributes
self.textColor = textColor
self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing self.flipHorizontallyDuringDrawing = flipHorizontallyDuringDrawing
self.desiredOffset = desiredOffset self.desiredOffset = desiredOffset
} }
// MARK: - DrawingOperation
public func affectedArea(in context: CGContext? = nil) -> CGRect { public func affectedArea(in context: CGContext? = nil) -> CGRect {
CGRect(origin: desiredOffset, size: CTLineGetImageBounds(line, context).size) CTLineGetImageBounds(line, context).offset(by: desiredOffset)
} }
public func apply(in context: CGContext) { public func apply(in context: CGContext) {
apply(in: context) { apply(in: context) {
let originForDrawing = offsetForDrawing(CTLineGetImageBounds(line, context).origin) $0.textPosition = offsetForDrawing(affectedArea(in: context).origin)
let desiredOffsetForDrawing = offsetForDrawing(desiredOffset)
let textPosition = CGPoint(x: desiredOffsetForDrawing.x - originForDrawing.x,
y: desiredOffsetForDrawing.y - originForDrawing.y)
$0.textPosition = textPosition
CTLineDraw(line, $0) CTLineDraw(line, $0)
} }
} }

View File

@ -0,0 +1,5 @@
# gitignore nef files
**/build/
**/nef/
LICENSE
end

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>launcher</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.fortysevendeg.nef</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 The nef Authors. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
## gitignore nef files
**/build/
**/nef/
LICENSE
## User data
**/xcuserdata/
podfile.lock
**.DS_Store
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## CocoaPods
**Pods**
## Carthage
**Carthage**
## SPM
.build
.swiftpm
swiftpm

View File

@ -0,0 +1,8 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TICoreGraphicsUtils' do
platform :ios, 12.0
use_frameworks!
pod 'TICoreGraphicsUtils', :path => '../../../../TICoreGraphicsUtils/TICoreGraphicsUtils.podspec'
end

View File

@ -0,0 +1,128 @@
/*:
# `DrawingOperation` - протокол для инкапсулирования низкоуровневых вызовов отрисовки CoreGraphicss.
Позволяет:
- сгруппировать низкоуровневые операции CoreGraphics в более высокоуровневые
- использовать композицию из высокоуровневых операций
- получить размер изображения необходимый для создания контекста
## Базовые операции
"Из коробки" доступны самые часто испольуемые операции.
*/
/*:
### `SolidFillDrawingOperation`
Операция для заполнения области рисования выбранным цветом и выбранной формой.
Например, можно нарисовать прямоугольник или круг на существующей области рисования
или просто создать изображение с однотонный фоном.
*/
import TICoreGraphicsUtils
import UIKit
let solidFillSize = CGSize(width: 200, height: 200)
let renderer = UIGraphicsImageRenderer(size: solidFillSize)
let solidFillDrawingOperation = SolidFillDrawingOperation(color: UIColor.purple.cgColor,
rect: CGRect(origin: .zero,
size: solidFillSize))
let solidFillImage = renderer.image {
solidFillDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `BorderDrawingOperation`
Операция создаёт рамку определённой формы и размера.
*/
let borderDrawingOperation = BorderDrawingOperation(frameableContentRect: CGRect(origin: .zero,
size: solidFillSize),
border: 2,
color: UIColor.red.cgColor,
radius: 4,
exteriorBorder: false)
let borderImage = renderer.image {
borderDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `CALayerDrawingOperation`
Операция отрисовывает содержимое CALayer в изображение. Обычно используется для снапшота UIView.
*/
let button = UIButton(type: .custom)
button.setTitle("This is button", for: .normal)
button.setBackgroundImage(borderImage, for: .normal)
button.sizeToFit()
let layerDrawingOperation = CALayerDrawingOperation(layer: button.layer, offset: .zero)
let layerRenderer = UIGraphicsImageRenderer(size: button.bounds.size)
let buttonSnapshotImage = layerRenderer.image {
layerDrawingOperation.apply(in: $0.cgContext)
}
/*:
### `TextDrawingOperation`
Операция отрисовывает текст с заданными атрибутами
*/
let textDrawingOperaton = TextDrawingOperation(text: "This is string",
textAttributes: [
.font: UIFont.boldSystemFont(ofSize: 15),
.foregroundColor: UIColor.white
])
let textRenderer = UIGraphicsImageRenderer(size: textDrawingOperaton.desiredContextSize)
let textImage = textRenderer.image {
textDrawingOperaton.apply(in: $0.cgContext)
}
/*:
### `TemplateDrawingOperation`
Операция заменяет все цвета кроме прозрачного на переданный цвет.
Используется для приданиям иконкам определённого цвета (аналог tintColor).
*/
if let cgImage = textImage.cgImage {
let templateDrawingOperation = TemplateDrawingOperation(image: cgImage,
imageSize: textImage.size,
color: UIColor.red.cgColor)
let tintedImage = textRenderer.image {
templateDrawingOperation.apply(in: $0.cgContext)
}
}
/*:
### `TransformDrawingOperation`
Операция производит изменение размера изображения учитывая пропорции
и другие настройки (по аналогии с contentMode у UIImage).
*/
if let cgImage = textImage.cgImage {
let newSize = CGSize(width: textImage.size.width / 1.5, height: textImage.size.height)
let resizeOperation = TransformDrawingOperation(image: cgImage,
imageSize: textImage.size,
maxNewSize: newSize,
resizeMode: .scaleAspectFill)
let resizeRenderer = UIGraphicsImageRenderer(size: newSize)
let resizedImage = resizeRenderer.image {
resizeOperation.apply(in: $0.cgContext)
}
}

View File

@ -0,0 +1,30 @@
import UIKit
public protocol NefPlaygroundLiveViewable {}
extension UIView: NefPlaygroundLiveViewable {}
extension UIViewController: NefPlaygroundLiveViewable {}
#if NOT_IN_PLAYGROUND
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {}
public static func needsIndefiniteExecution(_ state: Bool) {}
}
}
#else
import PlaygroundSupport
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {
PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable)
}
public static func needsIndefiniteExecution(_ state: Bool) {
PlaygroundPage.current.needsIndefiniteExecution = state
}
}
}
#endif

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true'/>

View File

@ -0,0 +1,396 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
A319933CF180B210C469FFB1 /* Pods_TICoreGraphicsUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TICoreGraphicsUtils.debug.xcconfig"; path = "Target Support Files/Pods-TICoreGraphicsUtils/Pods-TICoreGraphicsUtils.debug.xcconfig"; sourceTree = "<group>"; };
8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TICoreGraphicsUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TICoreGraphicsUtils.release.xcconfig"; path = "Target Support Files/Pods-TICoreGraphicsUtils/Pods-TICoreGraphicsUtils.release.xcconfig"; sourceTree = "<group>"; };
AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TICoreGraphicsUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8BACBE8022576CAD00266845 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A319933CF180B210C469FFB1 /* Pods_TICoreGraphicsUtils.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2238D5BA030C14EBEF939AC4 /* Pods */ = {
isa = PBXGroup;
children = (
82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */,
9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
8B39A26221D40F8700DE2643 = {
isa = PBXGroup;
children = (
8BACBE8422576CAD00266845 /* TICoreGraphicsUtils */,
8B39A26C21D40F8700DE2643 /* Products */,
2238D5BA030C14EBEF939AC4 /* Pods */,
B51B4BA44249652EDC4FBF61 /* Frameworks */,
);
sourceTree = "<group>";
};
8B39A26C21D40F8700DE2643 /* Products */ = {
isa = PBXGroup;
children = (
8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */,
);
name = Products;
sourceTree = "<group>";
};
8BACBE8422576CAD00266845 /* TICoreGraphicsUtils */ = {
isa = PBXGroup;
children = (
8BACBE8622576CAD00266845 /* Info.plist */,
);
path = TICoreGraphicsUtils;
sourceTree = "<group>";
};
B51B4BA44249652EDC4FBF61 /* Frameworks */ = {
isa = PBXGroup;
children = (
AE2E24C8921A6FA2CDB65242 /* Pods_TICoreGraphicsUtils.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8BACBE7E22576CAD00266845 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8BACBE8222576CAD00266845 /* TICoreGraphicsUtils */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TICoreGraphicsUtils" */;
buildPhases = (
3A6585E20D9DE6C1873231DC /* [CP] Check Pods Manifest.lock */,
8BACBE7E22576CAD00266845 /* Headers */,
8BACBE7F22576CAD00266845 /* Sources */,
8BACBE8022576CAD00266845 /* Frameworks */,
8BACBE8122576CAD00266845 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TICoreGraphicsUtils;
productName = TICoreGraphicsUtils2;
productReference = 8BACBE8322576CAD00266845 /* TICoreGraphicsUtils.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8B39A26321D40F8700DE2643 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = "47 Degrees";
TargetAttributes = {
8BACBE8222576CAD00266845 = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TICoreGraphicsUtils" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8B39A26221D40F8700DE2643;
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8BACBE8222576CAD00266845 /* TICoreGraphicsUtils */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8BACBE8122576CAD00266845 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3A6585E20D9DE6C1873231DC /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-TICoreGraphicsUtils-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8BACBE7F22576CAD00266845 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
8B39A27721D40F8800DE2643 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8B39A27821D40F8800DE2643 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8BACBE8822576CAD00266845 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 82AF4D8AFDF24586DD661012 /* Pods-TICoreGraphicsUtils.debug.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TICoreGraphicsUtils_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TICoreGraphicsUtils/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TICoreGraphicsUtils;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
8BACBE8922576CAD00266845 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9238CAF7BE416E9ACF0C153E /* Pods-TICoreGraphicsUtils.release.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TICoreGraphicsUtils_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TICoreGraphicsUtils/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TICoreGraphicsUtils;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TICoreGraphicsUtils" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8B39A27721D40F8800DE2643 /* Debug */,
8B39A27821D40F8800DE2643 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TICoreGraphicsUtils" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8BACBE8822576CAD00266845 /* Debug */,
8BACBE8922576CAD00266845 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TICoreGraphicsUtils.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TICoreGraphicsUtils.framework"
BlueprintName = "TICoreGraphicsUtils"
ReferencedContainer = "container:TICoreGraphicsUtils.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef location = "group:TICoreGraphicsUtils.playground"></FileRef>
<FileRef
location = "group:TICoreGraphicsUtils.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019. The nef authors.</string>
</dict>
</plist>

View File

@ -0,0 +1,6 @@
#!/bin/bash
workspace="TICoreGraphicsUtils.xcworkspace"
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
open "`pwd`/$workspacePath/$workspace"

View File

@ -0,0 +1 @@
TICoreGraphicsUtils.app/Contents/MacOS/TICoreGraphicsUtils.playground

View File

@ -0,0 +1,23 @@
Pod::Spec.new do |s|
s.name = 'TICoreGraphicsUtils'
s.version = '1.56.0'
s.summary = 'CoreGraphics drawing helpers'
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/LeadKit/src/tag/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' }
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '12.0'
s.swift_versions = ['5.7']
sources = 'Sources/**/*'
if ENV["DEVELOPMENT_INSTALL"] # installing using :path =>
s.source_files = sources
s.exclude_files = s.name + '.app'
else
s.source_files = s.name + '/' + sources
s.exclude_files = s.name + '/*.app'
end
s.framework = 'CoreGraphics'
end

View File

@ -0,0 +1,11 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TIDeeplink' do
platform :ios, 11
use_frameworks!
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIDeeplink', :path => '../../../../TIDeeplink/TIDeeplink.podspec'
end

View File

@ -0,0 +1,42 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import TISwiftUtils
public struct AnyDeeplinkHandler<Deeplink>: DeeplinkHandler {
private let handlerClosure: Closure<Deeplink, Operation?>
public init<Handler: DeeplinkHandler>(handler: Handler) where Handler.Deeplink == Deeplink {
self.handlerClosure = handler.handle
}
public func handle(deeplink: Deeplink) -> Operation? {
handlerClosure(deeplink)
}
}
public extension DeeplinkHandler {
func eraseToAnyDeeplinkHandler() -> AnyDeeplinkHandler<Deeplink> {
AnyDeeplinkHandler(handler: self)
}
}

View File

@ -0,0 +1,94 @@
//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import TISwiftUtils
import UIKit
open class BaseNavigationStackDeeplinkHandler<Deeplink>: DeeplinkHandler {
public var rootViewController: UIViewController
public var asAnyHandlerClosure: Closure<UIViewController, AnyDeeplinkHandler<Deeplink>?>
public init(rootViewController: UIViewController,
asAnyHandlerClosure: @escaping Closure<UIViewController, AnyDeeplinkHandler<Deeplink>?>) {
self.rootViewController = rootViewController
self.asAnyHandlerClosure = asAnyHandlerClosure
}
// MARK: - DeeplinkHandler
open func handle(deeplink: Deeplink) -> Operation? {
if let handler = asAnyHandlerClosure(rootViewController),
let operation = handler.handle(deeplink: deeplink) {
return operation
}
let (_, operation) = findHandler(for: deeplink, in: rootViewController)
return operation
}
// MARK: - Open methods
open func findHandler(for deeplink: Deeplink,
in viewController: UIViewController) -> (AnyDeeplinkHandler<Deeplink>?, Operation?) {
switch viewController {
case let navController as UINavigationController:
for controllerInNavigationStack in navController.viewControllers.reversed() {
if let handler = asAnyHandlerClosure(controllerInNavigationStack),
let operation = handler.handle(deeplink: deeplink) {
return (handler, operation)
}
}
case let tabController as UITabBarController:
if let selectedController = tabController.selectedViewController,
let handler = asAnyHandlerClosure(selectedController),
let operation = handler.handle(deeplink: deeplink) {
return (handler, operation)
} else if var tabControllers = tabController.viewControllers {
if tabController.selectedIndex != NSNotFound {
tabControllers.remove(at: tabController.selectedIndex)
}
for tabController in tabControllers {
if let handler = asAnyHandlerClosure(tabController),
let operation = handler.handle(deeplink: deeplink) {
return (handler, operation)
}
}
}
default:
let handler = asAnyHandlerClosure(viewController)
let operation = handler?.handle(deeplink: deeplink)
return (handler, operation)
}
return (nil, nil)
}
}

View File

@ -1,5 +1,5 @@
// //
// Copyright (c) 2022 Touch Instinct // Copyright (c) 2023 Touch Instinct
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal // of this software and associated documentation files (the Software), to deal
@ -21,8 +21,10 @@
// //
import Foundation import Foundation
import TISwiftUtils
public protocol AuthSettingsStorage: AnyObject { public protocol DeeplinkHandler {
/// Should be true by default (on app first run) associatedtype Deeplink
var shouldResetStoredAuthData: Bool { get set }
func handle(deeplink: Deeplink) -> Operation?
} }

5
TIDeepLink/TIDeeplink.app/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# gitignore nef files
**/build/
**/nef/
LICENSE

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>launcher</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIdentifier</key>
<string>com.fortysevendeg.nef</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.14</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 The nef Authors. All rights reserved.</string>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
## gitignore nef files
**/build/
**/nef/
LICENSE
## User data
**/xcuserdata/
podfile.lock
**.DS_Store
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## CocoaPods
**Pods**
## Carthage
**Carthage**
## SPM
.build
.swiftpm
swiftpm

View File

@ -0,0 +1,11 @@
ENV["DEVELOPMENT_INSTALL"] = "true"
target 'TIDeeplink' do
platform :ios, 12
use_frameworks!
pod 'TILogging', :path => '../../../../TILogging/TILogging.podspec'
pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec'
pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec'
pod 'TIDeeplink', :path => '../../../../TIDeeplink/TIDeeplink.podspec'
end

View File

@ -0,0 +1,164 @@
/*:
# Deeplink API
_TIDeeplink_ добавляет сервис `TIDeeplinksService` для обработки диплинков
## Как настроить
1. Создать объект, представляющий диплинк. Такой объект должен соответствовать протоколу `Hashable`
*/
import Foundation
import TIDeeplink
import TIFoundationUtils
import TISwiftUtils
import UIKit
enum ProjectDeeplink: Hashable {
case editProfile
case office(id: String)
}
//: 2. Создать объект, соответствующий протоколу `DeeplinkMapper`. Его задачей будет - преобразование _URL_ в диплинк
final class ProjectDeeplinkMapper: DeeplinkMapper {
func map(url: URL) -> ProjectDeeplink? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}
switch components.host {
case "office":
if let id = components.path.split(separator: "/").last {
return .office(id: String(id))
}
return nil
case "editProfile":
return .editProfile
default:
return nil
}
}
}
//: 3. Реализовать у объекта отвечающего за навигацию протокол`DeeplinkHandler`. В необходимом для реализации методе `handle(deeplink:)` нужно обработать передаваемый диплинк и вернуть соответствующее действие
class MainCoordinator: DeeplinkHandler {
func openEditProfileScreen() {
print("Presenting edit profile view controller")
}
func openOfficesScreen(completion: VoidClosure?) {
print("Presenting offices view controller")
completion?()
}
func openOfficesScreen(forId id: String) {
print("Presenting offices view controller by id: \(id)")
}
// MARK: DeeplinkHandler
func handle(deeplink: ProjectDeeplink) -> Operation? {
switch deeplink {
case .editProfile:
return BlockOperation { [weak self] in
self?.openEditProfileScreen()
}
case let .office(id):
return ClosureAsyncOperation<Void, Never> { [weak self] completion in
self?.openOfficesScreen {
self?.openOfficesScreen(forId: id)
completion(.success(()))
}
return Cancellables.nonCancellable()
}
}
}
}
/*:
**Опционально** 4. Создать сабкласс `TIDeeplinksService` для удобного использования по проекту
*/
final class ProjectDeeplinksService: TIDeeplinksService<ProjectDeeplink> {
static let shared = ProjectDeeplinksService()
convenience init(mapper: ProjectDeeplinkMapper = .init(), handler: MainCoordinator = .init()) {
self.init(mapper: mapper, handler: handler, operationQueue: .main)
}
}
/*:
Создаем и передаем в сервис Mapper и Handler.
> Так же можно воспользоваться инициализатором `init(mapper:handler:operationQueue:)`. Если Mapper и Handler передаются не в инициализаторе, то их самостоятельно необходимо привести к виду `AnyDeeplinkHandler` и `AnyDeeplinkMapper` с момощью методов: `eraseToAnyDeeplinkHandler()` и `eraseToAnyDeeplinkMapper()`
```swift
let coordinator = MainCoordinator()
let mapper = ProjectDeeplinkMapper()
ProjectDeeplinksService.shared.deeplinkMapper = mapper.eraseToAnyDeeplinkMapper()
ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkHandler()
```
В `AppDelegate` использвуем методы `deferredHandle(url:)` и `handlePendingDeeplinks()` для обработки диплинков.
- `deferredHandle(url:)`: пытается создать из _URL_ диплинк и добавить его в очередь на обработку
- `handlePendingDeeplinks()`: обрабатывает первый в череди диплинк
*/
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
ProjectDeeplinksService.shared.deferredHandle(url: url)
ProjectDeeplinksService.shared.handlePendingDeeplinks()
return true
}
guard let url = URL(string: "app://office/123") else {
fatalError()
}
application(.shared, open: url)
/*:
В качестве `DeeplinkHandler` можно использовать базовую реализацию `BaseNavigationStackDeeplinkHandler`, которая позволяет искать handler в иерархии view, способный обработать диплинк.
*/
protocol ProjectDeeplinkHandler: DeeplinkHandler where Deeplink == ProjectDeeplink {
}
class ViewController: UIViewController, ProjectDeeplinkHandler {
func handle(deeplink: ProjectDeeplink) -> Operation? {
switch deeplink {
case .editProfile:
return BlockOperation {
print("Presenting edit profile view controller")
}
case let .office(id):
return BlockOperation {
print("Presenting offices view controller by id: \(id)")
}
}
}
}
//: Создание Handler. Для настройки передается rootViewController, который должен наследоваться от `UIViewController` С этого контроллера будет начинаться поиск обработчика
let viewController = ViewController()
let navigationController = UINavigationController(rootViewController: viewController)
let handler = BaseNavigationStackDeeplinkHandler<ProjectDeeplink>(rootViewController: navigationController) {
guard let projectHandler = $0 as? (any ProjectDeeplinkHandler) else {
return nil
}
return projectHandler.eraseToAnyDeeplinkHandler()
}
//: Далее handler может передаваться для использования в `TIDeeplinksService`
let mapper = ProjectDeeplinkMapper()
let service = TIDeeplinksService(mapper: mapper, handler: handler)
service.deferredHandle(url: url)
service.handlePendingDeeplinks()
RunLoop.main.run()

View File

@ -0,0 +1,30 @@
import UIKit
public protocol NefPlaygroundLiveViewable {}
extension UIView: NefPlaygroundLiveViewable {}
extension UIViewController: NefPlaygroundLiveViewable {}
#if NOT_IN_PLAYGROUND
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {}
public static func needsIndefiniteExecution(_ state: Bool) {}
}
}
#else
import PlaygroundSupport
public enum Nef {
public enum Playground {
public static func liveView(_ view: NefPlaygroundLiveViewable) {
PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable)
}
public static func needsIndefiniteExecution(_ state: Bool) {
PlaygroundPage.current.needsIndefiniteExecution = state
}
}
}
#endif

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true' executeOnSourceChanges='true'/>

View File

@ -0,0 +1,396 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
2FC760DADC7ABCE05229EAD6 /* Pods_TIDeeplink.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EC1315C38BF91446DB6FCA4 /* Pods_TIDeeplink.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0EC1315C38BF91446DB6FCA4 /* Pods_TIDeeplink.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TIDeeplink.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6BA4C0D578FC5BA8798475E6 /* Pods-TIDeeplink.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIDeeplink.release.xcconfig"; path = "Target Support Files/Pods-TIDeeplink/Pods-TIDeeplink.release.xcconfig"; sourceTree = "<group>"; };
8BACBE8322576CAD00266845 /* TIDeeplink.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TIDeeplink.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A2898C64EB444BA1068CC72C /* Pods-TIDeeplink.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIDeeplink.debug.xcconfig"; path = "Target Support Files/Pods-TIDeeplink/Pods-TIDeeplink.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8BACBE8022576CAD00266845 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2FC760DADC7ABCE05229EAD6 /* Pods_TIDeeplink.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
154F2E18EA615FB457290B92 /* Frameworks */ = {
isa = PBXGroup;
children = (
0EC1315C38BF91446DB6FCA4 /* Pods_TIDeeplink.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
8B39A26221D40F8700DE2643 = {
isa = PBXGroup;
children = (
8BACBE8422576CAD00266845 /* TIDeeplink */,
8B39A26C21D40F8700DE2643 /* Products */,
CB5B9E4DF89ADAD1D8849E37 /* Pods */,
154F2E18EA615FB457290B92 /* Frameworks */,
);
sourceTree = "<group>";
};
8B39A26C21D40F8700DE2643 /* Products */ = {
isa = PBXGroup;
children = (
8BACBE8322576CAD00266845 /* TIDeeplink.framework */,
);
name = Products;
sourceTree = "<group>";
};
8BACBE8422576CAD00266845 /* TIDeeplink */ = {
isa = PBXGroup;
children = (
8BACBE8622576CAD00266845 /* Info.plist */,
);
path = TIDeeplink;
sourceTree = "<group>";
};
CB5B9E4DF89ADAD1D8849E37 /* Pods */ = {
isa = PBXGroup;
children = (
A2898C64EB444BA1068CC72C /* Pods-TIDeeplink.debug.xcconfig */,
6BA4C0D578FC5BA8798475E6 /* Pods-TIDeeplink.release.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8BACBE7E22576CAD00266845 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8BACBE8222576CAD00266845 /* TIDeeplink */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIDeeplink" */;
buildPhases = (
B00BE0F551706E267B42014F /* [CP] Check Pods Manifest.lock */,
8BACBE7E22576CAD00266845 /* Headers */,
8BACBE7F22576CAD00266845 /* Sources */,
8BACBE8022576CAD00266845 /* Frameworks */,
8BACBE8122576CAD00266845 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TIDeeplink;
productName = TIDeeplink2;
productReference = 8BACBE8322576CAD00266845 /* TIDeeplink.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
8B39A26321D40F8700DE2643 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = "47 Degrees";
TargetAttributes = {
8BACBE8222576CAD00266845 = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIDeeplink" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 8B39A26221D40F8700DE2643;
productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8BACBE8222576CAD00266845 /* TIDeeplink */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8BACBE8122576CAD00266845 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
B00BE0F551706E267B42014F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-TIDeeplink-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8BACBE7F22576CAD00266845 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
8B39A27721D40F8800DE2643 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8B39A27821D40F8800DE2643 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8BACBE8822576CAD00266845 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A2898C64EB444BA1068CC72C /* Pods-TIDeeplink.debug.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIDeeplink_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIDeeplink/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIDeeplink;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
8BACBE8922576CAD00266845 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6BA4C0D578FC5BA8798475E6 /* Pods-TIDeeplink.release.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Manual;
CURRENT_TIDeeplink_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "$(SRCROOT)/TIDeeplink/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIDeeplink;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIDeeplink" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8B39A27721D40F8800DE2643 /* Debug */,
8B39A27821D40F8800DE2643 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIDeeplink" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8BACBE8822576CAD00266845 /* Debug */,
8BACBE8922576CAD00266845 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 8B39A26321D40F8700DE2643 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:TIDeeplink.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIDeeplink.framework"
BlueprintName = "TIDeeplink"
ReferencedContainer = "container:TIDeeplink.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIDeeplink.framework"
BlueprintName = "TIDeeplink"
ReferencedContainer = "container:TIDeeplink.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8BACBE8222576CAD00266845"
BuildableName = "TIDeeplink.framework"
BlueprintName = "TIDeeplink"
ReferencedContainer = "container:TIDeeplink.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:TIDeeplink.playground">
</FileRef>
<FileRef
location = "group:TIDeeplink.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
<FileRef
location = "group:../../../../TIDeepLink/TIDeeplink.app/Contents/MacOS/Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019. The nef authors.</string>
</dict>
</plist>

View File

@ -0,0 +1,6 @@
#!/bin/bash
workspace="TIDeeplink.xcworkspace"
workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev)
open "`pwd`/$workspacePath/$workspace"

View File

@ -0,0 +1 @@
TIDeeplink.app/Contents/MacOS/TIDeeplink.playground

Some files were not shown because too many files have changed in this diff Show More