Compare commits
16 Commits
v1.0.0
...
example-ui
| Author | SHA1 | Date | |
|---|---|---|---|
| d400fc7d14 | |||
| baa29a2838 | |||
| ece274dfb2 | |||
| 43b9c9b3db | |||
| c54b4a353a | |||
| c16b72e8a8 | |||
| 22f4b0e895 | |||
| 2caa7ed0dc | |||
| 2883fa121f | |||
| 5bdd0d75a2 | |||
| 5f95198b36 | |||
| f62fcc70e0 | |||
| c8bbea2846 | |||
| 0eda4769b8 | |||
| 19a94e592a | |||
| 043bf58bcb |
@@ -9,8 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
3908002824474A3800E7727C /* BlobMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = 3908002624474A3800E7727C /* BlobMenu.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
393AAE9E246053B60059752A /* Transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE82246053B50059752A /* Transitions.swift */; };
|
||||
393AAE9F246053B60059752A /* BlobMenuEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE84246053B50059752A /* BlobMenuEnvironment.swift */; };
|
||||
393AAEA0246053B60059752A /* MenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE85246053B50059752A /* MenuItem.swift */; };
|
||||
393AAEA0246053B60059752A /* BlobMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE85246053B50059752A /* BlobMenuItem.swift */; };
|
||||
393AAEA1246053B60059752A /* StickyEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE87246053B50059752A /* StickyEffectView.swift */; };
|
||||
393AAEA2246053B60059752A /* HamburgerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE88246053B50059752A /* HamburgerView.swift */; };
|
||||
393AAEA3246053B60059752A /* MenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE89246053B50059752A /* MenuItemView.swift */; };
|
||||
@@ -32,6 +31,9 @@
|
||||
393AAEB3246053B60059752A /* AnimationCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE9B246053B50059752A /* AnimationCompletion.swift */; };
|
||||
393AAEB4246053B60059752A /* BezierUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE9C246053B50059752A /* BezierUtilities.swift */; };
|
||||
393AAEB5246053B60059752A /* CommonUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393AAE9D246053B50059752A /* CommonUtilities.swift */; };
|
||||
39484546247823ED0046236D /* BlobMenuModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39484545247823ED0046236D /* BlobMenuModel.swift */; };
|
||||
398270BD2474B93100BB7A2B /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270BC2474B93100BB7A2B /* CGRect+Extensions.swift */; };
|
||||
39A635A02478843A007946A6 /* BlobMenuConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A6359F2478843A007946A6 /* BlobMenuConfiguration.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -39,8 +41,7 @@
|
||||
3908002624474A3800E7727C /* BlobMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BlobMenu.h; sourceTree = "<group>"; };
|
||||
3908002724474A3800E7727C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
393AAE82246053B50059752A /* Transitions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Transitions.swift; sourceTree = "<group>"; };
|
||||
393AAE84246053B50059752A /* BlobMenuEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobMenuEnvironment.swift; sourceTree = "<group>"; };
|
||||
393AAE85246053B50059752A /* MenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuItem.swift; sourceTree = "<group>"; };
|
||||
393AAE85246053B50059752A /* BlobMenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobMenuItem.swift; sourceTree = "<group>"; };
|
||||
393AAE87246053B50059752A /* StickyEffectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickyEffectView.swift; sourceTree = "<group>"; };
|
||||
393AAE88246053B50059752A /* HamburgerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HamburgerView.swift; sourceTree = "<group>"; };
|
||||
393AAE89246053B50059752A /* MenuItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuItemView.swift; sourceTree = "<group>"; };
|
||||
@@ -62,6 +63,9 @@
|
||||
393AAE9B246053B50059752A /* AnimationCompletion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationCompletion.swift; sourceTree = "<group>"; };
|
||||
393AAE9C246053B50059752A /* BezierUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BezierUtilities.swift; sourceTree = "<group>"; };
|
||||
393AAE9D246053B50059752A /* CommonUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonUtilities.swift; sourceTree = "<group>"; };
|
||||
39484545247823ED0046236D /* BlobMenuModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlobMenuModel.swift; sourceTree = "<group>"; };
|
||||
398270BC2474B93100BB7A2B /* CGRect+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect+Extensions.swift"; sourceTree = "<group>"; };
|
||||
39A6359F2478843A007946A6 /* BlobMenuConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlobMenuConfiguration.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -95,9 +99,10 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
393AAE86246053B50059752A /* Views */,
|
||||
39A6359E2478841F007946A6 /* Configuration */,
|
||||
393AAE83246053B50059752A /* Models */,
|
||||
393AAE81246053B50059752A /* Effects */,
|
||||
393AAE8E246053B50059752A /* Extensions */,
|
||||
393AAE83246053B50059752A /* Models */,
|
||||
393AAE95246053B50059752A /* Utilities */,
|
||||
3908002624474A3800E7727C /* BlobMenu.h */,
|
||||
3908002724474A3800E7727C /* Info.plist */,
|
||||
@@ -116,8 +121,8 @@
|
||||
393AAE83246053B50059752A /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
393AAE84246053B50059752A /* BlobMenuEnvironment.swift */,
|
||||
393AAE85246053B50059752A /* MenuItem.swift */,
|
||||
393AAE85246053B50059752A /* BlobMenuItem.swift */,
|
||||
39484545247823ED0046236D /* BlobMenuModel.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -131,7 +136,6 @@
|
||||
393AAE8C246053B50059752A /* StickyPathGenerator.swift */,
|
||||
393AAE88246053B50059752A /* HamburgerView.swift */,
|
||||
393AAE89246053B50059752A /* MenuItemView.swift */,
|
||||
393AAE8D246053B50059752A /* Theme.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -140,6 +144,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
393AAE8F246053B50059752A /* CGSize+Extensions.swift */,
|
||||
398270BC2474B93100BB7A2B /* CGRect+Extensions.swift */,
|
||||
393AAE90246053B50059752A /* UIGestureRecognizer+Extensions.swift */,
|
||||
393AAE91246053B50059752A /* UIWindow+Extensions.swift */,
|
||||
393AAE92246053B50059752A /* CGPoint+Extensions.swift */,
|
||||
@@ -164,6 +169,15 @@
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
39A6359E2478841F007946A6 /* Configuration */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
393AAE8D246053B50059752A /* Theme.swift */,
|
||||
39A6359F2478843A007946A6 /* BlobMenuConfiguration.swift */,
|
||||
);
|
||||
path = Configuration;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -253,7 +267,10 @@
|
||||
393AAE9E246053B60059752A /* Transitions.swift in Sources */,
|
||||
393AAEAE246053B60059752A /* ScaleKeyframesAnimation.swift in Sources */,
|
||||
393AAEB1246053B60059752A /* KayframesAnimation.swift in Sources */,
|
||||
393AAEA0246053B60059752A /* MenuItem.swift in Sources */,
|
||||
39484546247823ED0046236D /* BlobMenuModel.swift in Sources */,
|
||||
398270BD2474B93100BB7A2B /* CGRect+Extensions.swift in Sources */,
|
||||
393AAEA0246053B60059752A /* BlobMenuItem.swift in Sources */,
|
||||
39A635A02478843A007946A6 /* BlobMenuConfiguration.swift in Sources */,
|
||||
393AAEAA246053B60059752A /* UIWindow+Extensions.swift in Sources */,
|
||||
393AAEB2246053B60059752A /* Then.swift in Sources */,
|
||||
393AAEA7246053B60059752A /* Theme.swift in Sources */,
|
||||
@@ -262,7 +279,6 @@
|
||||
393AAEA1246053B60059752A /* StickyEffectView.swift in Sources */,
|
||||
393AAEA4246053B60059752A /* BlobMenuView.swift in Sources */,
|
||||
393AAEA3246053B60059752A /* MenuItemView.swift in Sources */,
|
||||
393AAE9F246053B60059752A /* BlobMenuEnvironment.swift in Sources */,
|
||||
393AAEAD246053B60059752A /* Comparable+Extensions.swift in Sources */,
|
||||
393AAEAC246053B60059752A /* Collection+Extensions.swift in Sources */,
|
||||
393AAEB0246053B60059752A /* SizeKeyframesAnimation.swift in Sources */,
|
||||
|
||||
@@ -9,13 +9,27 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
3908003B2447D01D00E7727C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3908003A2447D01D00E7727C /* AppDelegate.swift */; };
|
||||
3908003D2447D01D00E7727C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3908003C2447D01D00E7727C /* SceneDelegate.swift */; };
|
||||
3908003F2447D01D00E7727C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3908003E2447D01D00E7727C /* ContentView.swift */; };
|
||||
3908003F2447D01D00E7727C /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3908003E2447D01D00E7727C /* RootView.swift */; };
|
||||
390800412447D01F00E7727C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 390800402447D01F00E7727C /* Assets.xcassets */; };
|
||||
390800442447D01F00E7727C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 390800432447D01F00E7727C /* Preview Assets.xcassets */; };
|
||||
390800472447D01F00E7727C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 390800452447D01F00E7727C /* LaunchScreen.storyboard */; };
|
||||
3908005A2447D25700E7727C /* BlobMenu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 390800562447D23300E7727C /* BlobMenu.framework */; };
|
||||
3908005B2447D25700E7727C /* BlobMenu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 390800562447D23300E7727C /* BlobMenu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
390800602447D49200E7727C /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 3908005F2447D49200E7727C /* README.md */; };
|
||||
393BF6972474131C004D193D /* PaginatedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393BF6962474131C004D193D /* PaginatedScrollView.swift */; };
|
||||
393BF69A24742B4F004D193D /* ExtendedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393BF69924742B4F004D193D /* ExtendedScrollView.swift */; };
|
||||
3948454224774B8A0046236D /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948454124774B8A0046236D /* Theme.swift */; };
|
||||
3948454424774EF40046236D /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948454324774EF40046236D /* Screen.swift */; };
|
||||
394DFE2A2477385700D89A1B /* RandomIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270D224772F4100BB7A2B /* RandomIcon.swift */; };
|
||||
398270BF2475756E00BB7A2B /* ExchangeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270BE2475756E00BB7A2B /* ExchangeView.swift */; };
|
||||
398270C1247575A600BB7A2B /* CommerceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270C0247575A600BB7A2B /* CommerceView.swift */; };
|
||||
398270C3247575B500BB7A2B /* StocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270C2247575B500BB7A2B /* StocksView.swift */; };
|
||||
398270CB2475F02B00BB7A2B /* PageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270CA2475F02B00BB7A2B /* PageControl.swift */; };
|
||||
398270D12476A60600BB7A2B /* TouchGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270D02476A60600BB7A2B /* TouchGesture.swift */; };
|
||||
39A6D8AB246172AE0090F507 /* WalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A6D8AA246172AE0090F507 /* WalletView.swift */; };
|
||||
39A6D8AD2461856B0090F507 /* GridStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A6D8AC2461856B0090F507 /* GridStack.swift */; };
|
||||
39A6D8AF24619B650090F507 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A6D8AE24619B650090F507 /* Utilities.swift */; };
|
||||
39A6D8B22461AC2F0090F507 /* Lorem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A6D8B12461AC2F0090F507 /* Lorem.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -50,16 +64,30 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
390800372447D01D00E7727C /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
390800372447D01D00E7727C /* Blob Menu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Blob Menu.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3908003A2447D01D00E7727C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
3908003C2447D01D00E7727C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
3908003E2447D01D00E7727C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
3908003E2447D01D00E7727C /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
||||
390800402447D01F00E7727C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
390800432447D01F00E7727C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
390800462447D01F00E7727C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
390800482447D01F00E7727C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
390800512447D23300E7727C /* BlobMenu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BlobMenu.xcodeproj; path = ../BlobMenu.xcodeproj; sourceTree = "<group>"; };
|
||||
3908005F2447D49200E7727C /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../../README.md; sourceTree = "<group>"; };
|
||||
393BF6962474131C004D193D /* PaginatedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedScrollView.swift; sourceTree = "<group>"; };
|
||||
393BF69924742B4F004D193D /* ExtendedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedScrollView.swift; sourceTree = "<group>"; };
|
||||
3948454124774B8A0046236D /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||
3948454324774EF40046236D /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
|
||||
398270BE2475756E00BB7A2B /* ExchangeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangeView.swift; sourceTree = "<group>"; };
|
||||
398270C0247575A600BB7A2B /* CommerceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommerceView.swift; sourceTree = "<group>"; };
|
||||
398270C2247575B500BB7A2B /* StocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StocksView.swift; sourceTree = "<group>"; };
|
||||
398270CA2475F02B00BB7A2B /* PageControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageControl.swift; sourceTree = "<group>"; };
|
||||
398270D02476A60600BB7A2B /* TouchGesture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchGesture.swift; sourceTree = "<group>"; };
|
||||
398270D224772F4100BB7A2B /* RandomIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomIcon.swift; sourceTree = "<group>"; };
|
||||
39A6D8AA246172AE0090F507 /* WalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletView.swift; sourceTree = "<group>"; };
|
||||
39A6D8AC2461856B0090F507 /* GridStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridStack.swift; sourceTree = "<group>"; };
|
||||
39A6D8AE24619B650090F507 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
||||
39A6D8B12461AC2F0090F507 /* Lorem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lorem.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -87,7 +115,7 @@
|
||||
390800382447D01D00E7727C /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
390800372447D01D00E7727C /* Example.app */,
|
||||
390800372447D01D00E7727C /* Blob Menu.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -97,6 +125,8 @@
|
||||
children = (
|
||||
3908004E2447D04800E7727C /* App */,
|
||||
390800502447D0D500E7727C /* Screens */,
|
||||
393BF69824741D33004D193D /* UI */,
|
||||
39A6D8B02461AC180090F507 /* Utilities */,
|
||||
3908004F2447D05A00E7727C /* Resources */,
|
||||
390800422447D01F00E7727C /* Preview Content */,
|
||||
);
|
||||
@@ -116,6 +146,7 @@
|
||||
children = (
|
||||
3908003A2447D01D00E7727C /* AppDelegate.swift */,
|
||||
3908003C2447D01D00E7727C /* SceneDelegate.swift */,
|
||||
3948454124774B8A0046236D /* Theme.swift */,
|
||||
);
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
@@ -134,7 +165,11 @@
|
||||
390800502447D0D500E7727C /* Screens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3908003E2447D01D00E7727C /* ContentView.swift */,
|
||||
3908003E2447D01D00E7727C /* RootView.swift */,
|
||||
39A6D8AA246172AE0090F507 /* WalletView.swift */,
|
||||
398270BE2475756E00BB7A2B /* ExchangeView.swift */,
|
||||
398270C0247575A600BB7A2B /* CommerceView.swift */,
|
||||
398270C2247575B500BB7A2B /* StocksView.swift */,
|
||||
);
|
||||
path = Screens;
|
||||
sourceTree = "<group>";
|
||||
@@ -154,6 +189,29 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
393BF69824741D33004D193D /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
39A6D8AC2461856B0090F507 /* GridStack.swift */,
|
||||
398270CA2475F02B00BB7A2B /* PageControl.swift */,
|
||||
393BF6962474131C004D193D /* PaginatedScrollView.swift */,
|
||||
393BF69924742B4F004D193D /* ExtendedScrollView.swift */,
|
||||
398270D224772F4100BB7A2B /* RandomIcon.swift */,
|
||||
);
|
||||
path = UI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
39A6D8B02461AC180090F507 /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
398270D02476A60600BB7A2B /* TouchGesture.swift */,
|
||||
39A6D8AE24619B650090F507 /* Utilities.swift */,
|
||||
39A6D8B12461AC2F0090F507 /* Lorem.swift */,
|
||||
3948454324774EF40046236D /* Screen.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -173,7 +231,7 @@
|
||||
);
|
||||
name = Example;
|
||||
productName = Example;
|
||||
productReference = 390800372447D01D00E7727C /* Example.app */;
|
||||
productReference = 390800372447D01D00E7727C /* Blob Menu.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@@ -244,9 +302,23 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
39A6D8AF24619B650090F507 /* Utilities.swift in Sources */,
|
||||
398270C1247575A600BB7A2B /* CommerceView.swift in Sources */,
|
||||
39A6D8AD2461856B0090F507 /* GridStack.swift in Sources */,
|
||||
398270D12476A60600BB7A2B /* TouchGesture.swift in Sources */,
|
||||
398270CB2475F02B00BB7A2B /* PageControl.swift in Sources */,
|
||||
3908003B2447D01D00E7727C /* AppDelegate.swift in Sources */,
|
||||
398270BF2475756E00BB7A2B /* ExchangeView.swift in Sources */,
|
||||
393BF69A24742B4F004D193D /* ExtendedScrollView.swift in Sources */,
|
||||
393BF6972474131C004D193D /* PaginatedScrollView.swift in Sources */,
|
||||
3948454224774B8A0046236D /* Theme.swift in Sources */,
|
||||
3908003D2447D01D00E7727C /* SceneDelegate.swift in Sources */,
|
||||
3908003F2447D01D00E7727C /* ContentView.swift in Sources */,
|
||||
39A6D8B22461AC2F0090F507 /* Lorem.swift in Sources */,
|
||||
398270C3247575B500BB7A2B /* StocksView.swift in Sources */,
|
||||
3908003F2447D01D00E7727C /* RootView.swift in Sources */,
|
||||
3948454424774EF40046236D /* Screen.swift in Sources */,
|
||||
39A6D8AB246172AE0090F507 /* WalletView.swift in Sources */,
|
||||
394DFE2A2477385700D89A1B /* RandomIcon.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -400,7 +472,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.Example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PRODUCT_NAME = "Blob Menu";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
@@ -420,7 +492,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.Example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PRODUCT_NAME = "Blob Menu";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import UIKit
|
||||
import BlobMenu
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
|
||||
@@ -9,16 +9,15 @@
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
|
||||
let contentView = ContentView()
|
||||
if let windowScene = scene as? UIWindowScene {
|
||||
let window = UIWindow(windowScene: windowScene)
|
||||
window.rootViewController = UIHostingController(rootView: contentView)
|
||||
window.rootViewController = UIHostingController(rootView: RootView())
|
||||
self.window = window
|
||||
window.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Theme.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 22.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
static let lightGray = Color(#colorLiteral(red: 0.899865165, green: 0.899865165, blue: 0.899865165, alpha: 1))
|
||||
|
||||
static var background: Color {
|
||||
return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.1960526407, green: 0.1960932612, blue: 0.1960500479, alpha: 1) : #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) })
|
||||
}
|
||||
|
||||
static var contrast: Color {
|
||||
return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.9705940673, green: 0.9705940673, blue: 0.9705940673, alpha: 1) : #colorLiteral(red: 0.1960526407, green: 0.1960932612, blue: 0.1960500479, alpha: 1) })
|
||||
}
|
||||
|
||||
static var shadow: Color { contrast.opacity(0.2) }
|
||||
static var stroke = Color.gray
|
||||
|
||||
static var textColor: Color { contrast }
|
||||
static var bodyTextColor: Color { contrast }
|
||||
static var informationColor: Color { background.opacity(0.6) }
|
||||
static var contrastInformationColor: Color { contrast.opacity(0.6) }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// CommerceView.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 20.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CommerceView: View {
|
||||
var body: some View {
|
||||
Screen(color: .background) {
|
||||
VStack {
|
||||
self.avatar
|
||||
self.title
|
||||
self.info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var avatar: some View {
|
||||
ZStack {
|
||||
Color.background
|
||||
.clipShape(Circle())
|
||||
.overlay(Circle().stroke(Color.stroke, lineWidth: 0.5))
|
||||
.frame(size: CGSize(uniform: 60))
|
||||
.shadow(color: Color.shadow, radius: 7, y: 3)
|
||||
RandomIcon()
|
||||
}
|
||||
}
|
||||
|
||||
private var title: some View {
|
||||
Text(Lorem.words(3).capitalized)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.textColor)
|
||||
.lineLimit(1)
|
||||
.padding(.top, 40)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
private var info: some View {
|
||||
Text(Lorem.sentences(3))
|
||||
.font(.body)
|
||||
.foregroundColor(.contrastInformationColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.all)
|
||||
}
|
||||
}
|
||||
|
||||
struct CommerceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CommerceView()
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 16.04.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import BlobMenu
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
enum Screen: Int {
|
||||
case wallet
|
||||
case exchange
|
||||
case commerce
|
||||
case stocks
|
||||
}
|
||||
|
||||
@State var screen: Screen = .wallet
|
||||
@Environment(\.blobMenuEnvironment) var menuEnvironment: BlobMenuEnvironment
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
screenView.edgesIgnoringSafeArea(Edge.Set.all)
|
||||
menuView
|
||||
}
|
||||
}
|
||||
|
||||
private var screenView: some View {
|
||||
switch screen {
|
||||
case .wallet: return Rectangle().fill(Color.random)
|
||||
case .exchange: return Rectangle().fill(Color.random)
|
||||
case .commerce: return Rectangle().fill(Color.random)
|
||||
case .stocks: return Rectangle().fill(Color.random)
|
||||
}
|
||||
}
|
||||
|
||||
private var menuView: some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
BlobMenuView.createMenu(items: MenuItem.all, selectedIndex: self.screen.rawValue).padding(.bottom, 30)
|
||||
}.onReceive(menuEnvironment.$selectedIndex) { index in
|
||||
guard let screen = Screen(rawValue: index) else { return }
|
||||
self.screen = screen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
|
||||
extension MenuItem {
|
||||
static let all: [MenuItem] = [
|
||||
MenuItem(selectedIcon: Image.walletSelected, unselectedIcon: Image.walletUnselected, offset: CGPoint(x: 1, y: -2)),
|
||||
MenuItem(selectedIcon: Image.exchangeSelected, unselectedIcon: Image.exchangeUnselected),
|
||||
MenuItem(selectedIcon: Image.bitcoinSelected, unselectedIcon: Image.bitcoinUnselected),
|
||||
MenuItem(selectedIcon: Image.gridSelected, unselectedIcon: Image.gridUnselected)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
extension Image {
|
||||
static let walletSelected = Image("Icon_Wallet_black")
|
||||
static let walletUnselected = Image("Icon_Wallet_gray")
|
||||
static let bitcoinSelected = Image("Icon_Bitcoin_black")
|
||||
static let bitcoinUnselected = Image("Icon_Bitcoin_gray")
|
||||
static let exchangeSelected = Image("Icon_Exchange_black")
|
||||
static let exchangeUnselected = Image("Icon_Exchange_gray")
|
||||
static let gridSelected = Image("Icon_Grid_black")
|
||||
static let gridUnselected = Image("Icon_Grid_gray")
|
||||
}
|
||||
|
||||
|
||||
extension Color {
|
||||
static var random: Color {
|
||||
return Color(red: .random(in: 0...1),
|
||||
green: .random(in: 0...1),
|
||||
blue: .random(in: 0...1))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// ExchangeView.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 20.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ExchangeView: View {
|
||||
|
||||
@State private var currentIndex: Int = 0
|
||||
|
||||
var body: some View {
|
||||
let pagerView = SwiftUIPagerView(pages: self.pages) { index in
|
||||
withAnimation { self.currentIndex = index }
|
||||
}
|
||||
|
||||
return Screen(color: .background) {
|
||||
VStack {
|
||||
self.title
|
||||
self.description
|
||||
pagerView.frame(height: 350)
|
||||
PageControl(pagesCount: 4, index: self.$currentIndex)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var title: some View {
|
||||
Text(Lorem.words(3).capitalized)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.textColor)
|
||||
.lineLimit(1)
|
||||
.padding(.top, 40)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
private var description: some View {
|
||||
Text(Lorem.sentences(3))
|
||||
.font(.body)
|
||||
.foregroundColor(.contrastInformationColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.all)
|
||||
}
|
||||
|
||||
private var pages: [Page] {
|
||||
return (0..<4).map { index in Page() }
|
||||
}
|
||||
}
|
||||
|
||||
struct Page: View, Identifiable {
|
||||
let id = UUID()
|
||||
|
||||
var body: some View {
|
||||
Color.background
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.stroke, lineWidth: 0.5))
|
||||
.frame(height: 300)
|
||||
.shadow(color: Color.shadow, radius: 7, y: 3)
|
||||
.overlay(RandomIcon().padding(), alignment: .topLeading)
|
||||
.padding(EdgeInsets(top: 0, leading: 15, bottom: 0, trailing: 15))
|
||||
}
|
||||
}
|
||||
|
||||
struct ExchangeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ExchangeView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 16.04.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import BlobMenu
|
||||
|
||||
struct RootView: View {
|
||||
|
||||
enum Screen: Int, CaseIterable {
|
||||
case wallet
|
||||
case exchange
|
||||
case commerce
|
||||
case stocks
|
||||
}
|
||||
|
||||
@State private var screen: Screen = .wallet
|
||||
@State private var isDragging: Bool = false
|
||||
|
||||
@ObservedObject private var blobMenuModel = BlobMenuModel(items: BlobMenuItem.all)
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
screenView.edgesIgnoringSafeArea(Edge.Set.all.subtracting(.top))
|
||||
menuView.opacity(isDragging ? 0.1 : 1)
|
||||
}
|
||||
.background(Color.background)
|
||||
}
|
||||
|
||||
private var screenView: some View {
|
||||
let screen = Screen(rawValue: blobMenuModel.selectedIndex) ?? .wallet
|
||||
switch screen {
|
||||
case .wallet: return WalletView(isDragging: $isDragging.animatable).asAnyView
|
||||
case .exchange: return ExchangeView().asAnyView
|
||||
case .commerce: return CommerceView().asAnyView
|
||||
case .stocks: return StocksView(isDragging: $isDragging.animatable).asAnyView
|
||||
}
|
||||
}
|
||||
|
||||
private var menuView: some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
BlobMenuView(model: blobMenuModel).padding(.bottom, 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RootView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RootView()
|
||||
}
|
||||
}
|
||||
|
||||
extension BlobMenuItem {
|
||||
static let all: [BlobMenuItem] = [
|
||||
BlobMenuItem(selectedIcon: Image.walletSelected, unselectedIcon: Image.walletUnselected, offset: CGPoint(x: 1, y: -2)),
|
||||
BlobMenuItem(selectedIcon: Image.exchangeSelected, unselectedIcon: Image.exchangeUnselected),
|
||||
BlobMenuItem(selectedIcon: Image.bitcoinSelected, unselectedIcon: Image.bitcoinUnselected),
|
||||
BlobMenuItem(selectedIcon: Image.gridSelected, unselectedIcon: Image.gridUnselected)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
extension Image {
|
||||
static let walletSelected = Image("Icon_Wallet_black")
|
||||
static let walletUnselected = Image("Icon_Wallet_gray")
|
||||
static let bitcoinSelected = Image("Icon_Bitcoin_black")
|
||||
static let bitcoinUnselected = Image("Icon_Bitcoin_gray")
|
||||
static let exchangeSelected = Image("Icon_Exchange_black")
|
||||
static let exchangeUnselected = Image("Icon_Exchange_gray")
|
||||
static let gridSelected = Image("Icon_Grid_black")
|
||||
static let gridUnselected = Image("Icon_Grid_gray")
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// StocksView.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 20.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct StocksView: View {
|
||||
|
||||
@Binding var isDragging: Bool
|
||||
|
||||
var body: some View {
|
||||
Screen(color: .background) {
|
||||
ExtendedScrollView(isDragging: self.$isDragging, contentInset: Theme.contentInset) {
|
||||
VStack(spacing: Theme.padding) {
|
||||
self.verticalCollection
|
||||
self.horizontalCollection
|
||||
self.verticalCollection
|
||||
}
|
||||
.background(Color.background)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var verticalCollection: some View {
|
||||
GridStack(columns: 4, rows: 2, spacing: Theme.padding) { row, col in
|
||||
self.cell(size: Theme.cellSize)
|
||||
.overlay(RandomIcon().padding(), alignment: .topLeading)
|
||||
}.background(Color.background)
|
||||
}
|
||||
|
||||
private var horizontalCollection: some View {
|
||||
ExtendedScrollView(axis: .horizontal, isDragging: $isDragging, contentInset: Theme.horizontalContentInset) {
|
||||
HStack(spacing: Theme.padding) {
|
||||
ForEach(0..<10) { index in
|
||||
self.cell(size: Theme.horizontalCellSize)
|
||||
.padding(.vertical, 20)
|
||||
.overlay(RandomIcon())
|
||||
}
|
||||
}.background(Color.background)
|
||||
}
|
||||
.frame(height: Theme.horizontalCellSize.height + 40)
|
||||
}
|
||||
|
||||
private func cell(size: CGSize) -> some View {
|
||||
Color.background
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.stroke, lineWidth: 0.5))
|
||||
.frame(size: size)
|
||||
.shadow(color: Color.shadow, radius: 7, y: 3)
|
||||
}
|
||||
}
|
||||
|
||||
struct StocksView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
StocksView(isDragging: .constant(false))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Theme
|
||||
extension StocksView {
|
||||
enum Theme {
|
||||
static let padding: CGFloat = 20
|
||||
static private let w = (UIScreen.main.bounds.width - padding * 3) / 2
|
||||
|
||||
static var horizontalCellSize: CGSize {
|
||||
let h = floor(w * 0.5)
|
||||
return CGSize(width: w, height: h)
|
||||
}
|
||||
|
||||
static var cellSize: CGSize {
|
||||
let w = (UIScreen.main.bounds.width - padding * 3) / 2
|
||||
let h = floor(w * 1.25)
|
||||
return CGSize(width: w, height: h)
|
||||
}
|
||||
|
||||
static let contentInset = UIEdgeInsets(top: 20, left: 0, bottom: UIWindow.safeInsets.bottom + 20, right: 0)
|
||||
|
||||
static let horizontalContentInset = UIEdgeInsets(horizontal: 20, vertical: 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// WalletView.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 05.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct WalletView: View {
|
||||
|
||||
@Binding var isDragging: Bool
|
||||
|
||||
var body: some View {
|
||||
Screen(color: .background) {
|
||||
ExtendedScrollView(isDragging: self.$isDragging, contentInset: Theme.contentInset) {
|
||||
ForEach(0..<10) { index in
|
||||
ItemCell()
|
||||
}.background(Color.background)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private struct ItemCell: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Rectangle()
|
||||
.fill(Color.lightGray)
|
||||
.aspectRatio(5/3, contentMode: .fill)
|
||||
.layoutPriority(1)
|
||||
.overlay(RandomIcon().padding(), alignment: .topLeading)
|
||||
Text(Lorem.words(3).capitalized)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.textColor)
|
||||
.lineLimit(1)
|
||||
.padding(Edge.Set.all.subtracting(.bottom))
|
||||
Text(Lorem.paragraph)
|
||||
.font(.body)
|
||||
.foregroundColor(.contrastInformationColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.all)
|
||||
}
|
||||
.background(Color.background)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.overlay(RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color.stroke, lineWidth: 0.5))
|
||||
.shadow(color: Color.shadow, radius: 7, y: 3)
|
||||
.padding(EdgeInsets(top: 0, leading: 15, bottom: 5, trailing: 15))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Theme
|
||||
extension WalletView {
|
||||
enum Theme {
|
||||
static let contentInset = UIEdgeInsets(top: 20, left: 0, bottom: UIWindow.safeInsets.bottom + 20, right: 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// ExtendedScrollView.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 19.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ExtendedScrollView: UIViewRepresentable {
|
||||
|
||||
private let isDragging: Binding<Bool>
|
||||
private let scrollView = UIScrollView()
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
return Coordinator(control: self, isDragging: isDragging)
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> UIScrollView {
|
||||
scrollView.delegate = context.coordinator
|
||||
return scrollView
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIScrollView, context: Context) {
|
||||
|
||||
}
|
||||
|
||||
init<Content: View>(axis: Axis = .vertical,
|
||||
isDragging: Binding<Bool> = .constant(false),
|
||||
showsIndicators: Bool = false,
|
||||
contentInset: UIEdgeInsets = .zero,
|
||||
@ViewBuilder content: () -> Content) {
|
||||
|
||||
self.isDragging = isDragging
|
||||
|
||||
let hosting = UIHostingController(rootView: content())
|
||||
hosting.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
hosting.edgesForExtendedLayout = .all
|
||||
hosting.extendedLayoutIncludesOpaqueBars = true
|
||||
|
||||
scrollView.addSubview(hosting.view)
|
||||
scrollView.showsVerticalScrollIndicator = showsIndicators
|
||||
scrollView.showsHorizontalScrollIndicator = showsIndicators
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
scrollView.contentInset = contentInset
|
||||
scrollView.contentOffset = .zero
|
||||
|
||||
let constraints: [NSLayoutConstraint]
|
||||
switch axis {
|
||||
case .horizontal:
|
||||
constraints = [
|
||||
hosting.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
|
||||
hosting.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
|
||||
hosting.view.topAnchor.constraint(equalTo: scrollView.topAnchor),
|
||||
hosting.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
|
||||
hosting.view.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
|
||||
]
|
||||
case .vertical:
|
||||
constraints = [
|
||||
hosting.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
|
||||
hosting.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
|
||||
hosting.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
|
||||
hosting.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
|
||||
hosting.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
|
||||
]
|
||||
}
|
||||
scrollView.addConstraints(constraints)
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, UIScrollViewDelegate {
|
||||
private let isDragging: Binding<Bool>
|
||||
private let control: ExtendedScrollView
|
||||
|
||||
init(control: ExtendedScrollView, isDragging: Binding<Bool>) {
|
||||
self.control = control
|
||||
self.isDragging = isDragging
|
||||
}
|
||||
|
||||
//MARK: - UIScrollView delegate methods
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
isDragging.wrappedValue = true
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
isDragging.wrappedValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// GridStack.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 05.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public struct GridStack<Content: View>: View {
|
||||
let columns: Int
|
||||
let rows: Int
|
||||
let spacing: CGFloat
|
||||
let content: (Int, Int) -> Content
|
||||
|
||||
public init(columns: Int, rows: Int, spacing: CGFloat = 0, @ViewBuilder content: @escaping (Int, Int) -> Content) {
|
||||
self.columns = columns
|
||||
self.rows = rows
|
||||
self.spacing = spacing
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(spacing: self.spacing) {
|
||||
ForEach(0..<self.rows, id: \.self) { row in
|
||||
HStack(spacing: self.spacing) {
|
||||
ForEach(0..<self.columns, id: \.self) { column in
|
||||
self.content(column, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct PageControl: View {
|
||||
|
||||
@Binding private var index: Int
|
||||
private let pagesCount: Int
|
||||
private let diameter: CGFloat
|
||||
private let spacing: CGFloat
|
||||
|
||||
private var width: CGFloat {
|
||||
return CGFloat(pagesCount) * diameter + CGFloat(pagesCount - 1) * spacing
|
||||
}
|
||||
|
||||
private var size: CGSize {
|
||||
return CGSize(width: width, height: diameter)
|
||||
}
|
||||
|
||||
init(diameter: CGFloat = 6,
|
||||
spacing: CGFloat = 10,
|
||||
pagesCount: Int,
|
||||
index: Binding<Int>) {
|
||||
|
||||
self.diameter = diameter
|
||||
self.spacing = spacing
|
||||
self.pagesCount = pagesCount
|
||||
self._index = index
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
HStack(spacing: spacing) {
|
||||
ForEach(0..<pagesCount) { i in
|
||||
Circle().fill(Color.contrast.opacity(self.index == i ? 1 : 0.3))
|
||||
}
|
||||
}
|
||||
.frame(size: size)
|
||||
|
||||
|
||||
Circle()
|
||||
.offset(CGPoint(x: getCenteredXPosition(for: self.index), y: 0))
|
||||
.fill(Color.contrast)
|
||||
.frame(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
private func getCenteredXPosition(for index: Int) -> CGFloat {
|
||||
let position = CGFloat(index) * (diameter + spacing)
|
||||
let halfAlldotsWidthWithSpaces = (CGFloat(pagesCount - 1) * (diameter + spacing) + diameter) / 2.0
|
||||
return position - halfAlldotsWidthWithSpaces + diameter / 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct SwiftUIPagerView<Content: View & Identifiable>: View {
|
||||
|
||||
@State private var index: Int = 0
|
||||
@State private var offset: CGFloat = 0
|
||||
|
||||
private let pages: [Content]
|
||||
private let indexChanged: (Int) -> Void
|
||||
|
||||
init(pages: [Content], indexChanged: @escaping (Int) -> Void) {
|
||||
self.pages = pages
|
||||
self.indexChanged = indexChanged
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
ForEach(self.pages) { page in
|
||||
page
|
||||
.frame(width: geometry.size.width, height: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
.content.offset(x: self.offset)
|
||||
.frame(width: geometry.size.width, height: nil, alignment: .leading)
|
||||
.gesture(DragGesture()
|
||||
.onChanged({ value in
|
||||
self.offset = value.translation.width - geometry.size.width * CGFloat(self.index)
|
||||
})
|
||||
.onEnded({ value in
|
||||
if abs(value.predictedEndTranslation.width) >= geometry.size.width / 2 {
|
||||
var nextIndex: Int = (value.predictedEndTranslation.width < 0) ? 1 : -1
|
||||
nextIndex += self.index
|
||||
self.index = nextIndex.limited(min: 0, max: self.pages.endIndex - 1)
|
||||
self.indexChanged(nextIndex)
|
||||
}
|
||||
withAnimation { self.offset = -geometry.size.width * CGFloat(self.index) }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// RandomIcon.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 22.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct RandomIcon: View {
|
||||
|
||||
var body: some View {
|
||||
let index = Int.random(in: 0..<5)
|
||||
let result: AnyView
|
||||
switch index {
|
||||
case 0: result = circle.frame(size: CGSize(uniform: 30)).asAnyView
|
||||
case 1: result = roundedRectangle.frame(size: CGSize(uniform: 30)).asAnyView
|
||||
case 2: result = capsule.frame(size: CGSize(width: 30, height: 20)).asAnyView
|
||||
case 3: result = elipse.frame(size: CGSize(width: 20, height: 30)).asAnyView
|
||||
default: result = polygon.frame(size: CGSize(uniform: 30)).asAnyView
|
||||
}
|
||||
return result.rotateAroundOnTap
|
||||
}
|
||||
|
||||
private var circle: some View {
|
||||
Circle().fill(Color.random)
|
||||
}
|
||||
|
||||
private var roundedRectangle: some View {
|
||||
RoundedRectangle(cornerRadius: 6).fill(Color.random)
|
||||
}
|
||||
|
||||
private var rectangle: some View {
|
||||
Rectangle().fill(Color.random)
|
||||
}
|
||||
|
||||
private var capsule: some View {
|
||||
Capsule(style: RoundedCornerStyle.circular).fill(Color.random)
|
||||
}
|
||||
|
||||
private var elipse: some View {
|
||||
Ellipse().fill(Color.random)
|
||||
}
|
||||
|
||||
private var polygon: some View {
|
||||
let sidesCount = Int.random(in: 3...8)
|
||||
return PolygonShape(sides: sidesCount).fill(Color.random)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private struct PolygonShape: Shape {
|
||||
var sides: Int
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let h = Double(min(rect.size.width, rect.size.height)) / 2.0
|
||||
let c = CGPoint(x: rect.size.width / 2.0, y: rect.size.height / 2.0)
|
||||
var path = Path()
|
||||
|
||||
for i in 0..<sides {
|
||||
let angle = Double(i) / Double(sides) * 2 * Double.pi
|
||||
let pt = CGPoint(x: c.x + CGFloat(cos(angle) * h), y: c.y + CGFloat(sin(angle) * h))
|
||||
|
||||
if i == 0 {
|
||||
path.move(to: pt)
|
||||
} else {
|
||||
path.addLine(to: pt)
|
||||
}
|
||||
}
|
||||
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
//
|
||||
// Lorem.swift
|
||||
// Example
|
||||
//
|
||||
// Author Lukas Kubanek
|
||||
// https://github.com/lukaskubanek/LoremSwiftum
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A lightweight lorem ipsum generator.
|
||||
public final class Lorem {
|
||||
|
||||
// ======================================================= //
|
||||
// MARK: - Text
|
||||
// ======================================================= //
|
||||
|
||||
/// Generates a single word.
|
||||
public static var word: String {
|
||||
return allWords.randomElement()!
|
||||
}
|
||||
|
||||
/// Generates multiple words whose count is defined by the given value.
|
||||
///
|
||||
/// - Parameter count: The number of words to generate.
|
||||
/// - Returns: The generated words joined by a space character.
|
||||
public static func words(_ count: Int) -> String {
|
||||
return _compose(
|
||||
word,
|
||||
count: count,
|
||||
joinBy: .space
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates multiple words whose count is randomly selected from within the given range.
|
||||
///
|
||||
/// - Parameter range: The range of number of words to generate.
|
||||
/// - Returns: The generated words joined by a space character.
|
||||
public static func words(_ range: Range<Int>) -> String {
|
||||
return _compose(word, count: Int.random(in: range), joinBy: .space)
|
||||
}
|
||||
|
||||
/// Generates multiple words whose count is randomly selected from within the given closed range.
|
||||
///
|
||||
/// - Parameter range: The range of number of words to generate.
|
||||
/// - Returns: The generated words joined by a space character.
|
||||
public static func words(_ range: ClosedRange<Int>) -> String {
|
||||
return _compose(word, count: Int.random(in: range), joinBy: .space)
|
||||
}
|
||||
|
||||
/// Generates a single sentence.
|
||||
public static var sentence: String {
|
||||
let numberOfWords = Int.random(
|
||||
in: minWordsCountInSentence...maxWordsCountInSentence
|
||||
)
|
||||
|
||||
return _compose(
|
||||
word,
|
||||
count: numberOfWords,
|
||||
joinBy: .space,
|
||||
endWith: .dot,
|
||||
decorate: { $0.firstLetterCapitalized }
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates multiple sentences whose count is defined by the given value.
|
||||
///
|
||||
/// - Parameter count: The number of sentences to generate.
|
||||
/// - Returns: The generated sentences joined by a space character.
|
||||
public static func sentences(_ count: Int) -> String {
|
||||
return _compose(
|
||||
sentence,
|
||||
count: count,
|
||||
joinBy: .space
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates multiple sentences whose count is selected from within the given range.
|
||||
///
|
||||
/// - Parameter count: The number of sentences to generate.
|
||||
/// - Returns: The generated sentences joined by a space character.
|
||||
public static func sentences(_ range: Range<Int>) -> String {
|
||||
return _compose(sentence, count: Int.random(in: range), joinBy: .space)
|
||||
}
|
||||
|
||||
/// Generates multiple sentences whose count is selected from within the given closed range.
|
||||
///
|
||||
/// - Parameter count: The number of sentences to generate.
|
||||
/// - Returns: The generated sentences joined by a space character.
|
||||
public static func sentences(_ range: ClosedRange<Int>) -> String {
|
||||
return _compose(sentence, count: Int.random(in: range), joinBy: .space)
|
||||
}
|
||||
|
||||
/// Generates a single paragraph.
|
||||
public static var paragraph: String {
|
||||
let numberOfSentences = Int.random(
|
||||
in: minSentencesCountInParagraph...maxSentencesCountInParagraph
|
||||
)
|
||||
|
||||
return _compose(
|
||||
sentence,
|
||||
count: numberOfSentences,
|
||||
joinBy: .space
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates multiple paragraphs whose count is defined by the given value.
|
||||
///
|
||||
/// - Parameter count: The number of paragraphs to generate.
|
||||
/// - Returns: The generated paragraphs joined by a new line character.
|
||||
public static func paragraphs(_ count: Int) -> String {
|
||||
return _compose(
|
||||
paragraph,
|
||||
count: count,
|
||||
joinBy: .newLine
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates multiple paragraphs whose count is selected from within the given range.
|
||||
///
|
||||
/// - Parameter count: The number of paragraphs to generate.
|
||||
/// - Returns: The generated paragraphs joined by a new line character.
|
||||
public static func paragraphs(_ range: Range<Int>) -> String {
|
||||
return _compose(
|
||||
paragraph,
|
||||
count: Int.random(in: range),
|
||||
joinBy: .newLine
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates multiple paragraphs whose count is selected from within the given closed range.
|
||||
///
|
||||
/// - Parameter count: The number of paragraphs to generate.
|
||||
/// - Returns: The generated paragraphs joined by a new line character.
|
||||
public static func paragraphs(_ range: ClosedRange<Int>) -> String {
|
||||
return _compose(
|
||||
paragraph,
|
||||
count: Int.random(in: range),
|
||||
joinBy: .newLine
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates a capitalized title.
|
||||
public static var title: String {
|
||||
let numberOfWords = Int.random(
|
||||
in: minWordsCountInTitle...maxWordsCountInTitle
|
||||
)
|
||||
|
||||
return _compose(
|
||||
word,
|
||||
count: numberOfWords,
|
||||
joinBy: .space,
|
||||
decorate: { $0.capitalized }
|
||||
)
|
||||
}
|
||||
|
||||
// ======================================================= //
|
||||
// MARK: - Names
|
||||
// ======================================================= //
|
||||
|
||||
/// Generates a first name.
|
||||
public static var firstName: String {
|
||||
return firstNames.randomElement()!
|
||||
}
|
||||
|
||||
/// Generates a last name.
|
||||
public static var lastName: String {
|
||||
return lastNames.randomElement()!
|
||||
}
|
||||
|
||||
/// Generates a full name.
|
||||
public static var fullName: String {
|
||||
return "\(firstName) \(lastName)"
|
||||
}
|
||||
|
||||
// ======================================================= //
|
||||
// MARK: - Email Addresses & URLs
|
||||
// ======================================================= //
|
||||
|
||||
/// Generates an email address.
|
||||
public static var emailAddress: String {
|
||||
let emailDelimiter = emailDelimiters.randomElement()!
|
||||
let emailDomain = emailDomains.randomElement()!
|
||||
|
||||
return "\(firstName)\(emailDelimiter)\(lastName)@\(emailDomain)".lowercased()
|
||||
}
|
||||
|
||||
/// Generates a URL.
|
||||
public static var url: String {
|
||||
let urlScheme = urlSchemes.randomElement()!
|
||||
let urlDomain = urlDomains.randomElement()!
|
||||
return "\(urlScheme)://\(urlDomain)"
|
||||
}
|
||||
|
||||
// ======================================================= //
|
||||
// MARK: - Tweets
|
||||
// ======================================================= //
|
||||
|
||||
/// Generates a random tweet which is shorter than 140 characters.
|
||||
public static var shortTweet: String {
|
||||
return _composeTweet(shortTweetMaxLength)
|
||||
}
|
||||
|
||||
/// Generates a random tweet which is shorter than 280 characters.
|
||||
public static var tweet: String {
|
||||
return _composeTweet(tweetMaxLength)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Lorem {
|
||||
|
||||
fileprivate enum Separator: String {
|
||||
case none = ""
|
||||
case space = " "
|
||||
case dot = "."
|
||||
case newLine = "\n"
|
||||
}
|
||||
|
||||
fileprivate static func _compose(
|
||||
_ provider: @autoclosure () -> String,
|
||||
count: Int,
|
||||
joinBy middleSeparator: Separator,
|
||||
endWith endSeparator: Separator = .none,
|
||||
decorate decorator: ((String) -> String)? = nil
|
||||
) -> String {
|
||||
var string = ""
|
||||
|
||||
for index in 0..<count {
|
||||
string += provider()
|
||||
|
||||
if (index < count - 1) {
|
||||
string += middleSeparator.rawValue
|
||||
} else {
|
||||
string += endSeparator.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
if let decorator = decorator {
|
||||
string = decorator(string)
|
||||
}
|
||||
|
||||
return string
|
||||
}
|
||||
|
||||
fileprivate static func _composeTweet(_ maxLength: Int) -> String {
|
||||
for numberOfSentences in [4, 3, 2, 1] {
|
||||
let tweet = sentences(numberOfSentences)
|
||||
if tweet.count < maxLength {
|
||||
return tweet
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
fileprivate static let minWordsCountInSentence = 4
|
||||
fileprivate static let maxWordsCountInSentence = 16
|
||||
fileprivate static let minSentencesCountInParagraph = 3
|
||||
fileprivate static let maxSentencesCountInParagraph = 9
|
||||
fileprivate static let minWordsCountInTitle = 2
|
||||
fileprivate static let maxWordsCountInTitle = 7
|
||||
fileprivate static let shortTweetMaxLength = 140
|
||||
fileprivate static let tweetMaxLength = 280
|
||||
|
||||
fileprivate static let allWords = ["alias", "consequatur", "aut", "perferendis", "sit", "voluptatem", "accusantium", "doloremque", "aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "et", "quasi", "architecto", "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut", "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni", "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt", "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet", "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam", "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore", "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad", "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam", "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas", "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea", "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure", "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse", "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos", "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam", "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos", "dolores", "et", "quas", "molestias", "excepturi", "sint", "occaecati", "cupiditate", "non", "provident", "sed", "ut", "perspiciatis", "unde", "omnis", "iste", "natus", "error", "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga", "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita", "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis", "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo", "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime", "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda", "est", "omnis", "dolor", "repellendus", "temporibus", "autem", "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui", "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur", "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut", "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et", "voluptates", "repudiandae", "sint", "et", "molestiae", "non", "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a", "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus", "maiores", "doloribus", "asperiores", "repellat"]
|
||||
|
||||
fileprivate static let firstNames = ["Judith", "Angelo", "Margarita", "Kerry", "Elaine", "Lorenzo", "Justice", "Doris", "Raul", "Liliana", "Kerry", "Elise", "Ciaran", "Johnny", "Moses", "Davion", "Penny", "Mohammed", "Harvey", "Sheryl", "Hudson", "Brendan", "Brooklynn", "Denis", "Sadie", "Trisha", "Jacquelyn", "Virgil", "Cindy", "Alexa", "Marianne", "Giselle", "Casey", "Alondra", "Angela", "Katherine", "Skyler", "Kyleigh", "Carly", "Abel", "Adrianna", "Luis", "Dominick", "Eoin", "Noel", "Ciara", "Roberto", "Skylar", "Brock", "Earl", "Dwayne", "Jackie", "Hamish", "Sienna", "Nolan", "Daren", "Jean", "Shirley", "Connor", "Geraldine", "Niall", "Kristi", "Monty", "Yvonne", "Tammie", "Zachariah", "Fatima", "Ruby", "Nadia", "Anahi", "Calum", "Peggy", "Alfredo", "Marybeth", "Bonnie", "Gordon", "Cara", "John", "Staci", "Samuel", "Carmen", "Rylee", "Yehudi", "Colm", "Beth", "Dulce", "Darius", "inley", "Javon", "Jason", "Perla", "Wayne", "Laila", "Kaleigh", "Maggie", "Don", "Quinn", "Collin", "Aniya", "Zoe", "Isabel", "Clint", "Leland", "Esmeralda", "Emma", "Madeline", "Byron", "Courtney", "Vanessa", "Terry", "Antoinette", "George", "Constance", "Preston", "Rolando", "Caleb", "Kenneth", "Lynette", "Carley", "Francesca", "Johnnie", "Jordyn", "Arturo", "Camila", "Skye", "Guy", "Ana", "Kaylin", "Nia", "Colton", "Bart", "Brendon", "Alvin", "Daryl", "Dirk", "Mya", "Pete", "Joann", "Uriel", "Alonzo", "Agnes", "Chris", "Alyson", "Paola", "Dora", "Elias", "Allen", "Jackie", "Eric", "Bonita", "Kelvin", "Emiliano", "Ashton", "Kyra", "Kailey", "Sonja", "Alberto", "Ty", "Summer", "Brayden", "Lori", "Kelly", "Tomas", "Joey", "Billie", "Katie", "Stephanie", "Danielle", "Alexis", "Jamal", "Kieran", "Lucinda", "Eliza", "Allyson", "Melinda", "Alma", "Piper", "Deana", "Harriet", "Bryce", "Eli", "Jadyn", "Rogelio", "Orlaith", "Janet", "Randal", "Toby", "Carla", "Lorie", "Caitlyn", "Annika", "Isabelle", "inn", "Ewan", "Maisie", "Michelle", "Grady", "Ida", "Reid", "Emely", "Tricia", "Beau", "Reese", "Vance", "Dalton", "Lexi", "Rafael", "Makenzie", "Mitzi", "Clinton", "Xena", "Angelina", "Kendrick", "Leslie", "Teddy", "Jerald", "Noelle", "Neil", "Marsha", "Gayle", "Omar", "Abigail", "Alexandra", "Phil", "Andre", "Billy", "Brenden", "Bianca", "Jared", "Gretchen", "Patrick", "Antonio", "Josephine", "Kyla", "Manuel", "Freya", "Kellie", "Tonia", "Jamie", "Sydney", "Andres", "Ruben", "Harrison", "Hector", "Clyde", "Wendell", "Kaden", "Ian", "Tracy", "Cathleen", "Shawn"]
|
||||
|
||||
fileprivate static let lastNames = ["Chung", "Chen", "Melton", "Hill", "Puckett", "Song", "Hamilton", "Bender", "Wagner", "McLaughlin", "McNamara", "Raynor", "Moon", "Woodard", "Desai", "Wallace", "Lawrence", "Griffin", "Dougherty", "Powers", "May", "Steele", "Teague", "Vick", "Gallagher", "Solomon", "Walsh", "Monroe", "Connolly", "Hawkins", "Middleton", "Goldstein", "Watts", "Johnston", "Weeks", "Wilkerson", "Barton", "Walton", "Hall", "Ross", "Chung", "Bender", "Woods", "Mangum", "Joseph", "Rosenthal", "Bowden", "Barton", "Underwood", "Jones", "Baker", "Merritt", "Cross", "Cooper", "Holmes", "Sharpe", "Morgan", "Hoyle", "Allen", "Rich", "Rich", "Grant", "Proctor", "Diaz", "Graham", "Watkins", "Hinton", "Marsh", "Hewitt", "Branch", "Walton", "O'Brien", "Case", "Watts", "Christensen", "Parks", "Hardin", "Lucas", "Eason", "Davidson", "Whitehead", "Rose", "Sparks", "Moore", "Pearson", "Rodgers", "Graves", "Scarborough", "Sutton", "Sinclair", "Bowman", "Olsen", "Love", "McLean", "Christian", "Lamb", "James", "Chandler", "Stout", "Cowan", "Golden", "Bowling", "Beasley", "Clapp", "Abrams", "Tilley", "Morse", "Boykin", "Sumner", "Cassidy", "Davidson", "Heath", "Blanchard", "McAllister", "McKenzie", "Byrne", "Schroeder", "Griffin", "Gross", "Perkins", "Robertson", "Palmer", "Brady", "Rowe", "Zhang", "Hodge", "Li", "Bowling", "Justice", "Glass", "Willis", "Hester", "Floyd", "Graves", "Fischer", "Norman", "Chan", "Hunt", "Byrd", "Lane", "Kaplan", "Heller", "May", "Jennings", "Hanna", "Locklear", "Holloway", "Jones", "Glover", "Vick", "O'Donnell", "Goldman", "McKenna", "Starr", "Stone", "McClure", "Watson", "Monroe", "Abbott", "Singer", "Hall", "Farrell", "Lucas", "Norman", "Atkins", "Monroe", "Robertson", "Sykes", "Reid", "Chandler", "Finch", "Hobbs", "Adkins", "Kinney", "Whitaker", "Alexander", "Conner", "Waters", "Becker", "Rollins", "Love", "Adkins", "Black", "Fox", "Hatcher", "Wu", "Lloyd", "Joyce", "Welch", "Matthews", "Chappell", "MacDonald", "Kane", "Butler", "Pickett", "Bowman", "Barton", "Kennedy", "Branch", "Thornton", "McNeill", "Weinstein", "Middleton", "Moss", "Lucas", "Rich", "Carlton", "Brady", "Schultz", "Nichols", "Harvey", "Stevenson", "Houston", "Dunn", "West", "O'Brien", "Barr", "Snyder", "Cain", "Heath", "Boswell", "Olsen", "Pittman", "Weiner", "Petersen", "Davis", "Coleman", "Terrell", "Norman", "Burch", "Weiner", "Parrott", "Henry", "Gray", "Chang", "McLean", "Eason", "Weeks", "Siegel", "Puckett", "Heath", "Hoyle", "Garrett", "Neal", "Baker", "Goldman", "Shaffer", "Choi", "Carver"]
|
||||
|
||||
fileprivate static let emailDomains = ["gmail.com", "yahoo.com", "hotmail.com", "email.com", "live.com", "me.com", "mac.com", "aol.com", "fastmail.com", "mail.com"]
|
||||
|
||||
fileprivate static let emailDelimiters = ["", ".", "-", "_"]
|
||||
|
||||
fileprivate static let urlSchemes = ["http", "https"]
|
||||
|
||||
fileprivate static let urlDomains = ["twitter.com", "google.com", "youtube.com", "wordpress.org", "adobe.com", "blogspot.com", "godaddy.com", "wikipedia.org", "wordpress.com", "yahoo.com", "linkedin.com", "amazon.com", "flickr.com", "w3.org", "apple.com", "myspace.com", "tumblr.com", "digg.com", "microsoft.com", "vimeo.com", "pinterest.com", "stumbleupon.com", "youtu.be", "miibeian.gov.cn", "baidu.com", "feedburner.com", "bit.ly"]
|
||||
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
fileprivate var firstLetterCapitalized: String {
|
||||
guard !isEmpty else { return self }
|
||||
return prefix(1).capitalized + dropFirst()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Screen.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 22.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct Screen<Content>: View where Content: View {
|
||||
let content: () -> Content
|
||||
let backgoundColor: Color
|
||||
|
||||
init(color: Color = .white, @ViewBuilder content: @escaping () -> Content) {
|
||||
self.content = content
|
||||
self.backgoundColor = color
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
backgoundColor.edgesIgnoringSafeArea(.all)
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct TouchGestureViewModifier: ViewModifier {
|
||||
let touchBegan: () -> Void
|
||||
let touchEnd: (_ success: Bool) -> Void
|
||||
|
||||
@State private var hasBegun = false
|
||||
@State private var hasEnded = false
|
||||
|
||||
private func isTooFar(_ translation: CGSize) -> Bool {
|
||||
let distance = sqrt(pow(translation.width, 2) + pow(translation.height, 2))
|
||||
return distance >= 20.0
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content.gesture(DragGesture(minimumDistance: 0)
|
||||
.onChanged { event in
|
||||
guard !self.hasEnded else { return }
|
||||
|
||||
if self.hasBegun == false {
|
||||
self.hasBegun = true
|
||||
self.touchBegan()
|
||||
} else if self.isTooFar(event.translation) {
|
||||
self.hasEnded = true
|
||||
self.touchEnd(false)
|
||||
}
|
||||
}
|
||||
.onEnded { event in
|
||||
if !self.hasEnded {
|
||||
let success = !self.isTooFar(event.translation)
|
||||
self.touchEnd(success)
|
||||
}
|
||||
self.hasBegun = false
|
||||
self.hasEnded = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func onTouchGesture(touchBegan: @escaping () -> Void,
|
||||
touchEnd: @escaping (_ success: Bool) -> Void) -> some View {
|
||||
modifier(TouchGestureViewModifier(touchBegan: touchBegan, touchEnd: touchEnd))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// Utilities.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Igor K. on 05.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
static var random: Color {
|
||||
return Color(
|
||||
red: .random(in: 0...1),
|
||||
green: .random(in: 0...1),
|
||||
blue: .random(in: 0...1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIWindow {
|
||||
|
||||
static var safeInsets: UIEdgeInsets {
|
||||
return current?.safeAreaInsets ?? UIEdgeInsets(top: 44, left: 0, bottom: 34, right: 0)
|
||||
}
|
||||
|
||||
static var isFullScreen: Bool {
|
||||
return current?.frame == UIScreen.main.bounds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension View {
|
||||
public func greedyFrame(alignment: Alignment) -> some View {
|
||||
return self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Binding where Value: BindingAnimatable {
|
||||
var animatable: Binding<Value> {
|
||||
return Binding<Value>(get: { return self.wrappedValue },
|
||||
set: { b in withAnimation { self.wrappedValue = b } })
|
||||
}
|
||||
}
|
||||
|
||||
protocol BindingAnimatable { }
|
||||
|
||||
extension Bool: BindingAnimatable { }
|
||||
extension Int: BindingAnimatable { }
|
||||
|
||||
|
||||
extension View {
|
||||
var rotateAroundOnTap: some View {
|
||||
self.modifier(RotationAroundModifier())
|
||||
}
|
||||
}
|
||||
|
||||
struct RotationAroundModifier: ViewModifier {
|
||||
@State private var animationAngle = 0.0
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
return content
|
||||
.onTapGesture { withAnimation { self.animationAngle += 360 } }
|
||||
.rotation3DEffect(.degrees(animationAngle), axis: (x: 0, y: 1, z: 0))
|
||||
.animation(.interpolatingSpring(stiffness: 150, damping: 17))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension View {
|
||||
var scaleOnTap: some View {
|
||||
self.modifier(TapScaleModifier())
|
||||
}
|
||||
}
|
||||
|
||||
struct TapScaleModifier: ViewModifier {
|
||||
|
||||
@State private var scaleValue: CGFloat = 1
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.scaleEffect(self.scaleValue)
|
||||
.onTouchGesture(
|
||||
touchBegan: { withAnimation { self.scaleValue = 1.05 } },
|
||||
touchEnd: { _ in withAnimation { self.scaleValue = 1.0 } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIEdgeInsets {
|
||||
public init(uniform value: CGFloat) {
|
||||
self.init(top: value, left: value, bottom: value, right: value)
|
||||
}
|
||||
|
||||
public init(horizontal h: CGFloat, vertical v: CGFloat) {
|
||||
self.init(top: v, left: h, bottom: v, right: h)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// BlobMenuConfiguration.swift
|
||||
// BlobMenu
|
||||
//
|
||||
// Created by Igor K. on 23.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public struct BlobMenuConfiguration {
|
||||
|
||||
public let hamburgerColor: Color
|
||||
public let backgroundColor: Color
|
||||
public let selectionColor: Color
|
||||
|
||||
public init(hamburgerColor: Color,
|
||||
backgroundColor: Color,
|
||||
selectionColor: Color) {
|
||||
|
||||
self.hamburgerColor = hamburgerColor
|
||||
self.backgroundColor = backgroundColor
|
||||
self.selectionColor = selectionColor
|
||||
}
|
||||
|
||||
public static var `default`: BlobMenuConfiguration {
|
||||
BlobMenuConfiguration(hamburgerColor: Color.hamburgerColor,
|
||||
backgroundColor: Color.backgroundColor,
|
||||
selectionColor: Color.selectionColor)
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,5 @@ extension Color {
|
||||
return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.9705940673, green: 0.9705940673, blue: 0.9705940673, alpha: 1) : #colorLiteral(red: 0.1960526407, green: 0.1960932612, blue: 0.1960500479, alpha: 1) })
|
||||
}
|
||||
|
||||
static let selection = Color(#colorLiteral(red: 0.9983773828, green: 0.7375702262, blue: 0.1739521325, alpha: 1))
|
||||
static let selectionColor = Color(#colorLiteral(red: 0.9983773828, green: 0.7375702262, blue: 0.1739521325, alpha: 1))
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension AnyTransition {
|
||||
static var blobMenuItem: AnyTransition {
|
||||
static var blobBlobMenuItem: AnyTransition {
|
||||
return AnyTransition.scale.combined(with: AnyTransition.rotation)
|
||||
.animation(.easeOut(duration: 0.35))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// CGRect+Extensions.swift
|
||||
// BlobMenu
|
||||
//
|
||||
// Created by Igor K. on 20.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension CGRect {
|
||||
|
||||
public init(size: CGSize) {
|
||||
self.init(origin: .zero, size: size)
|
||||
}
|
||||
|
||||
public var center: CGPoint {
|
||||
return CGPoint(x: midX, y: midY)
|
||||
}
|
||||
|
||||
public init(center: CGPoint, size: CGSize) {
|
||||
self.init(x: center.x - size.width / 2, y: center.y - size.height / 2, width: size.width, height: size.height)
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension UIWindow {
|
||||
public extension UIWindow {
|
||||
|
||||
static var current: UIWindow? {
|
||||
//if scene is not connected will use first normal level key window
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// BlobMenuEnvironment.swift
|
||||
// BlobMenu
|
||||
//
|
||||
// Created by Igor K. on 23.04.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public extension EnvironmentValues {
|
||||
var blobMenuEnvironment: BlobMenuEnvironment {
|
||||
get { return self[BlobMenuEnvironmentKey.self] }
|
||||
set { self[BlobMenuEnvironmentKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
struct BlobMenuEnvironmentKey: EnvironmentKey {
|
||||
static let defaultValue: BlobMenuEnvironment = BlobMenuEnvironment()
|
||||
}
|
||||
|
||||
public final class BlobMenuEnvironment: ObservableObject {
|
||||
@Published public internal(set) var isOpened: Bool = false
|
||||
@Published public internal(set) var isMenuItemsVisible: Bool = false
|
||||
@Published public internal(set) var selectedIndex: Int = 0
|
||||
|
||||
fileprivate init() {}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// MenuItem.swift
|
||||
// BlobMenuItem.swift
|
||||
// BlobMenu
|
||||
//
|
||||
// Created by Igor K. on 29.04.2020.
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public struct MenuItem: Identifiable, Hashable {
|
||||
public struct BlobMenuItem: Identifiable, Hashable {
|
||||
|
||||
public let id = UUID()
|
||||
public let selectedIcon: Image
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// BlobMenuMode.swift
|
||||
// BlobMenu
|
||||
//
|
||||
// Created by Igor K. on 22.05.2020.
|
||||
// Copyright © 2020 Ramotion. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public final class BlobMenuModel: ObservableObject {
|
||||
@Published public var items: [BlobMenuItem]
|
||||
@Published public var isOpened: Bool
|
||||
@Published public internal(set) var selectedIndex: Int
|
||||
@Published public internal(set) var isBlobMenuItemsVisible: Bool = false
|
||||
|
||||
public init(items: [BlobMenuItem],
|
||||
selectedIndex: Int = 0,
|
||||
isOpened: Bool = false) {
|
||||
|
||||
self.items = items
|
||||
self.isOpened = isOpened
|
||||
self.selectedIndex = selectedIndex.limited(0, items.count - 1)
|
||||
}
|
||||
|
||||
public func selectIndex(_ index: Int) {
|
||||
let limitedIndex = selectedIndex.limited(0, items.count - 1)
|
||||
selectedIndex = limitedIndex
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,10 @@ extension View {
|
||||
public func offset(_ offset: CGPoint) -> some View {
|
||||
return self.offset(x: offset.x, y: offset.y)
|
||||
}
|
||||
|
||||
public var asAnyView: AnyView {
|
||||
return AnyView(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,10 +28,12 @@ struct BackgroundPreferenceKey: PreferenceKey {
|
||||
|
||||
struct BackgroundView: View {
|
||||
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
return Rectangle()
|
||||
.fill(Color.backgroundColor)
|
||||
.shadow(color: Color.backgroundColor.opacity(0.45), radius: 8, x: 0, y: 4)
|
||||
.fill(color)
|
||||
.shadow(color: color.opacity(0.45), radius: 8, x: 0, y: 4)
|
||||
.anchorPreference(key: BackgroundPreferenceKey.self, value: .bounds, transform: { [BackgroundPreferenceData(bounds: $0)] })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,26 +10,18 @@ import SwiftUI
|
||||
|
||||
public struct BlobMenuView: View {
|
||||
|
||||
@State public var selectedIndex: Int
|
||||
private let configuration: BlobMenuConfiguration
|
||||
@ObservedObject private var viewModel: BlobMenuModel
|
||||
|
||||
@EnvironmentObject private var environment: BlobMenuEnvironment
|
||||
private let items: [MenuItem]
|
||||
|
||||
public static func createMenu(items: [MenuItem], selectedIndex: Int = 0) -> some View {
|
||||
return BlobMenuView(items: items, selectedIndex: selectedIndex).environmentObject(BlobMenuEnvironmentKey.defaultValue)
|
||||
}
|
||||
|
||||
private init(items: [MenuItem], selectedIndex: Int = 0) {
|
||||
self.items = items
|
||||
public init(model: BlobMenuModel,
|
||||
configuration: BlobMenuConfiguration = .default) {
|
||||
|
||||
let limitedIndex = selectedIndex.limited(0, items.count - 1)
|
||||
_selectedIndex = State<Int>.init(initialValue: limitedIndex)
|
||||
self.viewModel = model
|
||||
self.configuration = configuration
|
||||
|
||||
BlobMenuEnvironmentKey.defaultValue.selectedIndex = limitedIndex
|
||||
|
||||
UIWindow.current?.addGesture(type: .tap) { _ in
|
||||
BlobMenuEnvironmentKey.defaultValue.isMenuItemsVisible = false
|
||||
BlobMenuEnvironmentKey.defaultValue.isOpened = false
|
||||
UIWindow.current?.addGesture(type: .tap) {[weak model] _ in
|
||||
model?.isBlobMenuItemsVisible = false
|
||||
model?.isOpened = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +30,7 @@ public struct BlobMenuView: View {
|
||||
HStack {
|
||||
Spacer()
|
||||
self.background.overlay(self.itemsView)
|
||||
Spacer().size(width: self.environment.isOpened ? nil : Theme.padding)
|
||||
Spacer().size(width: self.viewModel.isOpened ? nil : Theme.padding)
|
||||
}
|
||||
.backgroundPreferenceValue(BackgroundPreferenceKey.self) { p in
|
||||
GeometryReader { geometry in
|
||||
@@ -48,7 +40,7 @@ public struct BlobMenuView: View {
|
||||
}
|
||||
HStack {
|
||||
Spacer()
|
||||
HamburgerView(isOpened: environment.isOpened)
|
||||
HamburgerView(isOpened: viewModel.isOpened, color: configuration.hamburgerColor)
|
||||
Spacer().size(width: Theme.padding)
|
||||
}
|
||||
}
|
||||
@@ -57,7 +49,7 @@ public struct BlobMenuView: View {
|
||||
|
||||
private func createStickyView(geometry: GeometryProxy, preferences: [BackgroundPreferenceData]) -> some View {
|
||||
|
||||
guard !self.environment.isMenuItemsVisible else {
|
||||
guard !self.viewModel.isBlobMenuItemsVisible else {
|
||||
return AnyView(Color.clear)
|
||||
}
|
||||
|
||||
@@ -71,29 +63,31 @@ public struct BlobMenuView: View {
|
||||
|
||||
let effectView = StickyEffectShape(baseRect: base, figureRect: b, figureCornerRadius: r, avulsionDistance: Theme.stickyEffectAvulsionDistance)
|
||||
|
||||
return AnyView(effectView.fill(Color.backgroundColor)
|
||||
.frame(size: CGSize(width: w, height: f.height)))
|
||||
return effectView
|
||||
.fill(configuration.backgroundColor)
|
||||
.frame(size: CGSize(width: w, height: f.height))
|
||||
.asAnyView
|
||||
}
|
||||
|
||||
private var background: some View {
|
||||
BackgroundView()
|
||||
BackgroundView(color: configuration.backgroundColor)
|
||||
.cornerRadius(Theme.closedSize.height / 2)
|
||||
.keyframes(size: Theme.backgroundSizeKeyframes(isOpened: environment.isOpened, items: items), progress: environment.isOpened ? 1 : 0)
|
||||
.onAnimationCompleted(condition: environment.isOpened) {
|
||||
self.environment.isMenuItemsVisible = true
|
||||
.keyframes(size: Theme.backgroundSizeKeyframes(isOpened: viewModel.isOpened, items: viewModel.items), progress: viewModel.isOpened ? 1 : 0)
|
||||
.animation(Animation.interpolatingSpring(mass: 1, stiffness: 170, damping: 15, initialVelocity: 1).delay(viewModel.isOpened ? 0.12 : 0))
|
||||
.onAnimationCompleted(condition: viewModel.isOpened) {
|
||||
self.viewModel.isBlobMenuItemsVisible = true
|
||||
}
|
||||
.animation(Animation.interpolatingSpring(mass: 1, stiffness: 170, damping: 15, initialVelocity: 1).delay(environment.isOpened ? 0.12 : 0))
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
self.environment.isOpened = true
|
||||
self.viewModel.isOpened = true
|
||||
}
|
||||
}
|
||||
.allowsHitTesting(!self.environment.isOpened)
|
||||
.allowsHitTesting(!self.viewModel.isOpened)
|
||||
}
|
||||
|
||||
private var itemsView: some View {
|
||||
Group {
|
||||
if Theme.isScrollable(items: items) {
|
||||
if Theme.isScrollable(items: viewModel.items) {
|
||||
ScrollView(.horizontal, showsIndicators: false) { itemsContent }
|
||||
} else {
|
||||
itemsContent
|
||||
@@ -101,16 +95,18 @@ public struct BlobMenuView: View {
|
||||
}
|
||||
.clipShape(Capsule(style: .circular))
|
||||
.animation(Animation.easeInOut(duration: 0.35).delay(0.15))
|
||||
.allowsHitTesting(self.environment.isMenuItemsVisible)
|
||||
.allowsHitTesting(self.viewModel.isBlobMenuItemsVisible)
|
||||
}
|
||||
|
||||
private var itemsContent: some View {
|
||||
HStack(spacing: 20) {
|
||||
ForEach(items.enumeratedArray(), id: \.element) { index, item in
|
||||
MenuItemView(item: item, isSelected: self.selectedIndex == index, isOpened: self.$environment.isMenuItemsVisible).onTapGesture {
|
||||
|
||||
self.selectedIndex = index
|
||||
BlobMenuEnvironmentKey.defaultValue.selectedIndex = index
|
||||
ForEach(viewModel.items.enumeratedArray(), id: \.element) { index, item in
|
||||
BlobMenuItemView(item: item,
|
||||
isSelected: self.viewModel.selectedIndex == index,
|
||||
isOpened: self.viewModel.isBlobMenuItemsVisible,
|
||||
selectionColor: self.configuration.selectionColor)
|
||||
.onTapGesture {
|
||||
self.viewModel.selectedIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,18 +119,18 @@ extension BlobMenuView {
|
||||
enum Theme {
|
||||
static let height: CGFloat = 60
|
||||
static let padding: CGFloat = 10
|
||||
static let menuItemsSpace: CGFloat = 20
|
||||
static let BlobMenuItemsSpace: CGFloat = 20
|
||||
static let stickyEffectAvulsionDistance: CGFloat = 120
|
||||
static let closedSize = CGSize(width: 60, height: height)
|
||||
static let collapsedSize = CGSize(width: 90, height: 70)
|
||||
static let maxOpenSize = CGSize(width: UIScreen.main.bounds.width - 60, height: height)
|
||||
|
||||
private static func itemsSize(items: [MenuItem]) -> CGSize {
|
||||
let w = CGFloat(items.count) * MenuItemView.Theme.size.width + CGFloat(items.count - 1) * menuItemsSpace
|
||||
private static func itemsSize(items: [BlobMenuItem]) -> CGSize {
|
||||
let w = CGFloat(items.count) * BlobMenuItemView.Theme.size.width + CGFloat(items.count - 1) * BlobMenuItemsSpace
|
||||
return CGSize(width: w, height: height)
|
||||
}
|
||||
|
||||
static func openedSize(items: [MenuItem]) -> CGSize {
|
||||
static func openedSize(items: [BlobMenuItem]) -> CGSize {
|
||||
if isScrollable(items: items) {
|
||||
return maxOpenSize
|
||||
} else {
|
||||
@@ -142,11 +138,11 @@ extension BlobMenuView {
|
||||
}
|
||||
}
|
||||
|
||||
static func isScrollable(items: [MenuItem]) -> Bool {
|
||||
static func isScrollable(items: [BlobMenuItem]) -> Bool {
|
||||
return itemsSize(items: items).width > maxOpenSize.width
|
||||
}
|
||||
|
||||
static func backgroundSizeKeyframes(isOpened: Bool, items: [MenuItem]) -> [Keyframe<CGSize>] {
|
||||
static func backgroundSizeKeyframes(isOpened: Bool, items: [BlobMenuItem]) -> [Keyframe<CGSize>] {
|
||||
if isOpened {
|
||||
//will use during opening animation
|
||||
return [
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct HamburgerView: View {
|
||||
|
||||
|
||||
public let isOpened: Bool
|
||||
public let color: Color
|
||||
|
||||
private var rotationAngle: Angle {
|
||||
return Angle(degrees: isOpened ? -90 : 0)
|
||||
@@ -31,7 +32,7 @@ public struct HamburgerView: View {
|
||||
private var line: some View {
|
||||
Rectangle()
|
||||
.frame(width: Theme.lineWidth, height: Theme.lineThickness)
|
||||
.foregroundColor(Color.hamburgerColor)
|
||||
.foregroundColor(color)
|
||||
.cornerRadius(Theme.lineCornerRadius)
|
||||
}
|
||||
}
|
||||
@@ -40,7 +41,7 @@ public struct HamburgerView: View {
|
||||
//MARK: - Preview
|
||||
struct HamburgerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
return HamburgerView(isOpened: false)
|
||||
return HamburgerView(isOpened: false, color: .hamburgerColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// MenuItemView.swift
|
||||
// BlobMenuItemView.swift
|
||||
// BlobMenu
|
||||
//
|
||||
// Created by Igor K. on 29.04.2020.
|
||||
@@ -9,11 +9,12 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public struct MenuItemView: View {
|
||||
public struct BlobMenuItemView: View {
|
||||
|
||||
public let item: MenuItem
|
||||
public let isSelected: Bool
|
||||
@Binding public var isOpened: Bool
|
||||
let item: BlobMenuItem
|
||||
let isSelected: Bool
|
||||
let isOpened: Bool
|
||||
let selectionColor: Color
|
||||
|
||||
public var body: some View {
|
||||
ZStack {
|
||||
@@ -30,12 +31,12 @@ public struct MenuItemView: View {
|
||||
let image = isSelected ? item.selectedIcon : item.unselectedIcon
|
||||
return image
|
||||
.offset(item.offset)
|
||||
.transition(AnyTransition.blobMenuItem)
|
||||
.transition(AnyTransition.blobBlobMenuItem)
|
||||
}
|
||||
|
||||
private var selectionView: some View {
|
||||
Circle()
|
||||
.foregroundColor(Color.selection)
|
||||
.foregroundColor(selectionColor)
|
||||
.frame(size: Theme.contentSize)
|
||||
.opacity(isSelected ? 1 : 0)
|
||||
.animation(nil)
|
||||
@@ -46,7 +47,7 @@ public struct MenuItemView: View {
|
||||
private var ringView: some View {
|
||||
let show = isOpened && isSelected
|
||||
return Circle()
|
||||
.stroke(Color.selection)
|
||||
.stroke(selectionColor)
|
||||
.frame(size: Theme.contentSize)
|
||||
.opacity(show ? 0 : 1)
|
||||
.animation(show ? Animation.easeInOut.delay(0.2) : nil)
|
||||
@@ -57,7 +58,7 @@ public struct MenuItemView: View {
|
||||
|
||||
|
||||
//MARK: - Theme
|
||||
extension MenuItemView {
|
||||
extension BlobMenuItemView {
|
||||
enum Theme {
|
||||
static let size = CGSize(uniform: 60)
|
||||
static let contentInsets: CGFloat = 15
|
||||
|
||||
@@ -31,9 +31,9 @@ struct StickyEffectShape: Shape {
|
||||
func path(in rect: CGRect) -> Path {
|
||||
|
||||
let path = pathGenerator.generatePath(baseRect: baseRect,
|
||||
figureRect: figureRect,
|
||||
figureCornerRadius: figureCornerRadius,
|
||||
avulsionDistance: avulsionDistance)
|
||||
figureRect: figureRect,
|
||||
figureCornerRadius: figureCornerRadius,
|
||||
avulsionDistance: avulsionDistance)
|
||||
|
||||
return Path(path)
|
||||
}
|
||||
|
||||
@@ -346,7 +346,16 @@ public final class StickyPathGenerator {
|
||||
let curvePath = UIBezierPath()
|
||||
curvePath.move(to: point0)
|
||||
curvePath.addCurve(to: point1, controlPoint1: cpUp1, controlPoint2: cpUp2)
|
||||
curvePath.addLine(to: point2)
|
||||
if crossed {
|
||||
curvePath.addLine(to: point2)
|
||||
} else {
|
||||
let center = input.figureRect.center
|
||||
let dc = CGPoint(x: center.x - 0.5, y: center.y) // to fix small gap between menu and arc
|
||||
let start = atan((point1.y - center.y) / (point1.x - center.x))
|
||||
let end = atan((point2.y - center.y) / (point2.x - center.x))
|
||||
curvePath.addArc(withCenter: dc, radius: input.figureCornerRadius, startAngle: start, endAngle: end, clockwise: true)
|
||||
curvePath.addLine(to: point2)
|
||||
}
|
||||
curvePath.addCurve(to: point3, controlPoint1: cpDown2, controlPoint2: cpDown1)
|
||||
|
||||
return curvePath
|
||||
|
||||
Reference in New Issue
Block a user