17 Commits

Author SHA1 Message Date
Alex K 96abfee256 converted to swift 5 2019-04-15 11:28:19 +03:00
i.kolpachkov 86a06411d7 [Toggler control]: small improvements 2018-12-10 01:38:34 +03:00
i.kolpachkov 8031f5a28f add collection layouts, string & color extensions 2018-11-26 23:26:16 +03:00
i.kolpachkov fdd2ed5b7c small improvement 2018-11-23 17:25:56 +03:00
i.kolpachkov 5cabfab574 UIColor extensions 2018-11-23 02:16:09 +03:00
i.kolpachkov 535b76fea0 Merge branch 'master' of https://github.com/Ramotion/utopia 2018-11-21 22:27:13 +03:00
i.kolpachkov 5b0672ee60 Core Graphics extensions 2018-11-21 22:26:59 +03:00
i.kolpachkov 68eee74e76 Merge branch 'master' of https://github.com/Ramotion/utopia 2018-11-20 13:32:40 +03:00
i.kolpachkov 5a4a0f30c4 pinned header collection flow layout & podspec updates 2018-11-20 13:32:20 +03:00
i.kolpachkov cc2a7348fd primary action triggered event handler 2018-11-14 01:51:27 +03:00
i.kolpachkov d89555b9ea small fix 2018-11-14 01:15:26 +03:00
i.kolpachkov dc98e270f0 [UIControl extensions]: add primary action signal 2018-11-14 01:12:50 +03:00
i.kolpachkov e18a72804d Search utilities improvements 2018-11-11 02:40:03 +03:00
i.kolpachkov aa9c4e22a6 add new devices 2018-11-06 14:02:54 +03:00
i.kolpachkov 1b7f547eda swift 4.2, structure improvements, horizontal alignment collection, touch interaction, etc. 2018-09-20 03:00:01 +03:00
i.kolpachkov dac53d1ac5 sources cleanup 2018-08-20 00:27:30 +03:00
Juri Vasylenko 5af96519f7 Merge pull request #3 from Ramotion/develop
Sources added
2018-08-17 12:25:29 +03:00
85 changed files with 2631 additions and 5003 deletions
+1
View File
@@ -0,0 +1 @@
4.2
+91 -215
View File
@@ -7,9 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
016CC3A1C1D7DDF9EDC5C1162D649B11 /* CollectionSupplement.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC9BCB36774C899E2CFF014515E184DF /* CollectionSupplement.swift */; };
02807C6CD1D68F6A8F1D5F5EDBC075F3 /* UIGestureRecognizer+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22E8F6CDD346ED32ECF2CF152C7F68F /* UIGestureRecognizer+ext.swift */; };
0469A2397AB66C46BE90652653352AB2 /* UICollectionView+DeepDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = D404C95A8030180967F3BA99BECFE580 /* UICollectionView+DeepDiff.swift */; };
09471D36F0C65BF373FE54C944C68820 /* StringValidators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73E2BA02088FD493C09D02E47ABE4388 /* StringValidators.swift */; };
0BE22BEB5AE38CEC94E0A184C8946969 /* UICollectionView+Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979016747919997EAF2EC27C458E3C36 /* UICollectionView+Reusable.swift */; };
0E0F05E5808AB2414171FE216A4511C6 /* UIView+transitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79E5A847C4EE299FFCC34B5C4BB4354 /* UIView+transitions.swift */; };
@@ -23,22 +21,30 @@
26DE0E6B75AB5050F5772B922A2AD509 /* UIButton+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FAAEBDF042428F0A388D948B4DF4879 /* UIButton+ext.swift */; };
286B06BEE5CF5C9680D6C4E3F7C671D5 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7D2225A392E110910C049631DFD21A8 /* Reachability.swift */; };
29396B3ADC9D79BC235C37FC87898C0E /* Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B8F1CF04C636E2B5F6107FCFE6A533 /* Snapshot.swift */; };
2ADB6BCE9B619A9A4B87388877977E37 /* Heckel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6EB35A67E9198A6995778F2F1A9FBFD /* Heckel.swift */; };
2B4E363F17E50EDA2C69E578CAAE43BF /* utopia-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = ECFF9B3761CE5BD778DBD8E0477703BF /* utopia-dummy.m */; };
2F281C3DB35F09EF83172BA7D2A2955E /* UIBarButtonItem+Signals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B1B08AF40BCF36FFC3BF6C3ACEC9B8 /* UIBarButtonItem+Signals.swift */; };
2F3FE4ACEF1BBA40DB8FAC3E25915658 /* Toggler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C127A0077D1A43E0B1AF57D5C3E0EC4 /* Toggler.swift */; };
30522776ACB49F80FB650434A08771A2 /* KeyboardObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D2E2CE51EEFE3790C4B1336397E3530 /* KeyboardObserver.swift */; };
32B0440AA07DD473ABD2D61DF2F3FCD8 /* Literals.swift in Sources */ = {isa = PBXBuildFile; fileRef = A243A5C208AE35F0F0C33004AA1B6620 /* Literals.swift */; };
34121FA0A44E5638EF672F112FA7EBCD /* WagnerFischer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90CCD71777EAB46247B48FA67E0B3132 /* WagnerFischer.swift */; };
34A0B3185049A34C0C8753721B994638 /* UITextField+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845CCECCD4F6822F1ED13F76CC8FE82 /* UITextField+ext.swift */; };
35937A6BCDC0AD740B2768CF56F18A0A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6604A7D69453B4569E4E4827FB9155A9 /* Foundation.framework */; };
35BB5E12A394D82FF0F8E675F3000FDB /* Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CADBB7F40CC7CCB173E2090A583C51 /* Math.swift */; };
35F36F6F8769CDC19189C6C44BECE03E /* SignalSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFAA0B21453AB7FAEF276D19B10890A /* SignalSubscription.swift */; };
391DB7CE215316AB0083B8C3 /* HorizontalAlignmentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391DB7CD215316AB0083B8C3 /* HorizontalAlignmentLayout.swift */; };
391DB7D0215316F50083B8C3 /* InteractiveDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391DB7CF215316F50083B8C3 /* InteractiveDismiss.swift */; };
391DB7D4215317990083B8C3 /* GestureRecognizerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391DB7D3215317990083B8C3 /* GestureRecognizerDelegate.swift */; };
391DB7D6215317A20083B8C3 /* TouchInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391DB7D5215317A20083B8C3 /* TouchInteraction.swift */; };
3948F8B92193A3000043FD2E /* PinnedHeaderFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948F8B82193A3000043FD2E /* PinnedHeaderFlowLayout.swift */; };
398EF3F919D8AFA5093B15156F27E353 /* AssociatedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90027142D498AD8ABA5215ADC06AC80 /* AssociatedObject.swift */; };
399D418F21AC8D1C00A0B1EA /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D418D21AC8D1200A0B1EA /* UIColor+Extensions.swift */; };
399D419421AC8DCF00A0B1EA /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419021AC8DC300A0B1EA /* NSAttributedString+Extensions.swift */; };
399D419521AC8DCF00A0B1EA /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419121AC8DC300A0B1EA /* String+Extensions.swift */; };
399D419921AC8E0D00A0B1EA /* VerticalAlignmentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419621AC8E0C00A0B1EA /* VerticalAlignmentLayout.swift */; };
399D419A21AC8E0D00A0B1EA /* PaginationFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419721AC8E0D00A0B1EA /* PaginationFlowLayout.swift */; };
399D419B21AC8E0D00A0B1EA /* SnappingFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D419821AC8E0D00A0B1EA /* SnappingFlowLayout.swift */; };
3C087E90B6D5631B925B524FBD183DA5 /* Associated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43A88C1BE5A50F7B80FB3A7EF1DD75D5 /* Associated.swift */; };
3C593180EE447D73A5ADBA3AE8909897 /* InputVisibilityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C3D37E1C6C9537466FBA3F006E1A2D /* InputVisibilityController.swift */; };
40077E1D3AAA704AC97E1AEDA22CF464 /* StatusBarAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4EEBAEB1E1FDB6EDA95CFF0B7ECFA7 /* StatusBarAppearance.swift */; };
46F572A824B37693AF514E85E0549C58 /* DictionaryErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDFCC54FF8EC7D7F64E9001D66CD11 /* DictionaryErrors.swift */; };
4837A82C9004D6D819B45977200EEF92 /* Haptic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DAD12A7E3A6444E9DD66F5B3EF62046 /* Haptic.swift */; };
4F58D683476B2C93625C121FAC487769 /* CoreAnimation+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2AB7054EF292E6C6EDA81B3B7AC3586 /* CoreAnimation+ext.swift */; };
50202062FF2FEFA5A435C025B28E6F2E /* LayoutGuides.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90010E69BA8984268435FC0BC4C19F /* LayoutGuides.swift */; };
@@ -47,48 +53,31 @@
5533FA61D479D4E52F22B6D7E5634127 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = C78E8030A43AACA23F8A7A60874912D6 /* Signal.swift */; };
556DBB6B22CB0D57352504204588353C /* SwiftyImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496E51ADD4779D6AC8AEB0E4AC214241 /* SwiftyImageView.swift */; };
56AFDBABD2F98A47878F3C6D24D2C18D /* UISearchUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A7A173B2BB4912B56024A0AB99C624 /* UISearchUtilities.swift */; };
57436A3B37C2DDBE9953C104FB706A81 /* DictionaryEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3F60E978A7284B439BE2FCDE3600871 /* DictionaryEncoder.swift */; };
5788D3A4E388EEC282525FA728EFFD4B /* CollectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7821B811634043201CB58B8B98018ED5 /* CollectionItem.swift */; };
58D9DE8371AC03A18432CDD1DC4C1FDB /* Pods-utopia_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC125537CD9723ABCD3B1A86A59DF34 /* Pods-utopia_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B190B9D09C4A639DA32D00681E1B4A7 /* Pods-utopia_Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B9483E800344D13D356F1D83D85F3E8D /* Pods-utopia_Example-dummy.m */; };
5F10FDD4882A344EBC6F15A2A7824F21 /* UITableView+DeepDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE0C9BA6C35A8CBF0101B1C408D1CC8 /* UITableView+DeepDiff.swift */; };
6043ADCB4A6A8BF4E704C73292A5F4FE /* CollectionItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8ADED2A9CC7AC64300CA0741531748 /* CollectionItemProtocol.swift */; };
696FBE84EEB2A8F325E8D551903BF524 /* UIControl+Signals.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B061582844CAED75F90A2B4DF96BBF /* UIControl+Signals.swift */; };
728E7F3E40B77C6AE1F2EA03435547F9 /* CodableUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9BB03A7F405472718898E3CF28F4BF /* CodableUtilities.swift */; };
737F4D68613986033F0FE3870FFD20D3 /* DictionaryCodingKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0737DBB484F81C7B892CB23EBFFDEF86 /* DictionaryCodingKey.swift */; };
73B0A6F48F64287FDB4E768D7647E352 /* UINavigationController+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE531C30779681DEBE1F6FF256BD3F89 /* UINavigationController+ext.swift */; };
75C03B94D42A4B2C7871A6607BB8E2D6 /* CollectionFlowDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C60770A293C32AF6DFEF8BD9597843 /* CollectionFlowDelegate.swift */; };
76E15EE6FF172907EC6293F7C471A7ED /* DiffAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E215442310E056A1978EA9D5760C1D4 /* DiffAware.swift */; };
77E60E92F55D44D77B524CE6712BB6FA /* SingleCellController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B7BDB142DD281BDC64282677DC85EAA /* SingleCellController.swift */; };
826398D12955504DC62BF1167ED6D605 /* Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9138D045BD09D068F3E99C8C8C6D9A52 /* Display.swift */; };
854C49070D0FE89878C8E5D0124CD65E /* ScrollDismission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507F6CFD78D079631EF4C7414D722BE9 /* ScrollDismission.swift */; };
8CBEFDF21CBA10F3358929B79D9A29B5 /* UIStackView+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC51A8E479CDC53438411C9DC52FACE /* UIStackView+ext.swift */; };
9052FD4E4A2BF4D796D2521EE47B2069 /* UIButton+layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FEFFB563FD2B0AD812EE3011859DACD /* UIButton+layout.swift */; };
93C8DEBCE66F72F7E1F2C56C3C48F212 /* CoreGraphics+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C2F48088F779A8F8FD0D3AB796A081B /* CoreGraphics+ext.swift */; };
999AFF8B082890847D8ED1EDF59F8BEE /* DeepDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FAB1B98BE9D96E1701AA0892C18E5B8 /* DeepDiff.swift */; };
9B702BEA0A412AE61A2A100047A2682A /* ImageSettable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 655F593EC54CC0CC958E764E3A09282D /* ImageSettable.swift */; };
9E1484918CFB62F8A4DBAFD2604BDBBC /* Require.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519B7CEB0403B5FAF2E8610A97B1501 /* Require.swift */; };
9E5606121D34743FE6F4D34A4A5A0704 /* UIViewController+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BE5E5B9BC9342D12093473CC3AAD147 /* UIViewController+ext.swift */; };
A064E2905C6E6EDBB8A956E45B6E2847 /* UIView+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F184EF2BEEE4AD727A781280D50E64 /* UIView+ext.swift */; };
A2591F809F08E89B6AD7B6DA7561154D /* CollectionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE0503B04700BC34A6A721CDE0F749 /* CollectionSection.swift */; };
A3DA1CA55FB34B50280E9E24460A7C50 /* Then.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7478349DD7D5B359C7BDCE6B82A1658 /* Then.swift */; };
A4D0DE6907F75000EFA2D909D97426DA /* UITableView+Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41498A7B2FB6C962AF2CAA1727FA4BEE /* UITableView+Reusable.swift */; };
A5EC34CCDF7A16CDB83E879951608D29 /* CollectionSupplementProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3962543B622CFDF60B27C1E57A8EB2DC /* CollectionSupplementProtocol.swift */; };
A848BE440EAD8450C7B15BE5DB31BAE7 /* String+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2149259B5AE77BCB89C40EA8AE51FAF8 /* String+ext.swift */; };
A956D28489AB7AB9B6DCF30C903F3E73 /* IndexPathConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E135A4AEFE85701CBF9E0A8FB4DA413 /* IndexPathConverter.swift */; };
B4634EB2195A74E5705E2EB5153D3961 /* Collections+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBAC24B57F8D1BC9EF7FD1B9338F8465 /* Collections+Extensions.swift */; };
B958EFF6259E0EE099176C57F09CEC4D /* UIImage+Gradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7BDDF72B5E7808085AF42B49A8E4304 /* UIImage+Gradient.swift */; };
B9E8F5741335FF9544757AEEFA063C4C /* UIAlertController+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E58A77B34639C4325DEBB0B8BBCB52 /* UIAlertController+ext.swift */; };
BD69427FEADE03F3A2931FDE6A90B28C /* UIScrollView+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = F19BE43ABB3F8A8B56A4DD5F038130D2 /* UIScrollView+ext.swift */; };
C1EA2BF250F881A8CE274F56698CA5ED /* Hapticable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 352DCA45D28C00C59D68C1E66313DA94 /* Hapticable.swift */; };
C5B3A325A391083BCBE0225D0E492651 /* AlphaTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73F563779A49E6F8BABD9F7F096FAE38 /* AlphaTransition.swift */; };
C980A8B4B7D46F918161A80D523F5B9A /* Comparable+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF39CDC694A39ABC8161159EFBF1969 /* Comparable+ext.swift */; };
CA40B44897ADD77342DE139BB3D226F0 /* DictionaryDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540BECA9B3C663E6711DAB28887C3F18 /* DictionaryDecoder.swift */; };
CA706B65DFD60F03EDC1773828AC4306 /* UIColor+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002CE560EE32DF3EA2939698D55CA6DC /* UIColor+ext.swift */; };
CA834F710C7B8DE7E78756369DB8B082 /* DateUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD719201D9EBBBFBC9D36540F14DFA52 /* DateUtilities.swift */; };
CAF36258A097CBCB956B2742329293F7 /* SwiftyColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55C7669A602A5ACC46EB4D109E37EBBD /* SwiftyColor.swift */; };
D5136D58B70ED60743594DDA623340A6 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B2707546E60D2905D4AF46DB62253F2 /* Result.swift */; };
D57151A2D17D6AA1A791C365350D6637 /* IndexSetConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD4AE276360C5D697A6FE49EC5966A25 /* IndexSetConverter.swift */; };
D7D7B6285FF6691B27A7D73FF196468A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 325E227EFA7126E2C5DD6AD776413857 /* Bundle.swift */; };
D91F78D2FCE584E9E3E945F8FDD0C80E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6604A7D69453B4569E4E4827FB9155A9 /* Foundation.framework */; };
D968A8500BD1AC4EC3FD58F23FCD6D43 /* Collections+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C8786B90BD56FD05CE4783E0C4376AA /* Collections+ext.swift */; };
@@ -97,13 +86,9 @@
E5B2024891F21779A4AB81AB91A2A03D /* UIImage+ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0F598BCDB420F275A7F1F43F9FC7C90 /* UIImage+ext.swift */; };
EAF44B11790232341E89C0702F35AE13 /* ValidatableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 187CAF707C37B678EAE174AAD0E9A9A0 /* ValidatableView.swift */; };
EB0A1625ED2019436ED4AC46F426F3DA /* TimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5615B6D865408D6F06B2F71F6FA57D /* TimingFunction.swift */; };
ECB21B60D35987F9AFFEC942F656D70B /* CollectionDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617AD57D60956AE03CF1A749AC9EAC63 /* CollectionDriver.swift */; };
F0A66F0F3A522BECA0BCFE8EAB863330 /* CollectionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C4DD617686BC4EFC847C517E9D795F /* CollectionDelegate.swift */; };
F1C2819748E2528F3DC497BD3B369EB2 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7209A4A336A5EED90B1CAFE2F60EF734 /* Change.swift */; };
F40750FA2D57A01A96A9EED6AF586FF9 /* TouchRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F35D35AEE8986797BCB7527E30A0FA /* TouchRecognizer.swift */; };
F40A972F66F6674CDEA0187860E8785B /* ScaleModalTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7B5BD275A40EA5A7887E4CDB8A5084 /* ScaleModalTransitioning.swift */; };
F844B62463F01BDF65C1553CD6670CC2 /* Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93708F44C72C4C524438FB78D87F11F2 /* Random.swift */; };
F8907750D7B78472C381EA587CA1DDE8 /* MoveReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E49B104F3AB6CF2FA7145DAD7073C5 /* MoveReducer.swift */; };
FBA69FEF5BF5077D1A63854B7286CC57 /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 240C5CEE131CBCE6AA722022C094268E /* AsyncOperation.swift */; };
FC73128B984C0755DA32C1E1A15A5DBB /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4F37218272562786A1EEC14A219F1CB /* GradientView.swift */; };
/* End PBXBuildFile section */
@@ -119,20 +104,14 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
002CE560EE32DF3EA2939698D55CA6DC /* UIColor+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIColor+ext.swift"; sourceTree = "<group>"; };
03B1B68A1207D9BA0DEC85B5917051B3 /* Pods-utopia_Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-utopia_Example-acknowledgements.markdown"; sourceTree = "<group>"; };
04B8F1CF04C636E2B5F6107FCFE6A533 /* Snapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Snapshot.swift; sourceTree = "<group>"; };
0737DBB484F81C7B892CB23EBFFDEF86 /* DictionaryCodingKey.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DictionaryCodingKey.swift; sourceTree = "<group>"; };
09EDFCC54FF8EC7D7F64E9001D66CD11 /* DictionaryErrors.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DictionaryErrors.swift; sourceTree = "<group>"; };
0B2707546E60D2905D4AF46DB62253F2 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
0DE0C9BA6C35A8CBF0101B1C408D1CC8 /* UITableView+DeepDiff.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UITableView+DeepDiff.swift"; sourceTree = "<group>"; };
0E215442310E056A1978EA9D5760C1D4 /* DiffAware.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DiffAware.swift; sourceTree = "<group>"; };
0FEFFB563FD2B0AD812EE3011859DACD /* UIButton+layout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIButton+layout.swift"; sourceTree = "<group>"; };
1823695516669A365A0F87CA37208712 /* Optional+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "Optional+ext.swift"; sourceTree = "<group>"; };
187CAF707C37B678EAE174AAD0E9A9A0 /* ValidatableView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ValidatableView.swift; sourceTree = "<group>"; };
1C2142D6AC34C79CCB601E097A9C3922 /* BarButtons.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BarButtons.swift; sourceTree = "<group>"; };
1DF39CDC694A39ABC8161159EFBF1969 /* Comparable+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "Comparable+ext.swift"; sourceTree = "<group>"; };
1E135A4AEFE85701CBF9E0A8FB4DA413 /* IndexPathConverter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = IndexPathConverter.swift; sourceTree = "<group>"; };
2149259B5AE77BCB89C40EA8AE51FAF8 /* String+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "String+ext.swift"; sourceTree = "<group>"; };
240C5CEE131CBCE6AA722022C094268E /* AsyncOperation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = "<group>"; };
24E58A77B34639C4325DEBB0B8BBCB52 /* UIAlertController+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIAlertController+ext.swift"; sourceTree = "<group>"; };
@@ -144,12 +123,19 @@
32E405E677F5BBAF94C423F133E9F257 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3519B7CEB0403B5FAF2E8610A97B1501 /* Require.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Require.swift; sourceTree = "<group>"; };
352DCA45D28C00C59D68C1E66313DA94 /* Hapticable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Hapticable.swift; sourceTree = "<group>"; };
3962543B622CFDF60B27C1E57A8EB2DC /* CollectionSupplementProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CollectionSupplementProtocol.swift; sourceTree = "<group>"; };
3B7BDB142DD281BDC64282677DC85EAA /* SingleCellController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SingleCellController.swift; sourceTree = "<group>"; };
3FAB1B98BE9D96E1701AA0892C18E5B8 /* DeepDiff.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DeepDiff.swift; sourceTree = "<group>"; };
391DB7CD215316AB0083B8C3 /* HorizontalAlignmentLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalAlignmentLayout.swift; sourceTree = "<group>"; };
391DB7CF215316F50083B8C3 /* InteractiveDismiss.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveDismiss.swift; sourceTree = "<group>"; };
391DB7D3215317990083B8C3 /* GestureRecognizerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GestureRecognizerDelegate.swift; sourceTree = "<group>"; };
391DB7D5215317A20083B8C3 /* TouchInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchInteraction.swift; sourceTree = "<group>"; };
3948F8B82193A3000043FD2E /* PinnedHeaderFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinnedHeaderFlowLayout.swift; sourceTree = "<group>"; };
399D418D21AC8D1200A0B1EA /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
399D419021AC8DC300A0B1EA /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
399D419121AC8DC300A0B1EA /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
399D419621AC8E0C00A0B1EA /* VerticalAlignmentLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalAlignmentLayout.swift; sourceTree = "<group>"; };
399D419721AC8E0D00A0B1EA /* PaginationFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationFlowLayout.swift; sourceTree = "<group>"; };
399D419821AC8E0D00A0B1EA /* SnappingFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnappingFlowLayout.swift; sourceTree = "<group>"; };
41498A7B2FB6C962AF2CAA1727FA4BEE /* UITableView+Reusable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UITableView+Reusable.swift"; sourceTree = "<group>"; };
43A88C1BE5A50F7B80FB3A7EF1DD75D5 /* Associated.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Associated.swift; sourceTree = "<group>"; };
45C60770A293C32AF6DFEF8BD9597843 /* CollectionFlowDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CollectionFlowDelegate.swift; sourceTree = "<group>"; };
474B741D8D941D2368F81BD923718887 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
485554E3BDF22424E17D04F2C017B0C9 /* SwiftyImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SwiftyImage.swift; sourceTree = "<group>"; };
496E51ADD4779D6AC8AEB0E4AC214241 /* SwiftyImageView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SwiftyImageView.swift; sourceTree = "<group>"; };
@@ -159,45 +145,35 @@
507F6CFD78D079631EF4C7414D722BE9 /* ScrollDismission.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ScrollDismission.swift; sourceTree = "<group>"; };
51A7A173B2BB4912B56024A0AB99C624 /* UISearchUtilities.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UISearchUtilities.swift; sourceTree = "<group>"; };
53845654F739F2E9534B24DEC5A577AD /* SimpleError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SimpleError.swift; sourceTree = "<group>"; };
540BECA9B3C663E6711DAB28887C3F18 /* DictionaryDecoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DictionaryDecoder.swift; sourceTree = "<group>"; };
55C7669A602A5ACC46EB4D109E37EBBD /* SwiftyColor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SwiftyColor.swift; sourceTree = "<group>"; };
58C4DD617686BC4EFC847C517E9D795F /* CollectionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CollectionDelegate.swift; sourceTree = "<group>"; };
5C127A0077D1A43E0B1AF57D5C3E0EC4 /* Toggler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Toggler.swift; sourceTree = "<group>"; };
5D8ADED2A9CC7AC64300CA0741531748 /* CollectionItemProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CollectionItemProtocol.swift; sourceTree = "<group>"; };
617AD57D60956AE03CF1A749AC9EAC63 /* CollectionDriver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CollectionDriver.swift; sourceTree = "<group>"; };
655F593EC54CC0CC958E764E3A09282D /* ImageSettable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ImageSettable.swift; sourceTree = "<group>"; };
6604A7D69453B4569E4E4827FB9155A9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
66469B3F430396881B1F26EDD4EB8C0C /* utopia.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; path = utopia.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
66469B3F430396881B1F26EDD4EB8C0C /* utopia.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; path = utopia.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
6C9C018F9DF21B16BCBA1BD49027615B /* TransitioningDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TransitioningDelegate.swift; sourceTree = "<group>"; };
7209A4A336A5EED90B1CAFE2F60EF734 /* Change.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Change.swift; sourceTree = "<group>"; };
73E2BA02088FD493C09D02E47ABE4388 /* StringValidators.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StringValidators.swift; sourceTree = "<group>"; };
73F563779A49E6F8BABD9F7F096FAE38 /* AlphaTransition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AlphaTransition.swift; sourceTree = "<group>"; };
7821B811634043201CB58B8B98018ED5 /* CollectionItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CollectionItem.swift; sourceTree = "<group>"; };
7C2F48088F779A8F8FD0D3AB796A081B /* CoreGraphics+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "CoreGraphics+ext.swift"; sourceTree = "<group>"; };
7FAAEBDF042428F0A388D948B4DF4879 /* UIButton+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIButton+ext.swift"; sourceTree = "<group>"; };
80C3D37E1C6C9537466FBA3F006E1A2D /* InputVisibilityController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = InputVisibilityController.swift; sourceTree = "<group>"; };
82E49B104F3AB6CF2FA7145DAD7073C5 /* MoveReducer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MoveReducer.swift; sourceTree = "<group>"; };
85C649278AE9FEC1B162C60ABAAE5123 /* Time.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
8652424025F661444A921AE66149ED15 /* utopia.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = utopia.modulemap; sourceTree = "<group>"; };
8BE5E5B9BC9342D12093473CC3AAD147 /* UIViewController+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIViewController+ext.swift"; sourceTree = "<group>"; };
8C4EEBAEB1E1FDB6EDA95CFF0B7ECFA7 /* StatusBarAppearance.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StatusBarAppearance.swift; sourceTree = "<group>"; };
8C8786B90BD56FD05CE4783E0C4376AA /* Collections+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "Collections+ext.swift"; sourceTree = "<group>"; };
90CCD71777EAB46247B48FA67E0B3132 /* WagnerFischer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = WagnerFischer.swift; sourceTree = "<group>"; };
9138D045BD09D068F3E99C8C8C6D9A52 /* Display.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Display.swift; sourceTree = "<group>"; };
93708F44C72C4C524438FB78D87F11F2 /* Random.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Random.swift; sourceTree = "<group>"; };
93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
95B1B08AF40BCF36FFC3BF6C3ACEC9B8 /* UIBarButtonItem+Signals.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Signals.swift"; sourceTree = "<group>"; };
979016747919997EAF2EC27C458E3C36 /* UICollectionView+Reusable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Reusable.swift"; sourceTree = "<group>"; };
97F35D35AEE8986797BCB7527E30A0FA /* TouchRecognizer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TouchRecognizer.swift; sourceTree = "<group>"; };
9C0BAFFF44BDDD1F5D3F75989ECECCDA /* Pods_utopia_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_utopia_Example.framework; path = "Pods-utopia_Example.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
9C1B520DA013589C85626024C3113C85 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = "<group>"; };
9C0BAFFF44BDDD1F5D3F75989ECECCDA /* Pods_utopia_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_utopia_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9C1B520DA013589C85626024C3113C85 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
9F7B5BD275A40EA5A7887E4CDB8A5084 /* ScaleModalTransitioning.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ScaleModalTransitioning.swift; sourceTree = "<group>"; };
9F9BB03A7F405472718898E3CF28F4BF /* CodableUtilities.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CodableUtilities.swift; sourceTree = "<group>"; };
9FC51A8E479CDC53438411C9DC52FACE /* UIStackView+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIStackView+ext.swift"; sourceTree = "<group>"; };
A054AB447DAE64070D4411D2FBF7A04B /* Pods-utopia_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-utopia_Example.modulemap"; sourceTree = "<group>"; };
A1F184EF2BEEE4AD727A781280D50E64 /* UIView+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIView+ext.swift"; sourceTree = "<group>"; };
A243A5C208AE35F0F0C33004AA1B6620 /* Literals.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Literals.swift; sourceTree = "<group>"; };
A3F60E978A7284B439BE2FCDE3600871 /* DictionaryEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DictionaryEncoder.swift; sourceTree = "<group>"; };
A4F37218272562786A1EEC14A219F1CB /* GradientView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = "<group>"; };
A9CADBB7F40CC7CCB173E2090A583C51 /* Math.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Math.swift; sourceTree = "<group>"; };
AD719201D9EBBBFBC9D36540F14DFA52 /* DateUtilities.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DateUtilities.swift; sourceTree = "<group>"; };
@@ -206,34 +182,28 @@
B7478349DD7D5B359C7BDCE6B82A1658 /* Then.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Then.swift; sourceTree = "<group>"; };
B7D2225A392E110910C049631DFD21A8 /* Reachability.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = "<group>"; };
B9483E800344D13D356F1D83D85F3E8D /* Pods-utopia_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-utopia_Example-dummy.m"; sourceTree = "<group>"; };
BAC1F58768C710A3C7F6F45E94924CCC /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = "<group>"; };
BC9BCB36774C899E2CFF014515E184DF /* CollectionSupplement.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CollectionSupplement.swift; sourceTree = "<group>"; };
BAC1F58768C710A3C7F6F45E94924CCC /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
BE531C30779681DEBE1F6FF256BD3F89 /* UINavigationController+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UINavigationController+ext.swift"; sourceTree = "<group>"; };
C0F598BCDB420F275A7F1F43F9FC7C90 /* UIImage+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIImage+ext.swift"; sourceTree = "<group>"; };
C78E8030A43AACA23F8A7A60874912D6 /* Signal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Signal.swift; sourceTree = "<group>"; };
C7BDDF72B5E7808085AF42B49A8E4304 /* UIImage+Gradient.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIImage+Gradient.swift"; sourceTree = "<group>"; };
CB675E040FD2E33E8569104B5F66CE0F /* NSAttributedStrings+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "NSAttributedStrings+ext.swift"; sourceTree = "<group>"; };
CDC125537CD9723ABCD3B1A86A59DF34 /* Pods-utopia_Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-utopia_Example-umbrella.h"; sourceTree = "<group>"; };
D0BE0503B04700BC34A6A721CDE0F749 /* CollectionSection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CollectionSection.swift; sourceTree = "<group>"; };
D3453900FDF815600817E368E63B0D82 /* Pods-utopia_Example-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-utopia_Example-resources.sh"; sourceTree = "<group>"; };
D404C95A8030180967F3BA99BECFE580 /* UICollectionView+DeepDiff.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UICollectionView+DeepDiff.swift"; sourceTree = "<group>"; };
D40A7DF8EA44630F470D2EFA45276B2F /* Pods-utopia_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-utopia_Example-frameworks.sh"; sourceTree = "<group>"; };
D845CCECCD4F6822F1ED13F76CC8FE82 /* UITextField+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UITextField+ext.swift"; sourceTree = "<group>"; };
E56C9C357A8A21ED85640622889CA007 /* Pods-utopia_Example-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-utopia_Example-acknowledgements.plist"; sourceTree = "<group>"; };
E6EB35A67E9198A6995778F2F1A9FBFD /* Heckel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Heckel.swift; sourceTree = "<group>"; };
EBAC24B57F8D1BC9EF7FD1B9338F8465 /* Collections+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "Collections+Extensions.swift"; sourceTree = "<group>"; };
ECFF9B3761CE5BD778DBD8E0477703BF /* utopia-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "utopia-dummy.m"; sourceTree = "<group>"; };
EF4F6D046DADE0DD3A4E045F76D1CE16 /* UIWindow+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIWindow+ext.swift"; sourceTree = "<group>"; };
F19BE43ABB3F8A8B56A4DD5F038130D2 /* UIScrollView+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIScrollView+ext.swift"; sourceTree = "<group>"; };
F2AB7054EF292E6C6EDA81B3B7AC3586 /* CoreAnimation+ext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "CoreAnimation+ext.swift"; sourceTree = "<group>"; };
F3B061582844CAED75F90A2B4DF96BBF /* UIControl+Signals.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIControl+Signals.swift"; sourceTree = "<group>"; };
F4006CD46651420CC284F64C2BB3E679 /* utopia.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = utopia.framework; path = utopia.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F4006CD46651420CC284F64C2BB3E679 /* utopia.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = utopia.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F61C18A1761D447D4249ABA4E14D11AE /* Pods-utopia_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-utopia_Example.release.xcconfig"; sourceTree = "<group>"; };
F79E5A847C4EE299FFCC34B5C4BB4354 /* UIView+transitions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIView+transitions.swift"; sourceTree = "<group>"; };
F90027142D498AD8ABA5215ADC06AC80 /* AssociatedObject.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssociatedObject.swift; sourceTree = "<group>"; };
FA90010E69BA8984268435FC0BC4C19F /* LayoutGuides.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LayoutGuides.swift; sourceTree = "<group>"; };
FAFC54380DC5D0CA56A4551D0E7806AE /* Pods-utopia_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-utopia_Example.debug.xcconfig"; sourceTree = "<group>"; };
FD4AE276360C5D697A6FE49EC5966A25 /* IndexSetConverter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = IndexSetConverter.swift; sourceTree = "<group>"; };
FE5615B6D865408D6F06B2F71F6FA57D /* TimingFunction.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TimingFunction.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -261,7 +231,6 @@
isa = PBXGroup;
children = (
655F593EC54CC0CC958E764E3A09282D /* ImageSettable.swift */,
55C7669A602A5ACC46EB4D109E37EBBD /* SwiftyColor.swift */,
485554E3BDF22424E17D04F2C017B0C9 /* SwiftyImage.swift */,
496E51ADD4779D6AC8AEB0E4AC214241 /* SwiftyImageView.swift */,
);
@@ -274,34 +243,9 @@
children = (
A4F37218272562786A1EEC14A219F1CB /* GradientView.swift */,
);
name = Views;
path = Views;
sourceTree = "<group>";
};
0C664FBE14B49F100468748D16E8AA36 /* DictionaryCoding */ = {
isa = PBXGroup;
children = (
0737DBB484F81C7B892CB23EBFFDEF86 /* DictionaryCodingKey.swift */,
540BECA9B3C663E6711DAB28887C3F18 /* DictionaryDecoder.swift */,
A3F60E978A7284B439BE2FCDE3600871 /* DictionaryEncoder.swift */,
09EDFCC54FF8EC7D7F64E9001D66CD11 /* DictionaryErrors.swift */,
);
name = DictionaryCoding;
path = DictionaryCoding;
sourceTree = "<group>";
};
1709A2FF999AEA557856138B4BEE53A4 /* iOS */ = {
isa = PBXGroup;
children = (
1E135A4AEFE85701CBF9E0A8FB4DA413 /* IndexPathConverter.swift */,
FD4AE276360C5D697A6FE49EC5966A25 /* IndexSetConverter.swift */,
D404C95A8030180967F3BA99BECFE580 /* UICollectionView+DeepDiff.swift */,
0DE0C9BA6C35A8CBF0101B1C408D1CC8 /* UITableView+DeepDiff.swift */,
);
name = iOS;
path = iOS;
sourceTree = "<group>";
};
2656CB43AC1C0394F9AE8BB2C3FDA13C /* Pods-utopia_Example */ = {
isa = PBXGroup;
children = (
@@ -320,15 +264,6 @@
path = "Target Support Files/Pods-utopia_Example";
sourceTree = "<group>";
};
27E0941C66C0AE0979F23F42144634D6 /* Section */ = {
isa = PBXGroup;
children = (
D0BE0503B04700BC34A6A721CDE0F749 /* CollectionSection.swift */,
);
name = Section;
path = Section;
sourceTree = "<group>";
};
290A9D5AED8BFBC4A9E64E0F9ED12AE3 /* Pod */ = {
isa = PBXGroup;
children = (
@@ -339,35 +274,49 @@
name = Pod;
sourceTree = "<group>";
};
298C0019EBCB51F394F9FBF6A237AF84 /* Shared */ = {
391DB7CC2153164E0083B8C3 /* CollectionLayout */ = {
isa = PBXGroup;
children = (
7209A4A336A5EED90B1CAFE2F60EF734 /* Change.swift */,
EBAC24B57F8D1BC9EF7FD1B9338F8465 /* Collections+Extensions.swift */,
3FAB1B98BE9D96E1701AA0892C18E5B8 /* DeepDiff.swift */,
82E49B104F3AB6CF2FA7145DAD7073C5 /* MoveReducer.swift */,
EBBAD62BE8FC301DB475A5815674E8B8 /* Algorithms */,
3948F8B82193A3000043FD2E /* PinnedHeaderFlowLayout.swift */,
391DB7CD215316AB0083B8C3 /* HorizontalAlignmentLayout.swift */,
399D419721AC8E0D00A0B1EA /* PaginationFlowLayout.swift */,
399D419821AC8E0D00A0B1EA /* SnappingFlowLayout.swift */,
399D419621AC8E0C00A0B1EA /* VerticalAlignmentLayout.swift */,
);
name = Shared;
path = Shared;
path = CollectionLayout;
sourceTree = "<group>";
};
391DB7D1215317290083B8C3 /* Animations */ = {
isa = PBXGroup;
children = (
391DB7D5215317A20083B8C3 /* TouchInteraction.swift */,
);
path = Animations;
sourceTree = "<group>";
};
391DB7D2215317760083B8C3 /* Gestures */ = {
isa = PBXGroup;
children = (
391DB7D3215317990083B8C3 /* GestureRecognizerDelegate.swift */,
);
path = Gestures;
sourceTree = "<group>";
};
4C85535653D978EAFC4C56A439ADA3FA /* utopia */ = {
isa = PBXGroup;
children = (
AF519AAB88449C069E4444CD0A7D84A8 /* CodingDeconding */,
8241AC4FBF88BD92580122D1ADF6D6E4 /* CollectionDriver */,
93DB251A38E863705E10AF7CBB6CF7D7 /* Haptic */,
01B7BFB7353AE9D18262AD05EA941F21 /* OptimizationTricks */,
290A9D5AED8BFBC4A9E64E0F9ED12AE3 /* Pod */,
768C46A62A04556281811B75E933B353 /* Signals */,
8C27DC0C74C30233B185E75F27845D0A /* Support Files */,
D9197775FFD136C39DC9830BA734884F /* SwiftStdLib+ext */,
C27A836CBE9528008AEACE449CAEE29E /* Transitions */,
A2D72D5D8ACADAD2D6EB22A8DE9C1415 /* Types */,
BAA834FD327249945A0B7BD0F2560561 /* UIKit */,
CB87AC1D6A6B7C7CC462ED8CAA7B02A3 /* Utilities */,
7595EBA1F3F4CD4ECB8D3C14E5C0B1A6 /* Validation */,
8C27DC0C74C30233B185E75F27845D0A /* Support Files */,
290A9D5AED8BFBC4A9E64E0F9ED12AE3 /* Pod */,
);
name = utopia;
path = ../..;
@@ -425,30 +374,6 @@
);
sourceTree = "<group>";
};
8241AC4FBF88BD92580122D1ADF6D6E4 /* CollectionDriver */ = {
isa = PBXGroup;
children = (
58C4DD617686BC4EFC847C517E9D795F /* CollectionDelegate.swift */,
617AD57D60956AE03CF1A749AC9EAC63 /* CollectionDriver.swift */,
AEAE1D9FBF4BEA1642502703476A99AA /* FlowLayout */,
AC63961471922BAD6C682A56AA2B6BE7 /* Item */,
27E0941C66C0AE0979F23F42144634D6 /* Section */,
B4DFD397BE9C24A51D2824708514BEF7 /* Supplement */,
);
name = CollectionDriver;
path = utopia/Source/CollectionDriver;
sourceTree = "<group>";
};
827B85A573C7BACC6DE48A67B0902E37 /* DeepDiff */ = {
isa = PBXGroup;
children = (
1709A2FF999AEA557856138B4BEE53A4 /* iOS */,
298C0019EBCB51F394F9FBF6A237AF84 /* Shared */,
);
name = DeepDiff;
path = DeepDiff;
sourceTree = "<group>";
};
8C27DC0C74C30233B185E75F27845D0A /* Support Files */ = {
isa = PBXGroup;
children = (
@@ -463,15 +388,6 @@
path = "Example/Pods/Target Support Files/utopia";
sourceTree = "<group>";
};
91132A607D073009ACB11BFAA53E075A /* Utilities */ = {
isa = PBXGroup;
children = (
9F9BB03A7F405472718898E3CF28F4BF /* CodableUtilities.swift */,
);
name = Utilities;
path = Utilities;
sourceTree = "<group>";
};
93DB251A38E863705E10AF7CBB6CF7D7 /* Haptic */ = {
isa = PBXGroup;
children = (
@@ -491,46 +407,15 @@
path = utopia/Source/Types;
sourceTree = "<group>";
};
AC63961471922BAD6C682A56AA2B6BE7 /* Item */ = {
isa = PBXGroup;
children = (
7821B811634043201CB58B8B98018ED5 /* CollectionItem.swift */,
5D8ADED2A9CC7AC64300CA0741531748 /* CollectionItemProtocol.swift */,
3B7BDB142DD281BDC64282677DC85EAA /* SingleCellController.swift */,
);
name = Item;
path = Item;
sourceTree = "<group>";
};
AEAE1D9FBF4BEA1642502703476A99AA /* FlowLayout */ = {
isa = PBXGroup;
children = (
45C60770A293C32AF6DFEF8BD9597843 /* CollectionFlowDelegate.swift */,
);
name = FlowLayout;
path = FlowLayout;
sourceTree = "<group>";
};
AF519AAB88449C069E4444CD0A7D84A8 /* CodingDeconding */ = {
isa = PBXGroup;
children = (
0C664FBE14B49F100468748D16E8AA36 /* DictionaryCoding */,
91132A607D073009ACB11BFAA53E075A /* Utilities */,
9F9BB03A7F405472718898E3CF28F4BF /* CodableUtilities.swift */,
);
name = CodingDeconding;
path = utopia/Source/CodingDeconding;
sourceTree = "<group>";
};
B4DFD397BE9C24A51D2824708514BEF7 /* Supplement */ = {
isa = PBXGroup;
children = (
BC9BCB36774C899E2CFF014515E184DF /* CollectionSupplement.swift */,
3962543B622CFDF60B27C1E57A8EB2DC /* CollectionSupplementProtocol.swift */,
);
name = Supplement;
path = Supplement;
sourceTree = "<group>";
};
BAA834FD327249945A0B7BD0F2560561 /* UIKit */ = {
isa = PBXGroup;
children = (
@@ -541,6 +426,9 @@
8C4EEBAEB1E1FDB6EDA95CFF0B7ECFA7 /* StatusBarAppearance.swift */,
5C127A0077D1A43E0B1AF57D5C3E0EC4 /* Toggler.swift */,
51A7A173B2BB4912B56024A0AB99C624 /* UISearchUtilities.swift */,
391DB7D2215317760083B8C3 /* Gestures */,
391DB7D1215317290083B8C3 /* Animations */,
391DB7CC2153164E0083B8C3 /* CollectionLayout */,
CD49264C135E357003C30ACD16A46BC9 /* Extensions */,
E9DB469A97791CE54334A504CC837D76 /* Keyboard */,
04906C8071F52A7C8413F1BDB4AC39D6 /* Views */,
@@ -562,6 +450,7 @@
children = (
73F563779A49E6F8BABD9F7F096FAE38 /* AlphaTransition.swift */,
9F7B5BD275A40EA5A7887E4CDB8A5084 /* ScaleModalTransitioning.swift */,
391DB7CF215316F50083B8C3 /* InteractiveDismiss.swift */,
6C9C018F9DF21B16BCBA1BD49027615B /* TransitioningDelegate.swift */,
F79E5A847C4EE299FFCC34B5C4BB4354 /* UIView+transitions.swift */,
);
@@ -585,12 +474,13 @@
3519B7CEB0403B5FAF2E8610A97B1501 /* Require.swift */,
0B2707546E60D2905D4AF46DB62253F2 /* Result.swift */,
507F6CFD78D079631EF4C7414D722BE9 /* ScrollDismission.swift */,
399D419021AC8DC300A0B1EA /* NSAttributedString+Extensions.swift */,
399D419121AC8DC300A0B1EA /* String+Extensions.swift */,
399D418D21AC8D1200A0B1EA /* UIColor+Extensions.swift */,
53845654F739F2E9534B24DEC5A577AD /* SimpleError.swift */,
B7478349DD7D5B359C7BDCE6B82A1658 /* Then.swift */,
85C649278AE9FEC1B162C60ABAAE5123 /* Time.swift */,
FE5615B6D865408D6F06B2F71F6FA57D /* TimingFunction.swift */,
002CE560EE32DF3EA2939698D55CA6DC /* UIColor+ext.swift */,
827B85A573C7BACC6DE48A67B0902E37 /* DeepDiff */,
);
name = Utilities;
path = utopia/Source/Utilities;
@@ -617,7 +507,6 @@
8BE5E5B9BC9342D12093473CC3AAD147 /* UIViewController+ext.swift */,
EF4F6D046DADE0DD3A4E045F76D1CE16 /* UIWindow+ext.swift */,
);
name = Extensions;
path = Extensions;
sourceTree = "<group>";
};
@@ -654,7 +543,6 @@
80C3D37E1C6C9537466FBA3F006E1A2D /* InputVisibilityController.swift */,
97F35D35AEE8986797BCB7527E30A0FA /* TouchRecognizer.swift */,
);
name = InputVisibilityController;
path = InputVisibilityController;
sourceTree = "<group>";
};
@@ -664,21 +552,9 @@
4D2E2CE51EEFE3790C4B1336397E3530 /* KeyboardObserver.swift */,
E3BA05F8DF75E9B7D6DF71470F844590 /* InputVisibilityController */,
);
name = Keyboard;
path = Keyboard;
sourceTree = "<group>";
};
EBBAD62BE8FC301DB475A5815674E8B8 /* Algorithms */ = {
isa = PBXGroup;
children = (
0E215442310E056A1978EA9D5760C1D4 /* DiffAware.swift */,
E6EB35A67E9198A6995778F2F1A9FBFD /* Heckel.swift */,
90CCD71777EAB46247B48FA67E0B3132 /* WagnerFischer.swift */,
);
name = Algorithms;
path = Algorithms;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -743,14 +619,23 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0930;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1020;
TargetAttributes = {
02B55224E15D80EA5F1C45D2650F46E3 = {
LastSwiftMigration = 1020;
};
55FF8012DB818DE68BDEF0BC12B62C22 = {
LastSwiftMigration = 1020;
};
};
};
buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7DB346D0F39D3F0E887471402A8071AB;
productRefGroup = 73E0200A5FD816721F62FE79BAD1BC01 /* Products */;
@@ -771,63 +656,47 @@
C5B3A325A391083BCBE0225D0E492651 /* AlphaTransition.swift in Sources */,
3C087E90B6D5631B925B524FBD183DA5 /* Associated.swift in Sources */,
398EF3F919D8AFA5093B15156F27E353 /* AssociatedObject.swift in Sources */,
399D418F21AC8D1C00A0B1EA /* UIColor+Extensions.swift in Sources */,
FBA69FEF5BF5077D1A63854B7286CC57 /* AsyncOperation.swift in Sources */,
DC9BC229563C9D4D76C4ADD9962E181A /* BarButtons.swift in Sources */,
D7D7B6285FF6691B27A7D73FF196468A /* Bundle.swift in Sources */,
F1C2819748E2528F3DC497BD3B369EB2 /* Change.swift in Sources */,
391DB7D6215317A20083B8C3 /* TouchInteraction.swift in Sources */,
728E7F3E40B77C6AE1F2EA03435547F9 /* CodableUtilities.swift in Sources */,
F0A66F0F3A522BECA0BCFE8EAB863330 /* CollectionDelegate.swift in Sources */,
ECB21B60D35987F9AFFEC942F656D70B /* CollectionDriver.swift in Sources */,
75C03B94D42A4B2C7871A6607BB8E2D6 /* CollectionFlowDelegate.swift in Sources */,
5788D3A4E388EEC282525FA728EFFD4B /* CollectionItem.swift in Sources */,
6043ADCB4A6A8BF4E704C73292A5F4FE /* CollectionItemProtocol.swift in Sources */,
D968A8500BD1AC4EC3FD58F23FCD6D43 /* Collections+ext.swift in Sources */,
B4634EB2195A74E5705E2EB5153D3961 /* Collections+Extensions.swift in Sources */,
A2591F809F08E89B6AD7B6DA7561154D /* CollectionSection.swift in Sources */,
016CC3A1C1D7DDF9EDC5C1162D649B11 /* CollectionSupplement.swift in Sources */,
A5EC34CCDF7A16CDB83E879951608D29 /* CollectionSupplementProtocol.swift in Sources */,
C980A8B4B7D46F918161A80D523F5B9A /* Comparable+ext.swift in Sources */,
4F58D683476B2C93625C121FAC487769 /* CoreAnimation+ext.swift in Sources */,
93C8DEBCE66F72F7E1F2C56C3C48F212 /* CoreGraphics+ext.swift in Sources */,
CA834F710C7B8DE7E78756369DB8B082 /* DateUtilities.swift in Sources */,
999AFF8B082890847D8ED1EDF59F8BEE /* DeepDiff.swift in Sources */,
737F4D68613986033F0FE3870FFD20D3 /* DictionaryCodingKey.swift in Sources */,
CA40B44897ADD77342DE139BB3D226F0 /* DictionaryDecoder.swift in Sources */,
57436A3B37C2DDBE9953C104FB706A81 /* DictionaryEncoder.swift in Sources */,
46F572A824B37693AF514E85E0549C58 /* DictionaryErrors.swift in Sources */,
76E15EE6FF172907EC6293F7C471A7ED /* DiffAware.swift in Sources */,
826398D12955504DC62BF1167ED6D605 /* Display.swift in Sources */,
FC73128B984C0755DA32C1E1A15A5DBB /* GradientView.swift in Sources */,
4837A82C9004D6D819B45977200EEF92 /* Haptic.swift in Sources */,
C1EA2BF250F881A8CE274F56698CA5ED /* Hapticable.swift in Sources */,
2ADB6BCE9B619A9A4B87388877977E37 /* Heckel.swift in Sources */,
9B702BEA0A412AE61A2A100047A2682A /* ImageSettable.swift in Sources */,
A956D28489AB7AB9B6DCF30C903F3E73 /* IndexPathConverter.swift in Sources */,
D57151A2D17D6AA1A791C365350D6637 /* IndexSetConverter.swift in Sources */,
3C593180EE447D73A5ADBA3AE8909897 /* InputVisibilityController.swift in Sources */,
30522776ACB49F80FB650434A08771A2 /* KeyboardObserver.swift in Sources */,
50202062FF2FEFA5A435C025B28E6F2E /* LayoutGuides.swift in Sources */,
32B0440AA07DD473ABD2D61DF2F3FCD8 /* Literals.swift in Sources */,
35BB5E12A394D82FF0F8E675F3000FDB /* Math.swift in Sources */,
F8907750D7B78472C381EA587CA1DDE8 /* MoveReducer.swift in Sources */,
E0E0FDD5E0C600B0AC5D7E4EE318AC2B /* None.swift in Sources */,
17C8EC8E91B5BB1EC60297B16F8B0E24 /* NSAttributedStrings+ext.swift in Sources */,
5179A999CF3F2B5C221D379A978334B1 /* Optional+ext.swift in Sources */,
F844B62463F01BDF65C1553CD6670CC2 /* Random.swift in Sources */,
286B06BEE5CF5C9680D6C4E3F7C671D5 /* Reachability.swift in Sources */,
399D419421AC8DCF00A0B1EA /* NSAttributedString+Extensions.swift in Sources */,
9E1484918CFB62F8A4DBAFD2604BDBBC /* Require.swift in Sources */,
D5136D58B70ED60743594DDA623340A6 /* Result.swift in Sources */,
F40A972F66F6674CDEA0187860E8785B /* ScaleModalTransitioning.swift in Sources */,
391DB7CE215316AB0083B8C3 /* HorizontalAlignmentLayout.swift in Sources */,
854C49070D0FE89878C8E5D0124CD65E /* ScrollDismission.swift in Sources */,
391DB7D4215317990083B8C3 /* GestureRecognizerDelegate.swift in Sources */,
5533FA61D479D4E52F22B6D7E5634127 /* Signal.swift in Sources */,
35F36F6F8769CDC19189C6C44BECE03E /* SignalSubscription.swift in Sources */,
25625A79AD94F64B1C05606994AEC409 /* SimpleError.swift in Sources */,
77E60E92F55D44D77B524CE6712BB6FA /* SingleCellController.swift in Sources */,
29396B3ADC9D79BC235C37FC87898C0E /* Snapshot.swift in Sources */,
399D419521AC8DCF00A0B1EA /* String+Extensions.swift in Sources */,
40077E1D3AAA704AC97E1AEDA22CF464 /* StatusBarAppearance.swift in Sources */,
A848BE440EAD8450C7B15BE5DB31BAE7 /* String+ext.swift in Sources */,
09471D36F0C65BF373FE54C944C68820 /* StringValidators.swift in Sources */,
CAF36258A097CBCB956B2742329293F7 /* SwiftyColor.swift in Sources */,
269A0BE57E27A3DFB1063BB77EDE4935 /* SwiftyImage.swift in Sources */,
556DBB6B22CB0D57352504204588353C /* SwiftyImageView.swift in Sources */,
A3DA1CA55FB34B50280E9E24460A7C50 /* Then.swift in Sources */,
@@ -840,9 +709,8 @@
2F281C3DB35F09EF83172BA7D2A2955E /* UIBarButtonItem+Signals.swift in Sources */,
26DE0E6B75AB5050F5772B922A2AD509 /* UIButton+ext.swift in Sources */,
9052FD4E4A2BF4D796D2521EE47B2069 /* UIButton+layout.swift in Sources */,
0469A2397AB66C46BE90652653352AB2 /* UICollectionView+DeepDiff.swift in Sources */,
0BE22BEB5AE38CEC94E0A184C8946969 /* UICollectionView+Reusable.swift in Sources */,
CA706B65DFD60F03EDC1773828AC4306 /* UIColor+ext.swift in Sources */,
391DB7D0215316F50083B8C3 /* InteractiveDismiss.swift in Sources */,
696FBE84EEB2A8F325E8D551903BF524 /* UIControl+Signals.swift in Sources */,
02807C6CD1D68F6A8F1D5F5EDBC075F3 /* UIGestureRecognizer+ext.swift in Sources */,
E5B2024891F21779A4AB81AB91A2A03D /* UIImage+ext.swift in Sources */,
@@ -851,7 +719,6 @@
BD69427FEADE03F3A2931FDE6A90B28C /* UIScrollView+ext.swift in Sources */,
56AFDBABD2F98A47878F3C6D24D2C18D /* UISearchUtilities.swift in Sources */,
8CBEFDF21CBA10F3358929B79D9A29B5 /* UIStackView+ext.swift in Sources */,
5F10FDD4882A344EBC6F15A2A7824F21 /* UITableView+DeepDiff.swift in Sources */,
A4D0DE6907F75000EFA2D909D97426DA /* UITableView+Reusable.swift in Sources */,
34A0B3185049A34C0C8753721B994638 /* UITextField+ext.swift in Sources */,
A064E2905C6E6EDBB8A956E45B6E2847 /* UIView+ext.swift in Sources */,
@@ -861,7 +728,6 @@
2B4E363F17E50EDA2C69E578CAAE43BF /* utopia-dummy.m in Sources */,
EAF44B11790232341E89C0702F35AE13 /* ValidatableView.swift in Sources */,
54C77D7CC63D94A7843FA06931A72124 /* Validation.swift in Sources */,
34121FA0A44E5638EF672F112FA7EBCD /* WagnerFischer.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -869,7 +735,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3948F8B92193A3000043FD2E /* PinnedHeaderFlowLayout.swift in Sources */,
399D419A21AC8E0D00A0B1EA /* PaginationFlowLayout.swift in Sources */,
399D419921AC8E0D00A0B1EA /* VerticalAlignmentLayout.swift in Sources */,
5B190B9D09C4A639DA32D00681E1B4A7 /* Pods-utopia_Example-dummy.m in Sources */,
399D419B21AC8E0D00A0B1EA /* SnappingFlowLayout.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -890,6 +760,7 @@
baseConfigurationReference = F61C18A1761D447D4249ABA4E14D11AE /* Pods-utopia_Example.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
@@ -914,6 +785,7 @@
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
@@ -947,7 +819,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -958,6 +830,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -1021,6 +894,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -1101,7 +975,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
@@ -1114,6 +988,7 @@
baseConfigurationReference = FAFC54380DC5D0CA56A4551D0E7806AE /* Pods-utopia_Example.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
@@ -1139,6 +1014,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
+12 -28
View File
@@ -8,8 +8,6 @@
/* Begin PBXBuildFile section */
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
CEAFA6DEE8FD46FA928FC6B9 /* Pods_utopia_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D96C1039201BCADF7E730F6 /* Pods_utopia_Example.framework */; };
@@ -18,15 +16,11 @@
/* Begin PBXFileReference section */
0D96C1039201BCADF7E730F6 /* Pods_utopia_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_utopia_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
12909272F90F0829F3E16C48 /* utopia.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = utopia.podspec; path = ../utopia.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
3D6E6CE2CA9E2A4ED29C9C80 /* Pods-utopia_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-utopia_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-utopia_Tests/Pods-utopia_Tests.release.xcconfig"; sourceTree = "<group>"; };
607FACD01AFB9204008FA782 /* utopia_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = utopia_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
6F466667E3C01068D5F3BABF /* Pods-utopia_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-utopia_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-utopia_Tests/Pods-utopia_Tests.debug.xcconfig"; sourceTree = "<group>"; };
AD58C60D33BF3DE957B80316 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
B099FDB815DE6B171E39CF89 /* Pods_utopia_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_utopia_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B60E758ED76098E52DACF178 /* Pods-utopia_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-utopia_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-utopia_Example/Pods-utopia_Example.debug.xcconfig"; sourceTree = "<group>"; };
@@ -78,10 +72,6 @@
isa = PBXGroup;
children = (
607FACD51AFB9204008FA782 /* AppDelegate.swift */,
607FACD71AFB9204008FA782 /* ViewController.swift */,
607FACD91AFB9204008FA782 /* Main.storyboard */,
607FACDC1AFB9204008FA782 /* Images.xcassets */,
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
607FACD31AFB9204008FA782 /* Supporting Files */,
);
name = "Example for utopia";
@@ -91,6 +81,8 @@
607FACD31AFB9204008FA782 /* Supporting Files */ = {
isa = PBXGroup;
children = (
607FACDC1AFB9204008FA782 /* Images.xcassets */,
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
607FACD41AFB9204008FA782 /* Info.plist */,
);
name = "Supporting Files";
@@ -111,8 +103,6 @@
children = (
B60E758ED76098E52DACF178 /* Pods-utopia_Example.debug.xcconfig */,
DFFC9C07CDE48E9551AEAD4E /* Pods-utopia_Example.release.xcconfig */,
6F466667E3C01068D5F3BABF /* Pods-utopia_Tests.debug.xcconfig */,
3D6E6CE2CA9E2A4ED29C9C80 /* Pods-utopia_Tests.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@@ -146,12 +136,12 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0830;
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = CocoaPods;
TargetAttributes = {
607FACCF1AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
LastSwiftMigration = 0900;
LastSwiftMigration = 1000;
};
};
};
@@ -178,7 +168,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
);
@@ -230,7 +219,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -238,14 +226,6 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
607FACD91AFB9204008FA782 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
607FACDA1AFB9204008FA782 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = {
isa = PBXVariantGroup;
children = (
@@ -269,12 +249,14 @@
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_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_RANGE_LOOP_ANALYSIS = YES;
@@ -322,12 +304,14 @@
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_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_RANGE_LOOP_ANALYSIS = YES;
@@ -361,13 +345,13 @@
baseConfigurationReference = B60E758ED76098E52DACF178 /* Pods-utopia_Example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = utopia/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MODULE_NAME = ExampleApp;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@@ -376,13 +360,13 @@
baseConfigurationReference = DFFC9C07CDE48E9551AEAD4E /* Pods-utopia_Example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = utopia/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MODULE_NAME = ExampleApp;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Release;
};
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -40,7 +40,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
@@ -70,7 +69,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
+1 -35
View File
@@ -1,11 +1,3 @@
//
// AppDelegate.swift
// utopia
//
// Created by Dmitriy Kalachev on 04/09/2018.
// Copyright (c) 2018 Dmitriy Kalachev. All rights reserved.
//
import UIKit
@UIApplicationMain
@@ -13,34 +5,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="ufC-wZ-h7g">
<objects>
<viewController id="vXZ-lx-hvc" customClass="ViewController" customModule="utopia_Example" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="jyV-Pf-zRb"/>
<viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
-2
View File
@@ -24,8 +24,6 @@
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
-24
View File
@@ -1,24 +0,0 @@
//
// ViewController.swift
// utopia
//
// Created by Dmitriy Kalachev on 04/09/2018.
// Copyright (c) 2018 Dmitriy Kalachev. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
+2 -33
View File
@@ -1,42 +1,11 @@
#
# Be sure to run `pod lib lint utopia.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'utopia'
s.version = '0.1.0'
s.summary = 'A short description of utopia.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.summary = 'Common utilities for Swift projects'
s.homepage = 'https://github.com/Ramotion/utopia'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Dmitriy Kalachev' => 'dima.k@ramotion.com' }
s.author = { 'Ramotion' => 'igor.k@ramotion.com' }
s.source = { :git => 'https://github.com/Ramotion/utopia.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '10.0'
s.source_files = 'utopia/Source/**/*'
# s.resource_bundles = {
# 'utopia' => ['utopia/Assets/*.png']
# }
# s.public_header_files = 'Pod/Source/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
end
@@ -41,7 +41,7 @@ extension KeyedDecodingContainer {
}
public func decodeSafelyIfPresent<T: Decodable>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) -> T? {
let decoded = try? decodeIfPresent(Safe<T>.self, forKey: key)
let decoded = ((try? decodeIfPresent(Safe<T>.self, forKey: key)) as Safe<T>??)
return decoded??.value
}
}
@@ -67,10 +67,10 @@ public struct Id<Entity>: Hashable {
self.raw = raw
}
public var hashValue: Int {
return raw.hashValue
public func hash(into hasher: inout Hasher) {
hasher.combine(raw.hashValue)
}
public static func ==(lhs: Id, rhs: Id) -> Bool {
return lhs.raw == rhs.raw
}
@@ -1,49 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is largely a copy of code from Swift.org open source project's
// files JSONEncoder.swift and Codeable.swift.
//
// Unfortunately those files do not expose the internal _JSONEncoder and
// _JSONDecoder classes, which are in fact dictionary encoder/decoders and
// precisely what we want...
//
// The original code is copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
// Modifications and additional code here is copyright (c) 2018 Sam Deane, and
// is licensed under the same terms.
//
//===----------------------------------------------------------------------===//
import Foundation
internal struct DictionaryCodingKey : CodingKey {
public var stringValue: String
public var intValue: Int?
public init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
public init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
public init(stringValue: String, intValue: Int?) {
self.stringValue = stringValue
self.intValue = intValue
}
internal init(index: Int) {
self.stringValue = "Index \(index)"
self.intValue = index
}
internal static let `super` = DictionaryCodingKey(stringValue: "super")!
}
File diff suppressed because it is too large Load Diff
@@ -1,915 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is largely a copy of code from Swift.org open source project's
// files JSONEncoder.swift and Codeable.swift.
//
// Unfortunately those files do not expose the internal _JSONEncoder and
// _JSONDecoder classes, which are in fact dictionary encoder/decoders and
// precisely what we want...
//
// The original code is copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
// Modifications and additional code here is copyright (c) 2018 Sam Deane, and
// is licensed under the same terms.
//
//===----------------------------------------------------------------------===//
import Foundation
//===----------------------------------------------------------------------===//
// Dictionary Encoder
//===----------------------------------------------------------------------===//
/// `DictionaryEncoder` facilitates the encoding of `Encodable` values into Dictionary.
open class DictionaryEncoder {
// MARK: Options
/// The strategy to use for encoding `Date` values.
public enum DateEncodingStrategy {
/// Defer to `Date` for choosing an encoding. This is the default strategy.
case deferredToDate
/// Encode the `Date` as a UNIX timestamp (as a Dictionary number).
case secondsSince1970
/// Encode the `Date` as UNIX millisecond timestamp (as a Dictionary number).
case millisecondsSince1970
/// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
case iso8601
/// Encode the `Date` as a string formatted by the given formatter.
case formatted(DateFormatter)
/// Encode the `Date` as a custom value encoded by the given closure.
///
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
case custom((Date, Encoder) throws -> Void)
}
/// The strategy to use for encoding `Data` values.
public enum DataEncodingStrategy {
/// Defer to `Data` for choosing an encoding.
case deferredToData
/// Encoded the `Data` as a Base64-encoded string. This is the default strategy.
case base64
/// Encode the `Data` as a custom value encoded by the given closure.
///
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
case custom((Data, Encoder) throws -> Void)
}
/// The strategy to use for non-Dictionary-conforming floating-point values (IEEE 754 infinity and NaN).
public enum NonConformingFloatEncodingStrategy {
/// Throw upon encountering non-conforming values. This is the default strategy.
case `throw`
/// Encode the values using the given representation strings.
case convertToString(positiveInfinity: String, negativeInfinity: String, nan: String)
}
/// The strategy to use for automatically changing the value of keys before encoding.
public enum KeyEncodingStrategy {
/// Use the keys specified by each type. This is the default strategy.
case useDefaultKeys
/// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key to Dictionary payload.
///
/// Capital characters are determined by testing membership in `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` (Unicode General Categories Lu and Lt).
/// The conversion to lower case uses `Locale.system`, also known as the ICU "root" locale. This means the result is consistent regardless of the current user's locale and language preferences.
///
/// Converting from camel case to snake case:
/// 1. Splits words at the boundary of lower-case to upper-case
/// 2. Inserts `_` between words
/// 3. Lowercases the entire string
/// 4. Preserves starting and ending `_`.
///
/// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`.
///
/// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted.
case convertToSnakeCase
/// Provide a custom conversion to the key in the encoded Dictionary from the keys specified by the encoded types.
/// The full path to the current encoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before encoding.
/// If the result of the conversion is a duplicate key, then only one value will be present in the result.
case custom((_ codingPath: [CodingKey]) -> CodingKey)
internal static func _convertToSnakeCase(_ stringKey: String) -> String {
guard stringKey.count > 0 else { return stringKey }
var words : [Range<String.Index>] = []
// The general idea of this algorithm is to split words on transition from lower to upper case, then on transition of >1 upper case characters to lowercase
//
// myProperty -> my_property
// myURLProperty -> my_url_property
//
// We assume, per Swift naming conventions, that the first character of the key is lowercase.
var wordStart = stringKey.startIndex
var searchRange = stringKey.index(after: wordStart)..<stringKey.endIndex
// Find next uppercase character
while let upperCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.uppercaseLetters, options: [], range: searchRange) {
let untilUpperCase = wordStart..<upperCaseRange.lowerBound
words.append(untilUpperCase)
// Find next lowercase character
searchRange = upperCaseRange.lowerBound..<searchRange.upperBound
guard let lowerCaseRange = stringKey.rangeOfCharacter(from: CharacterSet.lowercaseLetters, options: [], range: searchRange) else {
// There are no more lower case letters. Just end here.
wordStart = searchRange.lowerBound
break
}
// Is the next lowercase letter more than 1 after the uppercase? If so, we encountered a group of uppercase letters that we should treat as its own word
let nextCharacterAfterCapital = stringKey.index(after: upperCaseRange.lowerBound)
if lowerCaseRange.lowerBound == nextCharacterAfterCapital {
// The next character after capital is a lower case character and therefore not a word boundary.
// Continue searching for the next upper case for the boundary.
wordStart = upperCaseRange.lowerBound
} else {
// There was a range of >1 capital letters. Turn those into a word, stopping at the capital before the lower case character.
let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound)
words.append(upperCaseRange.lowerBound..<beforeLowerIndex)
// Next word starts at the capital before the lowercase we just found
wordStart = beforeLowerIndex
}
searchRange = lowerCaseRange.upperBound..<searchRange.upperBound
}
words.append(wordStart..<searchRange.upperBound)
let result = words.map({ (range) in
return stringKey[range].lowercased()
}).joined(separator: "_")
return result
}
}
/// The strategy to use in encoding dates. Defaults to `.deferredToDate`.
open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate
/// The strategy to use in encoding binary data. Defaults to `.base64`.
open var dataEncodingStrategy: DataEncodingStrategy = .base64
/// The strategy to use in encoding non-conforming numbers. Defaults to `.throw`.
open var nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy = .throw
/// The strategy to use for encoding keys. Defaults to `.useDefaultKeys`.
open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys
/// Contextual user-provided information for use during encoding.
open var userInfo: [CodingUserInfoKey : Any] = [:]
/// Options set on the top-level encoder to pass down the encoding hierarchy.
fileprivate struct _Options {
let dateEncodingStrategy: DateEncodingStrategy
let dataEncodingStrategy: DataEncodingStrategy
let nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy
let keyEncodingStrategy: KeyEncodingStrategy
let userInfo: [CodingUserInfoKey : Any]
}
/// The options set on the top-level encoder.
fileprivate var options: _Options {
return _Options(dateEncodingStrategy: dateEncodingStrategy,
dataEncodingStrategy: dataEncodingStrategy,
nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy,
keyEncodingStrategy: keyEncodingStrategy,
userInfo: userInfo)
}
// MARK: - Constructing a Dictionary Encoder
/// Initializes `self` with default strategies.
public init() {}
// MARK: - Encoding Values
/// Encodes the given top-level value and returns its Dictionary representation.
///
/// - parameter value: The value to encode.
/// - returns: A new `Data` value containing the encoded Dictionary data.
/// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`.
/// - throws: An error if any value throws an error during encoding.
open func encode<T : Encodable>(_ value: T) throws -> NSDictionary {
let encoder = _DictionaryEncoder(options: self.options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
}
if topLevel is NSNull {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as null Dictionary fragment."))
} else if topLevel is NSNumber {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as number Dictionary fragment."))
} else if topLevel is NSString {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as string Dictionary fragment."))
}
return topLevel as! NSDictionary
}
// MARK: - Encoding Values
/// Encodes the given top-level value and returns its Dictionary representation.
///
/// - parameter value: The value to encode.
/// - returns: A new `Data` value containing the encoded Dictionary data.
/// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`.
/// - throws: An error if any value throws an error during encoding.
open func encode<T : Encodable>(_ value: T) throws -> [String:Any] {
let encoder = _DictionaryEncoder(options: self.options)
guard let topLevel = try encoder.box_(value) else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values."))
}
if topLevel is NSNull {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as null Dictionary fragment."))
} else if topLevel is NSNumber {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as number Dictionary fragment."))
} else if topLevel is NSString {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) encoded as string Dictionary fragment."))
}
return topLevel as! [String:Any]
}
}
// MARK: - _DictionaryEncoder
fileprivate class _DictionaryEncoder : Encoder {
// MARK: Properties
/// The encoder's storage.
fileprivate var storage: _DictionaryEncodingStorage
/// Options set on the top-level encoder.
fileprivate let options: DictionaryEncoder._Options
/// The path to the current point in encoding.
public var codingPath: [CodingKey]
/// Contextual user-provided information for use during encoding.
public var userInfo: [CodingUserInfoKey : Any] {
return self.options.userInfo
}
// MARK: - Initialization
/// Initializes `self` with the given top-level encoder options.
fileprivate init(options: DictionaryEncoder._Options, codingPath: [CodingKey] = []) {
self.options = options
self.storage = _DictionaryEncodingStorage()
self.codingPath = codingPath
}
/// Returns whether a new element can be encoded at this coding path.
///
/// `true` if an element has not yet been encoded at this coding path; `false` otherwise.
fileprivate var canEncodeNewValue: Bool {
// Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container).
// At the same time, every time a container is requested, a new value gets pushed onto the storage stack.
// If there are more values on the storage stack than on the coding path, it means the value is requesting more than one container, which violates the precondition.
//
// This means that anytime something that can request a new container goes onto the stack, we MUST push a key onto the coding path.
// Things which will not request containers do not need to have the coding path extended for them (but it doesn't matter if it is, because they will not reach here).
return self.storage.count == self.codingPath.count
}
// MARK: - Encoder Methods
public func container<Key>(keyedBy: Key.Type) -> KeyedEncodingContainer<Key> {
// If an existing keyed container was already requested, return that one.
let topContainer: NSMutableDictionary
if self.canEncodeNewValue {
// We haven't yet pushed a container at this level; do so here.
topContainer = self.storage.pushKeyedContainer()
} else {
guard let container = self.storage.containers.last as? NSMutableDictionary else {
preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.")
}
topContainer = container
}
let container = DictionaryCodingKeyedEncodingContainer<Key>(referencing: self, codingPath: self.codingPath, wrapping: topContainer)
return KeyedEncodingContainer(container)
}
public func unkeyedContainer() -> UnkeyedEncodingContainer {
// If an existing unkeyed container was already requested, return that one.
let topContainer: NSMutableArray
if self.canEncodeNewValue {
// We haven't yet pushed a container at this level; do so here.
topContainer = self.storage.pushUnkeyedContainer()
} else {
guard let container = self.storage.containers.last as? NSMutableArray else {
preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.")
}
topContainer = container
}
return _DictionaryUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer)
}
public func singleValueContainer() -> SingleValueEncodingContainer {
return self
}
}
// MARK: - Encoding Storage and Containers
fileprivate struct _DictionaryEncodingStorage {
// MARK: Properties
/// The container stack.
/// Elements may be any one of the Dictionary types (NSNull, NSNumber, NSString, NSArray, NSDictionary).
private(set) fileprivate var containers: [NSObject] = []
// MARK: - Initialization
/// Initializes `self` with no containers.
fileprivate init() {}
// MARK: - Modifying the Stack
fileprivate var count: Int {
return self.containers.count
}
fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary {
let dictionary = NSMutableDictionary()
self.containers.append(dictionary)
return dictionary
}
fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray {
let array = NSMutableArray()
self.containers.append(array)
return array
}
fileprivate mutating func push(container: NSObject) {
self.containers.append(container)
}
fileprivate mutating func popContainer() -> NSObject {
precondition(self.containers.count > 0, "Empty container stack.")
return self.containers.popLast()!
}
}
// MARK: - Encoding Containers
fileprivate struct DictionaryCodingKeyedEncodingContainer<K : CodingKey> : KeyedEncodingContainerProtocol {
typealias Key = K
// MARK: Properties
/// A reference to the encoder we're writing to.
private let encoder: _DictionaryEncoder
/// A reference to the container we're writing to.
private let container: NSMutableDictionary
/// The path of coding keys taken to get to this point in encoding.
private(set) public var codingPath: [CodingKey]
// MARK: - Initialization
/// Initializes `self` with the given references.
fileprivate init(referencing encoder: _DictionaryEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) {
self.encoder = encoder
self.codingPath = codingPath
self.container = container
}
// MARK: - Coding Path Operations
private func _converted(_ key: CodingKey) -> CodingKey {
switch encoder.options.keyEncodingStrategy {
case .useDefaultKeys:
return key
case .convertToSnakeCase:
let newKeyString = DictionaryEncoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue)
return DictionaryCodingKey(stringValue: newKeyString, intValue: key.intValue)
case .custom(let converter):
return converter(codingPath + [key])
}
}
// MARK: - KeyedEncodingContainerProtocol Methods
public mutating func encodeNil(forKey key: Key) throws {
self.container[_converted(key).stringValue] = NSNull()
}
public mutating func encode(_ value: Bool, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: Int, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: Int8, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: Int16, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: Int32, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: Int64, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: UInt, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: UInt8, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: UInt16, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: UInt32, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: UInt64, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: String, forKey key: Key) throws {
self.container[_converted(key).stringValue] = self.encoder.box(value)
}
public mutating func encode(_ value: Float, forKey key: Key) throws {
// Since the float may be invalid and throw, the coding path needs to contain this key.
self.encoder.codingPath.append(key)
defer { self.encoder.codingPath.removeLast() }
self.container[_converted(key).stringValue] = try self.encoder.box(value)
}
public mutating func encode(_ value: Double, forKey key: Key) throws {
// Since the double may be invalid and throw, the coding path needs to contain this key.
self.encoder.codingPath.append(key)
defer { self.encoder.codingPath.removeLast() }
self.container[_converted(key).stringValue] = try self.encoder.box(value)
}
public mutating func encode<T : Encodable>(_ value: T, forKey key: Key) throws {
self.encoder.codingPath.append(key)
defer { self.encoder.codingPath.removeLast() }
self.container[_converted(key).stringValue] = try self.encoder.box(value)
}
public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
let dictionary = NSMutableDictionary()
self.container[_converted(key).stringValue] = dictionary
self.codingPath.append(key)
defer { self.codingPath.removeLast() }
let container = DictionaryCodingKeyedEncodingContainer<NestedKey>(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary)
return KeyedEncodingContainer(container)
}
public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
let array = NSMutableArray()
self.container[_converted(key).stringValue] = array
self.codingPath.append(key)
defer { self.codingPath.removeLast() }
return _DictionaryUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array)
}
public mutating func superEncoder() -> Encoder {
return _DictionaryReferencingEncoder(referencing: self.encoder, key: DictionaryCodingKey.super, convertedKey: _converted(DictionaryCodingKey.super), wrapping: self.container)
}
public mutating func superEncoder(forKey key: Key) -> Encoder {
return _DictionaryReferencingEncoder(referencing: self.encoder, key: key, convertedKey: _converted(key), wrapping: self.container)
}
}
fileprivate struct _DictionaryUnkeyedEncodingContainer : UnkeyedEncodingContainer {
// MARK: Properties
/// A reference to the encoder we're writing to.
private let encoder: _DictionaryEncoder
/// A reference to the container we're writing to.
private let container: NSMutableArray
/// The path of coding keys taken to get to this point in encoding.
private(set) public var codingPath: [CodingKey]
/// The number of elements encoded into the container.
public var count: Int {
return self.container.count
}
// MARK: - Initialization
/// Initializes `self` with the given references.
fileprivate init(referencing encoder: _DictionaryEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) {
self.encoder = encoder
self.codingPath = codingPath
self.container = container
}
// MARK: - UnkeyedEncodingContainer Methods
public mutating func encodeNil() throws { self.container.add(NSNull()) }
public mutating func encode(_ value: Bool) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: Int) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: Int8) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: Int16) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: Int32) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: Int64) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: UInt) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: UInt8) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: UInt16) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: UInt32) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: UInt64) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: String) throws { self.container.add(self.encoder.box(value)) }
public mutating func encode(_ value: Float) throws {
// Since the float may be invalid and throw, the coding path needs to contain this key.
self.encoder.codingPath.append(DictionaryCodingKey(index: self.count))
defer { self.encoder.codingPath.removeLast() }
self.container.add(try self.encoder.box(value))
}
public mutating func encode(_ value: Double) throws {
// Since the double may be invalid and throw, the coding path needs to contain this key.
self.encoder.codingPath.append(DictionaryCodingKey(index: self.count))
defer { self.encoder.codingPath.removeLast() }
self.container.add(try self.encoder.box(value))
}
public mutating func encode<T : Encodable>(_ value: T) throws {
self.encoder.codingPath.append(DictionaryCodingKey(index: self.count))
defer { self.encoder.codingPath.removeLast() }
self.container.add(try self.encoder.box(value))
}
public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
self.codingPath.append(DictionaryCodingKey(index: self.count))
defer { self.codingPath.removeLast() }
let dictionary = NSMutableDictionary()
self.container.add(dictionary)
let container = DictionaryCodingKeyedEncodingContainer<NestedKey>(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary)
return KeyedEncodingContainer(container)
}
public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
self.codingPath.append(DictionaryCodingKey(index: self.count))
defer { self.codingPath.removeLast() }
let array = NSMutableArray()
self.container.add(array)
return _DictionaryUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array)
}
public mutating func superEncoder() -> Encoder {
return _DictionaryReferencingEncoder(referencing: self.encoder, at: self.container.count, wrapping: self.container)
}
}
extension _DictionaryEncoder : SingleValueEncodingContainer {
// MARK: - SingleValueEncodingContainer Methods
fileprivate func assertCanEncodeNewValue() {
precondition(self.canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.")
}
public func encodeNil() throws {
assertCanEncodeNewValue()
self.storage.push(container: NSNull())
}
public func encode(_ value: Bool) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: Int) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: Int8) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: Int16) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: Int32) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: Int64) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: UInt) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: UInt8) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: UInt16) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: UInt32) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: UInt64) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: String) throws {
assertCanEncodeNewValue()
self.storage.push(container: self.box(value))
}
public func encode(_ value: Float) throws {
assertCanEncodeNewValue()
try self.storage.push(container: self.box(value))
}
public func encode(_ value: Double) throws {
assertCanEncodeNewValue()
try self.storage.push(container: self.box(value))
}
public func encode<T : Encodable>(_ value: T) throws {
assertCanEncodeNewValue()
try self.storage.push(container: self.box(value))
}
}
// MARK: - Concrete Value Representations
extension _DictionaryEncoder {
/// Returns the given value boxed in a container appropriate for pushing onto the container stack.
fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) }
fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) }
fileprivate func box(_ float: Float) throws -> NSObject {
guard !float.isInfinite && !float.isNaN else {
guard case let .convertToString(positiveInfinity: posInfString,
negativeInfinity: negInfString,
nan: nanString) = self.options.nonConformingFloatEncodingStrategy else {
throw EncodingError._invalidFloatingPointValue(float, at: codingPath)
}
if float == Float.infinity {
return NSString(string: posInfString)
} else if float == -Float.infinity {
return NSString(string: negInfString)
} else {
return NSString(string: nanString)
}
}
return NSNumber(value: float)
}
fileprivate func box(_ double: Double) throws -> NSObject {
guard !double.isInfinite && !double.isNaN else {
guard case let .convertToString(positiveInfinity: posInfString,
negativeInfinity: negInfString,
nan: nanString) = self.options.nonConformingFloatEncodingStrategy else {
throw EncodingError._invalidFloatingPointValue(double, at: codingPath)
}
if double == Double.infinity {
return NSString(string: posInfString)
} else if double == -Double.infinity {
return NSString(string: negInfString)
} else {
return NSString(string: nanString)
}
}
return NSNumber(value: double)
}
fileprivate func box(_ date: Date) throws -> NSObject {
switch self.options.dateEncodingStrategy {
case .deferredToDate:
// Must be called with a surrounding with(pushedKey:) call.
// Dates encode as single-value objects; this can't both throw and push a container, so no need to catch the error.
try date.encode(to: self)
return self.storage.popContainer()
case .secondsSince1970:
return NSNumber(value: date.timeIntervalSince1970)
case .millisecondsSince1970:
return NSNumber(value: 1000.0 * date.timeIntervalSince1970)
case .iso8601:
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
return NSString(string: _iso8601Formatter.string(from: date))
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
case .formatted(let formatter):
return NSString(string: formatter.string(from: date))
case .custom(let closure):
let depth = self.storage.count
do {
try closure(date, self)
} catch {
// If the value pushed a container before throwing, pop it back off to restore state.
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
guard self.storage.count > depth else {
// The closure didn't encode anything. Return the default keyed container.
return NSDictionary()
}
// We can pop because the closure encoded something.
return self.storage.popContainer()
}
}
fileprivate func box(_ data: Data) throws -> NSObject {
switch self.options.dataEncodingStrategy {
case .deferredToData:
// Must be called with a surrounding with(pushedKey:) call.
let depth = self.storage.count
do {
try data.encode(to: self)
} catch {
// If the value pushed a container before throwing, pop it back off to restore state.
// This shouldn't be possible for Data (which encodes as an array of bytes), but it can't hurt to catch a failure.
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
return self.storage.popContainer()
case .base64:
return NSString(string: data.base64EncodedString())
case .custom(let closure):
let depth = self.storage.count
do {
try closure(data, self)
} catch {
// If the value pushed a container before throwing, pop it back off to restore state.
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
guard self.storage.count > depth else {
// The closure didn't encode anything. Return the default keyed container.
return NSDictionary()
}
// We can pop because the closure encoded something.
return self.storage.popContainer()
}
}
fileprivate func box<T : Encodable>(_ value: T) throws -> NSObject {
return try self.box_(value) ?? NSDictionary()
}
// This method is called "box_" instead of "box" to disambiguate it from the overloads. Because the return type here is different from all of the "box" overloads (and is more general), any "box" calls in here would call back into "box" recursively instead of calling the appropriate overload, which is not what we want.
fileprivate func box_<T : Encodable>(_ value: T) throws -> NSObject? {
if T.self == Date.self || T.self == NSDate.self {
// Respect Date encoding strategy
return try self.box((value as! Date))
} else if T.self == Data.self || T.self == NSData.self {
// Respect Data encoding strategy
return try self.box((value as! Data))
} else if T.self == URL.self || T.self == NSURL.self {
// Encode URLs as single strings.
return self.box((value as! URL).absoluteString)
} else if T.self == Decimal.self || T.self == NSDecimalNumber.self {
// DictionarySerialization can natively handle NSDecimalNumber.
return (value as! NSDecimalNumber)
}
// The value should request a container from the _DictionaryEncoder.
let depth = self.storage.count
do {
try value.encode(to: self)
} catch {
// If the value pushed a container before throwing, pop it back off to restore state.
if self.storage.count > depth {
let _ = self.storage.popContainer()
}
throw error
}
// The top container should be a new container.
guard self.storage.count > depth else {
return nil
}
return self.storage.popContainer()
}
}
// MARK: - _DictionaryReferencingEncoder
/// _DictionaryReferencingEncoder is a special subclass of _DictionaryEncoder which has its own storage, but references the contents of a different encoder.
/// It's used in superEncoder(), which returns a new encoder for encoding a superclass -- the lifetime of the encoder should not escape the scope it's created in, but it doesn't necessarily know when it's done being used (to write to the original container).
fileprivate class _DictionaryReferencingEncoder : _DictionaryEncoder {
// MARK: Reference types.
/// The type of container we're referencing.
private enum Reference {
/// Referencing a specific index in an array container.
case array(NSMutableArray, Int)
/// Referencing a specific key in a dictionary container.
case dictionary(NSMutableDictionary, String)
}
// MARK: - Properties
/// The encoder we're referencing.
fileprivate let encoder: _DictionaryEncoder
/// The container reference itself.
private let reference: Reference
// MARK: - Initialization
/// Initializes `self` by referencing the given array container in the given encoder.
fileprivate init(referencing encoder: _DictionaryEncoder, at index: Int, wrapping array: NSMutableArray) {
self.encoder = encoder
self.reference = .array(array, index)
super.init(options: encoder.options, codingPath: encoder.codingPath)
self.codingPath.append(DictionaryCodingKey(index: index))
}
/// Initializes `self` by referencing the given dictionary container in the given encoder.
fileprivate init(referencing encoder: _DictionaryEncoder,
key: CodingKey, convertedKey: CodingKey, wrapping dictionary: NSMutableDictionary) {
self.encoder = encoder
self.reference = .dictionary(dictionary, convertedKey.stringValue)
super.init(options: encoder.options, codingPath: encoder.codingPath)
self.codingPath.append(key)
}
// MARK: - Coding Path Operations
fileprivate override var canEncodeNewValue: Bool {
// With a regular encoder, the storage and coding path grow together.
// A referencing encoder, however, inherits its parents coding path, as well as the key it was created for.
// We have to take this into account.
return self.storage.count == self.codingPath.count - self.encoder.codingPath.count - 1
}
// MARK: - Deinitialization
// Finalizes `self` by writing the contents of our storage to the referenced encoder's storage.
deinit {
let value: Any
switch self.storage.count {
case 0: value = NSDictionary()
case 1: value = self.storage.popContainer()
default: fatalError("Referencing encoder deallocated with multiple containers on stack.")
}
switch self.reference {
case .array(let array, let index):
array.insert(value, at: index)
case .dictionary(let dictionary, let key):
dictionary[NSString(string: key)] = value
}
}
}
@@ -1,82 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is largely a copy of code from Swift.org open source project's
// files JSONEncoder.swift and Codeable.swift.
//
// Unfortunately those files do not expose the internal _JSONEncoder and
// _JSONDecoder classes, which are in fact dictionary encoder/decoders and
// precisely what we want...
//
// The original code is copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
// Modifications and additional code here is copyright (c) 2018 Sam Deane, and
// is licensed under the same terms.
//
//===----------------------------------------------------------------------===//
import Foundation
//===----------------------------------------------------------------------===//
// Error Utilities
//===----------------------------------------------------------------------===//
internal extension EncodingError {
/// Returns a `.invalidValue` error describing the given invalid floating-point value.
///
///
/// - parameter value: The value that was invalid to encode.
/// - parameter path: The path of `CodingKey`s taken to encode this value.
/// - returns: An `EncodingError` with the appropriate path and debug description.
internal static func _invalidFloatingPointValue<T : FloatingPoint>(_ value: T, at codingPath: [CodingKey]) -> EncodingError {
let valueDescription: String
if value == T.infinity {
valueDescription = "\(T.self).infinity"
} else if value == -T.infinity {
valueDescription = "-\(T.self).infinity"
} else {
valueDescription = "\(T.self).nan"
}
let debugDescription = "Unable to encode \(valueDescription) directly in Dictionary. Use DictionaryEncoder.NonConformingFloatEncodingStrategy.convertToString to specify how the value should be encoded."
return .invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: debugDescription))
}
}
internal extension DecodingError {
/// Returns a `.typeMismatch` error describing the expected type.
///
/// - parameter path: The path of `CodingKey`s taken to decode a value of this type.
/// - parameter expectation: The type expected to be encountered.
/// - parameter reality: The value that was encountered instead of the expected type.
/// - returns: A `DecodingError` with the appropriate path and debug description.
internal static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Any) -> DecodingError {
let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead."
return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description))
}
/// Returns a description of the type of `value` appropriate for an error message.
///
/// - parameter value: The value whose type to describe.
/// - returns: A string describing `value`.
/// - precondition: `value` is one of the types below.
internal static func _typeDescription(of value: Any) -> String {
if value is NSNull {
return "a null value"
} else if value is NSNumber /* FIXM: If swift-corelibs-foundation isn't updated to use NSNumber, this check will be necessary: || value is Int || value is Double */ {
return "a number"
} else if value is String {
return "a string/data"
} else if value is [Any] {
return "an array"
} else if value is [String : Any] {
return "a dictionary"
} else {
return "\(type(of: value))"
}
}
}
@@ -1,87 +0,0 @@
import UIKit
open class CollectionDelegate: NSObject, UICollectionViewDelegate {
weak var scrollViewDelegate: UIScrollViewDelegate?
public weak var collectionView: UICollectionView? {
didSet {
collectionView?.delegate = self
}
}
public let collectionDriver: CollectionDriver
public init(driver: CollectionDriver) {
self.collectionDriver = driver
super.init()
}
public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard !collectionDriver.isShowingSkeleton else { return }
collectionDriver.item(at: indexPath).willDisplay(cell)
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
collectionDriver.item(at: indexPath).didSelect(cell)
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewDidScroll?(scrollView)
for section in 0..<collectionDriver.sections.count {
let indexPath = IndexPath(item: 0, section: section)
collectionDriver.supplement(ofKind: UICollectionElementKindSectionHeader, at: indexPath)?.didScroll(scrollView)
collectionDriver.supplement(ofKind: UICollectionElementKindSectionFooter, at: indexPath)?.didScroll(scrollView)
}
}
public func scrollViewDidZoom(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewDidZoom?(scrollView)
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewWillBeginDragging?(scrollView)
}
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
scrollViewDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
scrollViewDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
}
public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewWillBeginDecelerating?(scrollView)
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewDidEndDecelerating?(scrollView)
}
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewDidEndScrollingAnimation?(scrollView)
}
public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return scrollViewDelegate?.viewForZooming?(in: scrollView)
}
public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
scrollViewDelegate?.scrollViewWillBeginZooming?(scrollView, with: view)
}
public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
scrollViewDelegate?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale)
}
public func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
return scrollViewDelegate?.scrollViewShouldScrollToTop?(scrollView) ?? true
}
public func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewDidScrollToTop?(scrollView)
}
}
@@ -1,348 +0,0 @@
import UIKit
import SkeletonView
public class CollectionDriver {
public private(set) var mode = DisplayMode.normal
private var diffMode = DiffMode.immediate
var willPerformBatchUpdate: ((UICollectionView) -> Void)? = nil
var didPerformBatchUpdate: ((UICollectionView) -> Void)? = nil
public var collectionView: UICollectionView? {
didSet {
sections.attachTo(collectionView, driver: self)
if let collectionView = collectionView {
collectionView.dataSource = wrapper
collectionView.prefetchDataSource = wrapper
collectionView.reloadData()
}
}
}
public var sections: [CollectionSection] {
didSet {
oldValue.attachTo(nil)
sections.attachTo(collectionView, driver: self)
enqueueDiff {
let changes = diff(old: oldValue, new: self.sections)
return .sections(changes)
}
}
}
public var emptyView: UIView
var isShowingSkeleton: Bool {
guard let collectionView = collectionView else { return false }
return !(collectionView.dataSource is CollectionDataSourceWrapper)
}
private lazy var wrapper = CollectionDataSourceWrapper(collectionDriver: self)
// MARK: -
public init(sections: [CollectionSection] = [], emptyView: UIView = EmptyView.noData) {
self.sections = sections
self.emptyView = emptyView
}
// MARK: - Modes
public func setLoadingMode(_ loadingMode: DisplayMode.LoadingMode) {
self.mode = .loading(loadingMode)
guard let collectionView = collectionView else { return }
switch loadingMode {
case .refreshControl:
if let refreshControl = collectionView.refreshControl, !refreshControl.isRefreshing {
refreshControl.beginRefreshing()
}
case .skeleton:
hideError()
collectionView.showSkeleton(usingColor: UIColor.unselected)
}
}
public func refreshSkeleton() {
guard let collectionView = collectionView else { return }
collectionView.hideSkeleton()
collectionView.alpha = 0
delay(TimeInterval.oneFrame) {
collectionView.showSkeleton(usingColor: UIColor.unselected)
UIView.animate(withDuration: 0.2) {
collectionView.alpha = 1
}
}
}
public func setNormalMode(_ closure: @escaping () -> Void = {}) {
let oldMode = mode
mode = .normal
guard let collectionView = collectionView else { return }
hideError()
switch oldMode {
case .loading(.skeleton):
collectionView.hideSkeleton(reloadDataAfter: false)
// Disable diffing to avoid crashes and stuff
diffMode = .disabled
closure()
collectionView.reloadData()
DispatchQueue.main.async {
// Reload data completed so it's safe to enable diff again
self.diffMode = .immediate
}
case .loading(.refreshControl):
collectionView.refreshControl?.endRefreshing()
fallthrough
case .error, .normal:
DispatchQueue.main.async {
closure()
}
}
DispatchQueue.main.async {
collectionView.backgroundView = self.isEmpty ? self.emptyView : nil
}
}
public func setErrorMode(_ error: Error) {
let oldMode = mode
mode = .error(error)
guard let collectionView = collectionView else { return }
switch oldMode {
case .loading(.skeleton):
collectionView.hideSkeleton(reloadDataAfter: false)
case .loading(.refreshControl):
collectionView.refreshControl?.endRefreshing()
case .error, .normal:
break
}
if let errorView = collectionView.backgroundView as? ErrorView {
errorView.error = error
} else {
let errorView = ErrorView()
errorView.error = error
collectionView.backgroundView = errorView
}
}
private func hideError() {
collectionView?.backgroundView = nil
}
// MARK: - Diff
public func batchUpdate(_ closure: () -> Void) {
diffMode = .enqueue
closure()
if let collectionView = collectionView {
willPerformBatchUpdate?(collectionView)
collectionView.performBatchUpdates({
diffQueue.forEach { applyDiffResult($0) }
diffQueue = []
}, completion: { [weak self] _ in
guard let `self` = self, let collectionView = self.collectionView else { return }
self.didPerformBatchUpdate?(collectionView)
})
}
diffMode = .immediate
}
enum DiffResult {
case items([Change<CollectionItem>], section: Int)
case sections([Change<CollectionSection>])
}
private var diffQueue: [DiffResult] = []
func enqueueDiff(_ calculateDiff: @escaping () -> DiffResult) {
guard let collectionView = collectionView else { return }
switch diffMode {
case .immediate:
willPerformBatchUpdate?(collectionView)
collectionView.performBatchUpdates({
applyDiffResult( calculateDiff() )
}, completion: { [weak self] _ in
guard let `self` = self, let collectionView = self.collectionView else { return }
self.didPerformBatchUpdate?(collectionView)
})
case .enqueue:
diffQueue.append( calculateDiff() )
case .disabled:
break
}
}
private func applyDiffResult(_ result: DiffResult) {
guard let collectionView = collectionView else { return }
switch result {
case let .items(changes, section: sectionIndex):
collectionView.reload(changes: changes, section: sectionIndex, calledInsideBatch: true)
case let .sections(changes):
collectionView.reloadSections(changes: changes, calledInsideBatch: true)
}
}
// MARK: - Data
public var isEmpty: Bool {
return sections.reduce(0, { $0 + $1.items.count }) == 0
}
public func item(at indexPath: IndexPath) -> CollectionItem {
return sections[indexPath.section].items[indexPath.item]
}
public func safeItem(at indexPath: IndexPath) -> CollectionItem? {
guard indexPath.section < sections.endIndex,
indexPath.item < sections[indexPath.section].items.endIndex
else { return nil }
return sections[indexPath.section].items[indexPath.item]
}
public func supplement(ofKind: String, at indexPath: IndexPath) -> CollectionSupplement? {
return sections[indexPath.section].supplementaryItems[ofKind]?[indexPath.item]
}
}
// MARK: - Mode Definition
extension CollectionDriver {
public enum DisplayMode {
case normal
case loading(LoadingMode)
case error(Error)
public enum LoadingMode {
case refreshControl
case skeleton
}
}
enum DiffMode {
case immediate
case enqueue
case disabled
}
}
// MARK: - Wrapper
private final class CollectionDataSourceWrapper: NSObject, SkeletonCollectionViewDataSource, UICollectionViewDataSourcePrefetching {
unowned let collectionDriver: CollectionDriver
init(collectionDriver: CollectionDriver) {
self.collectionDriver = collectionDriver
}
// MARK: Data Source
func numberOfSections(in collectionView: UICollectionView) -> Int {
return collectionDriver.sections.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return collectionDriver.sections[section].items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionDriver.item(at: indexPath).dequeueCell()
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if let supplement = collectionDriver.supplement(ofKind: kind, at: indexPath) {
return supplement.dequeueView()
} else {
return UICollectionReusableView()
}
}
// MARK: Skeleton
func numSections(in collectionSkeletonView: UICollectionView) -> Int {
return collectionDriver.sections.count
}
func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier {
return collectionDriver.sections[indexPath.section].skeletonCellIdentifier.require(hint: "Specify skeleton cell")
}
func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = collectionDriver.sections[section].skeletonCellsCount {
return count
} else if let collection = collectionDriver.collectionView,
let flowlayout = collection.collectionViewLayout as? UICollectionViewFlowLayout {
let count = Int(ceil(collection.frame.height/flowlayout.itemSize.height))
return count
} else {
return 0
}
}
// MARK: Prefetch
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
guard !collectionDriver.isShowingSkeleton else { return }
indexPaths.forEach {
collectionDriver
.safeItem(at: $0)? // indexPaths contains non-existing items if updating
.prefetch()
}
}
func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
guard !collectionDriver.isShowingSkeleton else { return }
indexPaths.forEach {
collectionDriver
.safeItem(at: $0)?
.cancelPrefetch()
}
}
}
// MARK: - Private extensions
private extension Array where Element: CollectionSection {
func attachTo(_ collectionView: UICollectionView?, driver: CollectionDriver? = nil) {
if let collectionView = collectionView {
enumerated().forEach {
$1.attach(to: collectionView, at: $0)
$1.driver = driver
}
} else {
forEach { $0.detach() }
}
}
}
@@ -1,159 +0,0 @@
import UIKit
public class CollectionFlowDelegate: CollectionDelegate, UICollectionViewDelegateFlowLayout {
public var defaultLayout: Layout
private var layouts: [CollectionSection: Layout] = [:]
var flowLayout: UICollectionViewFlowLayout {
guard let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else {
fatalError("CollectionFlowDelegate can manage only collections with flow layout")
}
return layout
}
// MARK: - Init
public init(driver: CollectionDriver, defaultLayout: Layout = Layout()) {
self.defaultLayout = defaultLayout
super.init(driver: driver)
}
// MARK: - Section Layouts
public func layoutForSection(_ section: CollectionSection) -> Layout {
if let layout = layouts[section] {
return layout
}
layouts[section] = defaultLayout
return defaultLayout
}
func layoutForSection(_ index: Int) -> Layout {
return layoutForSection(collectionDriver.sections[index])
}
public func configureLayout(for section: CollectionSection, closure: (inout Layout) -> Void) {
layouts[section] = layoutForSection(section).with(closure)
}
// MARK: - Delegate
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let layout = layoutForSection(indexPath.section)
let constraint = layout.itemConstraints(layout: flowLayout)
if collectionDriver.isShowingSkeleton {
if let itemSizeFor = collectionDriver.sections[indexPath.section].skeletonSizeForConstraint {
let itemSize = itemSizeFor(constraint)
return itemSize
} else {
return CGSize.zero
}
} else {
let itemSize = collectionDriver.item(at: indexPath).sizeForConstraint(constraint)
return itemSize
}
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return layoutForSection(section).lineSpacing
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return layoutForSection(section).interitemSpacing
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return layoutForSection(section).insets
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let constraint = layoutForSection(section).supplementConstraints(layout: flowLayout)
return collectionDriver.sections[section].header?.sizeForConstraint(constraint) ?? .zero
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
let constraint = layoutForSection(section).supplementConstraints(layout: flowLayout)
return collectionDriver.sections[section].footer?.sizeForConstraint(constraint) ?? .zero
}
}
// MARK: - Layout
extension CollectionFlowDelegate {
public struct Layout: Then {
public var itemsPerRow: Int = 1
public var lineSpacing: CGFloat = 0
public var interitemSpacing: CGFloat = 0
public var insets: UIEdgeInsets = .zero
public init() {}
func supplementConstraints(layout: UICollectionViewFlowLayout) -> CGSize {
switch layout.scrollDirection {
case .vertical: return CGSize(layout.collectionViewWidth, 0)
case .horizontal: return CGSize(0, layout.collectionViewHeight)
}
}
func itemConstraints(layout: UICollectionViewFlowLayout) -> CGSize {
switch layout.scrollDirection {
case .vertical:
let width = (layout.collectionViewWidth - (
CGFloat(itemsPerRow - 1) * interitemSpacing
+ insets.left
+ insets.right
)) / CGFloat(itemsPerRow)
return CGSize(width, 0)
case .horizontal:
let height = (layout.collectionViewHeight - (
CGFloat(itemsPerRow - 1) * interitemSpacing
+ insets.top
+ insets.bottom
)) / CGFloat(itemsPerRow)
return CGSize(0, height)
}
}
}
}
// MARK: -
public extension CollectionSection {
public var header: CollectionSupplement? {
get {
return supplementaryItems[UICollectionElementKindSectionHeader]?.first
}
set {
supplementaryItems[UICollectionElementKindSectionHeader]
= newValue == nil ? [] : [newValue!]
}
}
public var footer: CollectionSupplement? {
get {
return supplementaryItems[UICollectionElementKindSectionFooter]?.first
}
set {
supplementaryItems[UICollectionElementKindSectionFooter]
= newValue == nil ? [] : [newValue!]
}
}
}
extension UICollectionViewFlowLayout {
var collectionViewWidth: CGFloat { return collectionView?.bounds.width ?? 0 }
var collectionViewHeight: CGFloat { return collectionView?.bounds.height ?? 0 }
}
@@ -1,94 +0,0 @@
import UIKit
public class CollectionItem: Hashable {
private(set) public var data: AnyHashable
private let cellClass: AnyClass
var indexPath: IndexPath?
weak var collectionView: UICollectionView?
// MARK: - Blocks
var dequeueCell: () -> UICollectionViewCell = { fatalError() }
var fillCell: () -> Void = {}
let prefetch: () -> Void
let cancelPrefetch: () -> Void
let willDisplay: (UICollectionViewCell) -> Void
let didSelect: (UICollectionViewCell) -> Void
var sizeForConstraint: (CGSize) -> CGSize = { _ in fatalError() }
public init<T: CollectionItemProtocol>(_ controller: T) {
cellClass = T.Cell.self
data = AnyHashable(controller.data)
willDisplay = {
if let cell = $0 as? T.Cell {
controller.cellWillDisplay(cell)
}
}
didSelect = {
if let cell = $0 as? T.Cell {
controller.cellSelected(cell)
}
}
prefetch = controller.prefetchContent
cancelPrefetch = controller.cancelPrefetchContent
dequeueCell = { [unowned self] in
guard let indexPath = self.indexPath,
let collectionView = self.collectionView
else { fatalError() }
let cell: T.Cell = collectionView.dequeueCell(for: indexPath)
controller.fillCell(cell, animated: false)
return cell
}
fillCell = { [weak self] in
DispatchQueue.main.async { [weak self] in
guard let `self` = self, let indexPath = self.indexPath, let collectionView = self.collectionView else { return }
if let cell = collectionView.cellForItem(at: indexPath) as? T.Cell {
controller.fillCell(cell, animated: true)
}
}
}
sizeForConstraint = {
return controller.size(constrainedTo: $0.width, height: $0.height)
}
controller.events.dataUpdated = { [unowned self] newData in
self.data = newData
self.fillCell()
}
controller.events.dataAndSizeUpdated = { [unowned self] newData in
self.data = newData
guard let indexPath = self.indexPath,
let collectionView = self.collectionView
else { return }
UIView.performWithoutAnimation {
collectionView.reloadItems(at: [ indexPath ])
}
}
controller.events.animatedSizeUpdated = { [unowned self] in
self.collectionView?.performBatchUpdates({}, completion: nil)
}
}
// MARK: - Hashable
public var hashValue: Int {
return data.hashValue
}
public static func == (lhs: CollectionItem, rhs: CollectionItem) -> Bool {
return lhs.cellClass == rhs.cellClass && lhs.data == rhs.data
}
}
@@ -1,50 +0,0 @@
import UIKit
public struct CollectionItemEvents<Data> {
public var dataUpdated: (Data) -> Void = { _ in }
public var dataAndSizeUpdated: (Data) -> Void = { _ in }
public var animatedSizeUpdated: () -> Void = { }
public init() { }
}
public protocol CollectionItemProtocol: class {
associatedtype Cell: UICollectionViewCell
associatedtype Data: Hashable
// Data-related
var data: Data { get }
// Cell-related
func fillCell(_ cell: Cell, animated: Bool)
func size(constrainedTo width: CGFloat, height: CGFloat) -> CGSize
func cellWillDisplay(_ cell: Cell)
func cellSelected(_ cell: Cell)
// Events to pass to collection driver
var events: CollectionItemEvents<Data> { get set }
// Prefetching
func prefetchContent()
func cancelPrefetchContent()
}
public extension CollectionItemProtocol {
static func registerReusableCell(in collectionView: UICollectionView) {
collectionView.registerCell(Cell.self)
}
var collectionItem: CollectionItem {
return CollectionItem(self)
}
func singleItemSection(_ identifier: String = ProcessInfo.processInfo.globallyUniqueString) -> CollectionSection {
return CollectionSection(identifier: identifier, items: [ self.collectionItem ])
}
// MARK: - Default implementations
func prefetchContent() { }
func cancelPrefetchContent() { }
func cellWillDisplay(_ cell: Cell) { }
func cellSelected(_ cell: Cell) { }
}
@@ -1,35 +0,0 @@
import UIKit
import VendefyService
final class SingleCellController<CollectionCell: UICollectionViewCell & Fillable, ItemData>: CollectionItemProtocol where CollectionCell.Data == ItemData {
typealias Cell = CollectionCell
typealias Data = ItemData
var cellSelected: ((Cell, Data) -> ())?
var cellConfigure: ((Cell) -> ())?
var data: ItemData
var events = CollectionItemEvents<Data>()
let height: CGFloat
init(data: ItemData, height: CGFloat) {
self.data = data
self.height = height
}
// MARK: CollectionItemProtocol
func fillCell(_ cell: CollectionCell, animated: Bool) {
cell.fill(data)
cellConfigure?(cell)
}
func size(constrainedTo width: CGFloat, height: CGFloat) -> CGSize {
return CGSize(width: width, height: self.height)
}
func cellSelected(_ cell: Cell) {
cellSelected?(cell, data)
}
}
@@ -1,118 +0,0 @@
import UIKit
import SkeletonView
public class CollectionSection: Hashable, Then {
private var sectionIndex: Int?
private weak var collectionView: UICollectionView?
weak var driver: CollectionDriver?
// MARK: - Data
public var items: [CollectionItem] {
didSet {
oldValue.attachTo(nil, sectionIndex: nil)
items.attachTo(collectionView, sectionIndex: sectionIndex)
if let collectionView = collectionView,
let sectionIndex = sectionIndex,
let driver = driver
{
driver.enqueueDiff {
// Call fill for visible cells so bindings are set because
// CollectionItem are always recreated.
collectionView.indexPathsForVisibleItems.forEach { indexPath in
guard indexPath.section == sectionIndex else { return }
if indexPath.item < self.items.endIndex {
self.items[indexPath.item].fillCell()
}
}
// Calculate
let changes = diff(old: oldValue, new: self.items)
return .items(changes, section: sectionIndex)
}
}
}
}
public var supplementaryItems: [String: [CollectionSupplement]] = [:] {
didSet {
supplementaryItems.values.forEach {
$0.attachTo(collectionView, sectionIndex: sectionIndex)
}
}
}
public let identifier: String
// MARK: - Skeleton
public var skeletonCellIdentifier: ReusableCellIdentifier?
public var skeletonSizeForConstraint: ((CGSize) -> CGSize)?
public var skeletonCellsCount: Int?
// MARK: - Init
public init(identifier: String = ProcessInfo.processInfo.globallyUniqueString, items: [CollectionItem] = []) {
self.identifier = identifier
self.items = items
}
// MARK: -
func attach(to collectionView: UICollectionView, at index: Int) {
self.collectionView = collectionView
self.sectionIndex = index
items.attachTo(collectionView, sectionIndex: index)
supplementaryItems.values.forEach {
$0.attachTo(collectionView, sectionIndex: index)
}
}
func detach() {
collectionView = nil
sectionIndex = nil
items.attachTo(nil, sectionIndex: nil)
supplementaryItems.values.forEach {
$0.attachTo(nil, sectionIndex: nil)
}
}
// MARK: - Hashable
public var hashValue: Int {
return identifier.hashValue
}
public static func == (lhs: CollectionSection, rhs: CollectionSection) -> Bool {
return lhs.identifier == rhs.identifier && lhs.items == rhs.items
}
}
// MARK: -
private extension Array where Element: CollectionItem {
func attachTo(_ collectionView: UICollectionView?, sectionIndex: Int?) {
enumerated().forEach { index, item in
item.collectionView = collectionView
item.indexPath = sectionIndex.map { IndexPath(item: index, section: $0) }
}
}
}
private extension Array where Element: CollectionSupplement {
func attachTo(_ collectionView: UICollectionView?, sectionIndex: Int?) {
enumerated().forEach { index, item in
item.collectionView = collectionView
item.indexPath = sectionIndex.map { IndexPath(item: index, section: $0) }
}
}
}
@@ -1,34 +0,0 @@
import UIKit
public class CollectionSupplement {
var indexPath: IndexPath?
weak var collectionView: UICollectionView?
// MARK: - Blocks
var sizeForConstraint: (CGSize) -> CGSize = { _ in fatalError() }
var dequeueView: () -> UICollectionReusableView = { fatalError() }
var didScroll: (UIScrollView) -> () = { _ in }
// MARK: -
public init<T: CollectionSupplementProtocol>(_ controller: T) {
sizeForConstraint = {
return controller.sizeForConstraint($0)
}
dequeueView = { [unowned self] in
guard let collectionView = self.collectionView,
let indexPath = self.indexPath
else { fatalError() }
let view: T.View = collectionView.dequeueSupplement(kind: T.kind, for: indexPath)
controller.fill(view)
return view
}
didScroll = { scrollView in controller.didScroll(scrollView: scrollView) }
}
}
@@ -1,22 +0,0 @@
import UIKit
public protocol CollectionSupplementProtocol {
associatedtype View: UICollectionReusableView
static var kind: String { get }
func sizeForConstraint(_ constraint: CGSize) -> CGSize
func fill(_ view: View)
func didScroll(scrollView: UIScrollView)
}
public extension CollectionSupplementProtocol {
static func registerReusable(in collectionView: UICollectionView) {
collectionView.registerSupplement(View.self, kind: kind)
}
var supplementaryItem: CollectionSupplement {
return CollectionSupplement(self)
}
// default implementation
func didScroll(scrollView: UIScrollView) { }
}
+2 -2
View File
@@ -2,8 +2,8 @@ import Foundation
import UIKit
public enum Haptic {
case impact(UIImpactFeedbackStyle)
case notification(UINotificationFeedbackType)
case impact(UIImpactFeedbackGenerator.FeedbackStyle)
case notification(UINotificationFeedbackGenerator.FeedbackType)
case selection
public func generate() {
+1 -1
View File
@@ -29,7 +29,7 @@ extension Hapticable where Self: UIButton {
set { setAssociatedObject(&hapticKey, newValue) }
}
public var hapticControlEvents: UIControlEvents? {
public var hapticControlEvents: UIControl.Event? {
get { return getAssociatedObject(&eventKey) }
set { setAssociatedObject(&eventKey, newValue) }
}
@@ -2,7 +2,7 @@ import UIKit
public extension UIView {
public var backgroundImage: UIImage? {
var backgroundImage: UIImage? {
get {
guard let obj = layer.contents else { return nil }
return UIImage(cgImage: obj as! CGImage)
@@ -21,8 +21,8 @@ public extension UIView {
public extension UIView {
public final func updateOpaque() {
if let color = backgroundColor, color.alphaValue == 1.0, alpha == 1.0 {
final func updateOpaque() {
if let color = backgroundColor, color.rgba.a == 1.0, alpha == 1.0 {
isOpaque = true
}
else {
@@ -1,23 +0,0 @@
import UIKit
public extension UIColor
{
public final var redValue: CGFloat { return rgba().r }
public final var greenValue: CGFloat { return rgba().g }
public final var blueValue: CGFloat { return rgba().b }
public final var alphaValue: CGFloat { return rgba().a }
private final func rgba() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat)
{
guard let components: [CGFloat] = cgColor.components else { return (0, 0, 0, 0) }
let numberOfComponents: Int = cgColor.numberOfComponents
switch numberOfComponents {
case 4:
return (components[0], components[1], components[2], components[3])
case 2:
return (components[0], components[0], components[0], components[1])
default:
return (0, 0, 0, 1)
}
}
}
@@ -2,12 +2,12 @@ import UIKit
public extension UIImage
{
public final var isOpaque: Bool {
final var isOpaque: Bool {
let alphaInfo = cgImage?.alphaInfo
return !(alphaInfo == .first || alphaInfo == .last || alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast)
}
public final func reSize(to size: CGSize) -> UIImage {
final func reSize(to size: CGSize) -> UIImage {
guard size.width > 0 && size.height > 0 else { return self }
UIGraphicsBeginImageContextWithOptions(size, isOpaque, 0.0)
@@ -18,7 +18,7 @@ public extension UIImage
return scaledImage
}
public final func reSize(toFit size: CGSize) -> UIImage {
final func reSize(toFit size: CGSize) -> UIImage {
guard size.width > 0 && size.height > 0 else { return self }
let imageAspectRatio = self.size.width / self.size.height
@@ -40,7 +40,7 @@ public extension UIImage
return scaledImage
}
public final func reSize(toFill size: CGSize) -> UIImage {
final func reSize(toFill size: CGSize) -> UIImage {
guard size.width > 0 && size.height > 0 else { return self }
let imageAspectRatio = self.size.width / self.size.height
@@ -62,7 +62,7 @@ public extension UIImage
return scaledImage
}
public final func rounded(withCornerRadius radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
final func rounded(withCornerRadius radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let scaledRadius = divideRadiusByImageScale ? radius / scale : radius
@@ -78,7 +78,7 @@ public extension UIImage
return roundedImage
}
public final func roundedIntoCircle() -> UIImage {
final func roundedIntoCircle() -> UIImage {
let radius = min(size.width, size.height) / 2.0
var squareImage = self
if size.width != size.height {
@@ -26,7 +26,7 @@ public class SwiftyImageView: UIView {
public extension SwiftyImageView {
public enum ImageTransition {
enum ImageTransition {
case noTransition
case crossDissolve(TimeInterval)
case curlDown(TimeInterval)
@@ -37,7 +37,7 @@ public extension SwiftyImageView {
case flipFromTop(TimeInterval)
case custom(
duration: TimeInterval,
animationOptions: UIViewAnimationOptions,
animationOptions: UIView.AnimationOptions,
animations: (SwiftyImageView, UIImage) -> Void,
completion: ((Bool) -> Void)?
)
@@ -67,10 +67,10 @@ public extension SwiftyImageView {
}
/// The animation options of the image transition.
public var animationOptions: UIViewAnimationOptions {
public var animationOptions: UIView.AnimationOptions {
switch self {
case .noTransition:
return UIViewAnimationOptions()
return UIView.AnimationOptions()
case .crossDissolve:
return .transitionCrossDissolve
case .curlDown:
@@ -109,7 +109,7 @@ public extension SwiftyImageView {
}
}
public final func transition(_ imageTransition: ImageTransition, with image: UIImage) {
final func transition(_ imageTransition: ImageTransition, with image: UIImage) {
UIView.transition(with: self, duration: imageTransition.duration, options: imageTransition.animationOptions, animations: { imageTransition.animations(self, image) }, completion: imageTransition.completion)
}
@@ -13,7 +13,7 @@ import UIKit
/// Extends UIBarButtonItem with signal for the action.
public extension UIBarButtonItem {
/// A signal that fires for each action event.
public var onAction: Signal<(Void)> {
var onAction: Signal<(Void)> {
return getOrCreateSignal();
}
+28 -18
View File
@@ -9,72 +9,77 @@ import UIKit
/// Extends UIControl with signals for all ui control events.
public extension UIControl {
/// A signal that fires for each touch down control event.
public var onTouchDown: Signal<(Void)> {
var onTouchDown: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDown);
}
/// A signal that fires for each touch down repeat control event.
public var onTouchDownRepeat: Signal<(Void)> {
var onTouchDownRepeat: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDownRepeat);
}
/// A signal that fires for each touch drag inside control event.
public var onTouchDragInside: Signal<(Void)> {
var onTouchDragInside: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDragInside);
}
/// A signal that fires for each touch drag outside control event.
public var onTouchDragOutside: Signal<(Void)> {
var onTouchDragOutside: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDragOutside);
}
/// A signal that fires for each touch drag enter control event.
public var onTouchDragEnter: Signal<(Void)> {
var onTouchDragEnter: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDragEnter);
}
/// A signal that fires for each touch drag exit control event.
public var onTouchDragExit: Signal<(Void)> {
var onTouchDragExit: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchDragExit);
}
/// A signal that fires for each touch up inside control event.
public var onTouchUpInside: Signal<(Void)> {
var onTouchUpInside: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchUpInside);
}
/// A signal that fires for each primary action control event.
var onPrimaryActionTriggered: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.primaryActionTriggered);
}
/// A signal that fires for each touch up outside control event.
public var onTouchUpOutside: Signal<(Void)> {
var onTouchUpOutside: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchUpOutside);
}
/// A signal that fires for each touch cancel control event.
public var onTouchCancel: Signal<(Void)> {
var onTouchCancel: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.touchCancel);
}
/// A signal that fires for each value changed control event.
public var onValueChanged: Signal<(Void)> {
var onValueChanged: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.valueChanged);
}
/// A signal that fires for each editing did begin control event.
public var onEditingDidBegin: Signal<(Void)> {
var onEditingDidBegin: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.editingDidBegin);
}
/// A signal that fires for each editing changed control event.
public var onEditingChanged: Signal<(Void)> {
var onEditingChanged: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.editingChanged);
}
/// A signal that fires for each editing did end control event.
public var onEditingDidEnd: Signal<(Void)> {
var onEditingDidEnd: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.editingDidEnd);
}
/// A signal that fires for each editing did end on exit control event.
public var onEditingDidEndOnExit: Signal<(Void)> {
var onEditingDidEndOnExit: Signal<(Void)> {
return getOrCreateSignalForUIControlEvent(.editingDidEndOnExit);
}
@@ -84,7 +89,7 @@ public extension UIControl {
static var SignalDictionaryKey = "signals_signalKey"
}
private static let eventToKey: [UIControlEvents: NSString] = [
private static let eventToKey: [UIControl.Event: NSString] = [
.touchDown: "TouchDown",
.touchDownRepeat: "TouchDownRepeat",
.touchDragInside: "TouchDragInside",
@@ -92,6 +97,7 @@ public extension UIControl {
.touchDragEnter: "TouchDragEnter",
.touchDragExit: "TouchDragExit",
.touchUpInside: "TouchUpInside",
.primaryActionTriggered: "PrimaryActionTriggered",
.touchUpOutside: "TouchUpOutside",
.touchCancel: "TouchCancel",
.valueChanged: "ValueChanged",
@@ -100,7 +106,7 @@ public extension UIControl {
.editingDidEnd: "EditingDidEnd",
.editingDidEndOnExit: "EditingDidEndOnExit"]
private func getOrCreateSignalForUIControlEvent(_ event: UIControlEvents) -> Signal<(Void)> {
private func getOrCreateSignalForUIControlEvent(_ event: UIControl.Event) -> Signal<(Void)> {
guard let key = UIControl.eventToKey[event] else {
assertionFailure("Event type is not handled")
return Signal()
@@ -117,7 +123,7 @@ public extension UIControl {
}
}
private func handleUIControlEvent(_ uiControlEvent: UIControlEvents) {
private func handleUIControlEvent(_ uiControlEvent: UIControl.Event) {
getOrCreateSignalForUIControlEvent(uiControlEvent).fire(())
}
@@ -148,6 +154,10 @@ public extension UIControl {
@objc private dynamic func eventHandlerTouchUpInside() {
handleUIControlEvent(.touchUpInside)
}
@objc private dynamic func eventHandlerPrimaryActionTriggered() {
handleUIControlEvent(.primaryActionTriggered)
}
@objc private dynamic func eventHandlerTouchUpOutside() {
handleUIControlEvent(.touchUpOutside)
@@ -178,7 +188,7 @@ public extension UIControl {
}
}
extension UIControlEvents: Hashable {
extension UIControl.Event: Hashable {
public var hashValue: Int {
return Int(self.rawValue)
}
@@ -73,39 +73,14 @@ extension Collection where Self.Index == Self.Indices.Iterator.Element {
}
}
public extension Collection {
/**
Creats a shuffled version of this array using the Fisher-Yates (fast and uniform) shuffle.
- returns: A shuffled version of this array.
*/
public func shuffled() -> [Iterator.Element] {
var list = Array(self)
list.shuffle()
return list
}
}
public extension MutableCollection where Index == Int {
/**
Shuffle the array using the Fisher-Yates (fast and uniform) shuffle. Mutating.
*/
public mutating func shuffle() {
// Empty and single-element collections don't shuffle.
guard count > 1 else { return }
for i in 0 ..< (count - 1) {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
self.swapAt(i, j)
}
}
/**
Returns a random element from the collection.
- returns: A random element from the collection.
*/
public func random() -> Iterator.Element {
func random() -> Iterator.Element {
let index = Int(arc4random_uniform(UInt32(count)))
return self[index]
}
+11 -12
View File
@@ -4,7 +4,7 @@ import CoreGraphics
public extension Int {
/// Returns a random Int point number between 0 and Int.max.
public static var random: Int {
static var random: Int {
return Int.random(n: Int.max)
}
@@ -12,7 +12,7 @@ public extension Int {
///
/// - Parameter n: Interval max
/// - Returns: Returns a random Int point number between 0 and n max
public static func random(n: Int) -> Int {
static func random(n: Int) -> Int {
return Int(arc4random_uniform(UInt32(n)))
}
@@ -22,9 +22,8 @@ public extension Int {
/// - min: Interval minimun
/// - max: Interval max
/// - Returns: Returns a random Int point number between 0 and n max
public static func random(min: Int, max: Int) -> Int {
static func random(min: Int, max: Int) -> Int {
return Int.random(n: max - min + 1) + min
}
}
@@ -33,7 +32,7 @@ public extension Int {
public extension Double {
/// Returns a random floating point number between 0.0 and 1.0, inclusive.
public static var random: Double {
static var random: Double {
return Double(arc4random()) / 0xFFFFFFFF
}
@@ -41,7 +40,7 @@ public extension Double {
///
/// - Parameter n: Interval max
/// - Returns: Returns a random double point number between 0 and n max
public static func random(min: Double, max: Double) -> Double {
static func random(min: Double, max: Double) -> Double {
return Double.random * (max - min) + min
}
}
@@ -51,15 +50,15 @@ public extension Double {
public extension Float {
/// Returns a random floating point number between 0.0 and 1.0, inclusive.
public static var random: Float {
return Float(arc4random()) / 0xFFFFFFFF
static var random: Float {
return Float(arc4random()) / 4294967296
}
/// Random float between 0 and n-1.
///
/// - Parameter n: Interval max
/// - Returns: Returns a random float point number between 0 and n max
public static func random(min: Float, max: Float) -> Float {
static func random(min: Float, max: Float) -> Float {
return Float.random * (max - min) + min
}
}
@@ -69,12 +68,12 @@ public extension Float {
public extension CGFloat {
/// Randomly returns either 1.0 or -1.0.
public static var randomSign: CGFloat {
static var randomSign: CGFloat {
return (arc4random_uniform(2) == 0) ? 1.0 : -1.0
}
/// Returns a random floating point number between 0.0 and 1.0, inclusive.
public static var random: CGFloat {
static var random: CGFloat {
return CGFloat(Float.random)
}
@@ -82,7 +81,7 @@ public extension CGFloat {
///
/// - Parameter n: Interval max
/// - Returns: Returns a random CGFloat point number between 0 and n max
public static func random(min: CGFloat, max: CGFloat) -> CGFloat {
static func random(min: CGFloat, max: CGFloat) -> CGFloat {
return CGFloat.random * (max - min) + min
}
}
@@ -25,7 +25,7 @@ extension String {
size = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
}
let attributes: [NSAttributedStringKey: Any] = [ NSAttributedStringKey.font: font ]
let attributes: [NSAttributedString.Key: Any] = [ NSAttributedString.Key.font: font ]
let result = (self as NSString).boundingRect(with: size,options: [ .usesLineFragmentOrigin ], attributes: attributes, context: nil).size
return result
@@ -0,0 +1,145 @@
import Foundation
import UIKit
protocol InteractiveDismiss: class {
var canBeginInteractiveDismiss: Bool { get }
var backgroundView: UIView { get }
var contentView: UIView { get }
func setupInteractiveDismission()
func viewDidCompleteInteractiveDismiss()
func viewPossiblyWillDissmiss()
}
private struct AssociatedKeys {
static var exceededThreshold: Int = 0
static var totalTranslation: Int = 1
static var gestureDelegate: Int = 2
}
extension InteractiveDismiss where Self: UIViewController {
private var exceededDismissionThreshold: Bool {
get { return getAssociatedObject(&AssociatedKeys.exceededThreshold) ?? false }
set { setAssociatedObject(&AssociatedKeys.exceededThreshold, newValue) }
}
private var totalTranslationY: CGFloat {
get { return getAssociatedObject(&AssociatedKeys.totalTranslation) ?? 0 }
set { setAssociatedObject(&AssociatedKeys.totalTranslation, newValue) }
}
private var gestureDelegate: GestureRecognizerDelegate? {
get { return getAssociatedObject(&AssociatedKeys.gestureDelegate) }
set { setAssociatedObject(&AssociatedKeys.gestureDelegate, newValue) }
}
func dismissAnimated() {
animateExit()
}
func setupInteractiveDismission() {
let panGesture = UIPanGestureRecognizer {[weak self] recognizer in
self?.didPanView(panGesture: recognizer as! UIPanGestureRecognizer)
}
panGesture.cancelsTouchesInView = false
let delegate = GestureRecognizerDelegate()
delegate.recognizerShouldBegin = { [unowned self] _ in return self.canBeginInteractiveDismiss }
panGesture.delegate = delegate
gestureDelegate = delegate
view.addGestureRecognizer(panGesture)
}
private func didPanView(panGesture: UIPanGestureRecognizer) {
// Update translation
let translation = panGesture.translation(in: view)
// Check which view should update
if contentView.frame.origin.y + translation.y > 0 {
// We are scrolling down
totalTranslationY += translation.y
// Check threshold
if totalTranslationY > Theme.scrollDownSlopThreshold {
contentView.frame.origin.y += translation.y
// Updated the view behind this with the proper index
// Prevents a jitter when exiting
if !exceededDismissionThreshold {
exceededDismissionThreshold = true
viewPossiblyWillDissmiss()
}
} else {
contentView.frame.origin.y = 0
}
// Perform changes
performTranslationBasedStyles()
}
// Handle extras
switch panGesture.state {
case .ended:
if totalTranslationY > Theme.scrollDownSlopThreshold {
// Determine velocity
let velocity = panGesture.velocity(in: view)
let projectedY = Inertia.project(initialVelocity: velocity.y)
let duration = TimeInterval(view.frame.size.height / velocity.y)
// Handle case
if projectedY >= Theme.flingToDismissVelocityThreshold {
animateExit(duration, projectedY)
} else if contentView.frame.origin.y >= view.frame.height * 0.4 {
animateExit()
} else {
let springVelocity = projectedY / view.frame.height
animateBack(intensity: springVelocity)
}
}
// Reset
totalTranslationY = 0
exceededDismissionThreshold = false
default: break
}
// Reset
panGesture.setTranslation(.zero, in: view)
}
private func animateExit(_ duration: TimeInterval = 0.33, _ projectedY: CGFloat = 0) {
UIView.animate(withDuration: duration, animations: {
self.contentView.frame.origin.y = self.view.frame.height + projectedY
self.backgroundView.alpha = 0
}) { _ in
self.viewDidCompleteInteractiveDismiss()
self.dismiss(animated: false, completion: nil)
}
}
private func animateBack(intensity: CGFloat) {
let velocity = intensity.limited(1, 1 + intensity)
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: velocity, options: .allowUserInteraction, animations: {
self.contentView.frame.origin.y = 0
self.backgroundView.alpha = 1
}, completion: nil)
}
// Changes styles based on the drag og the content view
private func performTranslationBasedStyles() {
let translation = 1 - (contentView.frame.origin.y / view.frame.height)
backgroundView.alpha = translation
}
}
// MARK: - Theme
private enum Theme {
static let scrollDownSlopThreshold: CGFloat = 36
static let flingToDismissVelocityThreshold: CGFloat = 150
}
@@ -88,7 +88,7 @@ public final class ScaleModalDismissAC: NSObject, UIViewControllerAnimatedTransi
}, completion: { _ in
fromVC.view.removeFromSuperview()
fromVC.removeFromParentViewController()
fromVC.removeFromParent()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
+2 -3
View File
@@ -5,9 +5,8 @@ public let none = None()
public struct None {}
extension None: Hashable {
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) { }
}
public func == (lhs: None, rhs: None) -> Bool { return true }
@@ -0,0 +1,74 @@
import Foundation
import UIKit
import UIKit.UIGestureRecognizerSubclass
protocol TouchInteraction: class {
func setupDefaultTouchInteraction()
}
extension UIView: TouchInteraction {}
extension TouchInteraction where Self: UIView {
func setupDefaultTouchInteraction() {
let recognizer = TouchInteractionRecognizer { recognizer in
switch recognizer.state {
case .began:
UIView.animate(withDuration: 0.1, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
self.alpha = 0.6
})
case .ended, .failed, .cancelled:
UIView.animate(withDuration: 0.175, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
self.transform = CGAffineTransform(scaleX: 1, y: 1)
self.alpha = 1
})
default: break
}
}
self.addGestureRecognizer(recognizer)
self.isUserInteractionEnabled = true
}
}
class TouchInteractionRecognizer: UIGestureRecognizer {
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
delaysTouchesBegan = false
delaysTouchesEnded = false
cancelsTouchesInView = false
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if touches.count != 1 {
state = .failed
}
state = .began
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
if touches.count != 1 {
state = .failed
}
state = .ended
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
state = .cancelled
}
override func reset() {
super.reset()
state = .possible
}
}
+4 -4
View File
@@ -25,25 +25,25 @@ extension UIBarButtonItem {
get { return objc_getAssociatedObject(self, &AssociatedKeys.ActionName) as? Action }
}
public convenience init(image: UIImage?, style: UIBarButtonItemStyle = .plain, action: @escaping () -> Void) {
public convenience init(image: UIImage?, style: UIBarButtonItem.Style = .plain, action: @escaping () -> Void) {
let handler = Action(action: action)
self.init(image: image, style: style, target: handler, action: #selector(Action.handleAction(_:)))
barButtonAction = handler
}
public convenience init(title: String?, style: UIBarButtonItemStyle = .plain, action: @escaping () -> Void) {
public convenience init(title: String?, style: UIBarButtonItem.Style = .plain, action: @escaping () -> Void) {
let handler = Action(action: action)
self.init(title: title, style: style, target: handler, action: #selector(Action.handleAction(_:)))
barButtonAction = handler
}
public convenience init(barButtonSystemItem: UIBarButtonSystemItem, action: @escaping () -> Void) {
public convenience init(barButtonSystemItem: UIBarButtonItem.SystemItem, action: @escaping () -> Void) {
let handler = Action(action: action)
self.init(barButtonSystemItem: barButtonSystemItem, target: handler, action: #selector(Action.handleAction(_:)))
barButtonAction = handler
}
public convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItemStyle, action: @escaping () -> Void) {
public convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItem.Style, action: @escaping () -> Void) {
let handler = Action(action: action)
self.init(image: image, landscapeImagePhone: landscapeImagePhone, style: style, target: handler, action: #selector(Action.handleAction(_:)))
barButtonAction = handler
@@ -0,0 +1,210 @@
import Foundation
import UIKit
public final class HorizontalAlignmentLayout: UICollectionViewFlowLayout {
// KVO
fileprivate struct KVO {
var keyPath: String
var options: NSKeyValueObservingOptions
var context: Int
static var contentOffset = KVO(
keyPath: #keyPath(UICollectionViewFlowLayout.collectionView.contentOffset),
options: .new,
context: 0
)
static var bounds = KVO(
keyPath: #keyPath(UICollectionViewFlowLayout.collectionView.bounds),
options: .new,
context: 1
)
}
public enum Alignment {
case left
case center
case right
}
// Properties
public var currentPage: Int?
public var currentPageDidChange: (Int) -> Void = { _ in }
let alignment: Alignment
var collectionWidth: CGFloat {
guard let collection = collectionView else { return 0 }
return collection.bounds.size.width - collection.contentInset.right - collection.contentInset.left
}
deinit {
removeObserver(self, forKeyPath: KVO.bounds.keyPath, context: &KVO.bounds.context)
}
public init(itemSize: CGSize, spacing: CGFloat, alignment: Alignment) {
self.alignment = alignment
super.init()
self.itemSize = itemSize
self.scrollDirection = .horizontal
self.minimumLineSpacing = spacing
addObserver(self,
forKeyPath: KVO.bounds.keyPath,
options: KVO.bounds.options,
context: &KVO.bounds.context)
addObserver(self,
forKeyPath: KVO.contentOffset.keyPath,
options: KVO.contentOffset.options,
context: &KVO.contentOffset.context)
}
public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
// MARK: override
extension HorizontalAlignmentLayout {
public override func prepare() {
switch alignment {
case .left:
sectionInset.left = 0
sectionInset.right = collectionWidth - itemSize.width
case .center:
let inset = (collectionWidth - itemSize.width) / 2
sectionInset.left = inset
sectionInset.right = inset
case .right:
sectionInset.right = 0
sectionInset.left = collectionWidth - itemSize.width
}
super.prepare()
}
public override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
return proposedContentOffset
}
let proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.width, height: collectionView.bounds.height)
guard let layoutAttributes = self.layoutAttributesForElements(in: proposedRect) else {
return proposedContentOffset
}
var candidateAttributes: UICollectionViewLayoutAttributes?
let proposedContentOffsetX: CGFloat
switch alignment {
case .left:
proposedContentOffsetX = proposedContentOffset.x + itemSize.width / 2
case .center:
proposedContentOffsetX = proposedContentOffset.x + collectionView.bounds.width / 2
case .right:
proposedContentOffsetX = proposedContentOffset.x + collectionView.bounds.width
}
for attributes in layoutAttributes {
guard attributes.representedElementCategory == .cell else { continue }
if candidateAttributes == nil {
candidateAttributes = attributes
continue
}
let attributePosition: CGFloat
let candidatePosition: CGFloat
switch alignment {
case .left:
attributePosition = attributes.frame.minX
candidatePosition = candidateAttributes!.frame.minX
case .center:
attributePosition = attributes.center.x
candidatePosition = candidateAttributes!.center.x
case .right:
attributePosition = attributes.frame.maxX
candidatePosition = candidateAttributes!.frame.maxX
}
if abs(attributePosition - proposedContentOffsetX) < abs(candidatePosition - proposedContentOffsetX) {
candidateAttributes = attributes
}
}
guard let aCandidateAttributes = candidateAttributes else {
return proposedContentOffset
}
var newOffsetX: CGFloat
switch alignment {
case .left:
newOffsetX = aCandidateAttributes.frame.minX - collectionView.contentInset.left
case .center:
newOffsetX = aCandidateAttributes.center.x - collectionView.bounds.size.width / 2
case .right:
newOffsetX = aCandidateAttributes.frame.minX - collectionView.bounds.size.width + itemSize.width
}
let offset = newOffsetX - collectionView.contentOffset.x
if (velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0) {
let pageWidth = itemSize.width + minimumLineSpacing
newOffsetX += velocity.x > 0 ? pageWidth : -pageWidth
}
return CGPoint(x: newOffsetX, y: proposedContentOffset.y)
}
public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
switch context {
case (&KVO.bounds.context)?:
// TODO: Current layout makes hack for first item to appear in the center.
// (method configure Insets). Settings insets shouldn't be done in invalidateLayout.
// Layout should handle appear state on it's own.
let oldValue = change?[.oldKey] as? NSValue
let newValue = change?[.newKey] as? NSValue
if oldValue?.cgRectValue != newValue?.cgRectValue {
invalidateLayout()
}
case (&KVO.contentOffset.context)?:
guard let collectionView = collectionView, collectionView.frame.size != CGSize.zero else {
return
}
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size)
let layoutAttributes = layoutAttributesForElements(in: visibleRect)
let center: CGFloat
switch alignment {
case .left:
let middle = collectionView.contentInset.left + itemSize.width / 2
center = collectionView.contentOffset.x + middle
case .center:
let middle = collectionView.frame.width / 2
center = collectionView.contentOffset.x + middle
case .right:
let middle = collectionView.frame.width - collectionView.contentInset.right - itemSize.width / 2
center = collectionView.contentOffset.x + middle
}
let closestAttribute = layoutAttributes?.sorted {
abs($0.center.x - center) < abs($1.center.x - center)
}.first
if let closestAttribute = closestAttribute, currentPage != closestAttribute.indexPath.row {
currentPage = closestAttribute.indexPath.row
if let currentPage = currentPage {
currentPageDidChange(currentPage)
}
}
default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
@@ -0,0 +1,42 @@
import UIKit
public class PaginationFlowLayout: UICollectionViewFlowLayout {
override public func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView,
let layoutAttributes: Array = layoutAttributesForElements(in: collectionView.bounds),
layoutAttributes.count != 0 else {
return proposedContentOffset
}
var firstAttribute: UICollectionViewLayoutAttributes = layoutAttributes[0]
for attribute: UICollectionViewLayoutAttributes in layoutAttributes {
guard attribute.representedElementCategory == .cell else { continue }
switch scrollDirection {
case .horizontal:
if((velocity.x > 0.0 && attribute.center.x > firstAttribute.center.x) ||
(velocity.x <= 0.0 && attribute.center.x < firstAttribute.center.x)) {
firstAttribute = attribute;
}
case .vertical:
if((velocity.y > 0.0 && attribute.center.y > firstAttribute.center.y) ||
(velocity.y <= 0.0 && attribute.center.y < firstAttribute.center.y)) {
firstAttribute = attribute;
}
@unknown default:
fatalError()
}
}
switch scrollDirection {
case .horizontal:
return CGPoint(x: firstAttribute.center.x - collectionView.bounds.size.width * 0.5, y: proposedContentOffset.y)
case .vertical:
return CGPoint(x: proposedContentOffset.x, y: firstAttribute.center.y - collectionView.bounds.size.height * 0.5)
@unknown default:
fatalError()
}
}
}
@@ -0,0 +1,43 @@
import Foundation
import UIKit
public final class PinnedHeaderFlowLayout: UICollectionViewFlowLayout {
private let header = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, with: IndexPath(row: 0, section: 0))
private var headerSize: CGSize = .zero
public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = self.collectionView else { return nil }
let attributes = super.layoutAttributesForElements(in: rect)
let headerAttributes = attributes?.filter { $0.isFirstHeader }.first
if let headerAttributes = headerAttributes {
headerSize = headerAttributes.size
}
if collectionView.contentOffset.y > 0 {
header.frame.origin.y = collectionView.contentOffset.y
} else {
header.frame.origin.y = 0
}
var newAttribures = attributes?.filter { !$0.isFirstHeader }
header.frame.size = headerSize
header.zIndex = Int.max
newAttribures?.append(header)
return newAttribures
}
}
private extension UICollectionViewLayoutAttributes {
var isFirstHeader: Bool {
guard let elementKind = representedElementKind else { return false }
return (elementKind == UICollectionView.elementKindSectionHeader && indexPath.section == 0)
}
}
@@ -0,0 +1,50 @@
import Foundation
import UIKit
final class SnappingFlowLayout: UICollectionViewFlowLayout {
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
}
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
switch scrollDirection {
case .horizontal:
let horizontalOffset = proposedContentOffset.x + collectionView.contentInset.left
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
layoutAttributesArray?.forEach { layoutAttributes in
let itemOffset = layoutAttributes.frame.origin.x
if fabsf(Float(itemOffset - horizontalOffset)) < fabsf(Float(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset
}
}
let result = CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
return result
case .vertical:
let verticalOffset = proposedContentOffset.y + collectionView.contentInset.top
let targetRect = CGRect(x: 0, y: proposedContentOffset.y, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
layoutAttributesArray?.forEach { layoutAttributes in
let itemOffset = layoutAttributes.frame.origin.y
if fabsf(Float(itemOffset - verticalOffset)) < fabsf(Float(offsetAdjustment)) {
offsetAdjustment = itemOffset - verticalOffset
}
}
let result = CGPoint(x: proposedContentOffset.x, y: proposedContentOffset.y + offsetAdjustment)
return result
@unknown default:
fatalError()
}
}
}
@@ -0,0 +1,210 @@
import Foundation
import UIKit
public final class VerticalAlignmentLayout: UICollectionViewFlowLayout {
// KVO
fileprivate struct KVO {
var keyPath: String
var options: NSKeyValueObservingOptions
var context: Int
static var contentOffset = KVO(
keyPath: #keyPath(UICollectionViewFlowLayout.collectionView.contentOffset),
options: .new,
context: 0
)
static var bounds = KVO(
keyPath: #keyPath(UICollectionViewFlowLayout.collectionView.bounds),
options: .new,
context: 1
)
}
public enum Alignment {
case top
case center
case bottom
}
// Properties
public var currentPage: Int?
public var currentPageDidChange: (Int) -> Void = { _ in }
let alignment: Alignment
var collectionHeight: CGFloat {
guard let collection = collectionView else { return 0 }
return collection.bounds.size.height - collection.contentInset.top - collection.contentInset.bottom
}
deinit {
removeObserver(self, forKeyPath: KVO.bounds.keyPath, context: &KVO.bounds.context)
}
public init(itemSize: CGSize, spacing: CGFloat, alignment: Alignment) {
self.alignment = alignment
super.init()
self.itemSize = itemSize
self.scrollDirection = .vertical
self.minimumLineSpacing = spacing
addObserver(self,
forKeyPath: KVO.bounds.keyPath,
options: KVO.bounds.options,
context: &KVO.bounds.context)
addObserver(self,
forKeyPath: KVO.contentOffset.keyPath,
options: KVO.contentOffset.options,
context: &KVO.contentOffset.context)
}
public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
// MARK: override
extension VerticalAlignmentLayout {
public override func prepare() {
switch alignment {
case .top:
sectionInset.top = 0
sectionInset.bottom = collectionHeight - itemSize.height
case .center:
let inset = (collectionHeight - itemSize.height) / 2
sectionInset.top = inset
sectionInset.bottom = inset
case .bottom:
sectionInset.top = collectionHeight - itemSize.height
sectionInset.bottom = 0
}
super.prepare()
}
public override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
return proposedContentOffset
}
let proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.width, height: collectionView.bounds.height)
guard let layoutAttributes = self.layoutAttributesForElements(in: proposedRect) else {
return proposedContentOffset
}
var candidateAttributes: UICollectionViewLayoutAttributes?
let proposedContentOffsetY: CGFloat
switch alignment {
case .top:
proposedContentOffsetY = proposedContentOffset.y + itemSize.height / 2
case .center:
proposedContentOffsetY = proposedContentOffset.y + collectionView.bounds.height / 2
case .bottom:
proposedContentOffsetY = proposedContentOffset.y + collectionView.bounds.height
}
for attributes in layoutAttributes {
guard attributes.representedElementCategory == .cell else { continue }
if candidateAttributes == nil {
candidateAttributes = attributes
continue
}
let attributePosition: CGFloat
let candidatePosition: CGFloat
switch alignment {
case .top:
attributePosition = attributes.frame.minY
candidatePosition = candidateAttributes!.frame.minY
case .center:
attributePosition = attributes.center.y
candidatePosition = candidateAttributes!.center.y
case .bottom:
attributePosition = attributes.frame.maxY
candidatePosition = candidateAttributes!.frame.maxY
}
if abs(attributePosition - proposedContentOffsetY) < abs(candidatePosition - proposedContentOffsetY) {
candidateAttributes = attributes
}
}
guard let aCandidateAttributes = candidateAttributes else {
return proposedContentOffset
}
var newOffsetY: CGFloat
switch alignment {
case .top:
newOffsetY = aCandidateAttributes.frame.minY - collectionView.contentInset.top
case .center:
newOffsetY = aCandidateAttributes.center.y - collectionView.bounds.size.height / 2
case .bottom:
newOffsetY = aCandidateAttributes.frame.minY - collectionView.bounds.size.height + itemSize.width
}
let offset = newOffsetY - collectionView.contentOffset.y
if (velocity.y < 0 && offset > 0) || (velocity.y > 0 && offset < 0) {
let pageHeight = itemSize.height + minimumLineSpacing
newOffsetY += velocity.y > 0 ? pageHeight : -pageHeight
}
return CGPoint(x: proposedContentOffset.x, y: newOffsetY)
}
public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
switch context {
case (&KVO.bounds.context)?:
// TODO: Current layout makes hack for first item to appear in the center.
// (method configure Insets). Settings insets shouldn't be done in invalidateLayout.
// Layout should handle appear state on it's own.
let oldValue = change?[.oldKey] as? NSValue
let newValue = change?[.newKey] as? NSValue
if oldValue?.cgRectValue != newValue?.cgRectValue {
invalidateLayout()
}
case (&KVO.contentOffset.context)?:
guard let collectionView = collectionView, collectionView.frame.size != CGSize.zero else {
return
}
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size)
let layoutAttributes = layoutAttributesForElements(in: visibleRect)
let center: CGFloat
switch alignment {
case .top:
let middle = collectionView.contentInset.top + itemSize.height / 2
center = collectionView.contentOffset.y + middle
case .center:
let middle = collectionView.frame.height / 2
center = collectionView.contentOffset.y + middle
case .bottom:
let middle = collectionView.frame.height - collectionView.contentInset.bottom - itemSize.height / 2
center = collectionView.contentOffset.y + middle
}
let closestAttribute = layoutAttributes?.sorted {
abs($0.center.y - center) < abs($1.center.y - center)
}.first
if let closestAttribute = closestAttribute, currentPage != closestAttribute.indexPath.row {
currentPage = closestAttribute.indexPath.row
if let currentPage = currentPage {
currentPageDidChange(currentPage)
}
}
default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
+57 -48
View File
@@ -2,60 +2,69 @@ import Foundation
import UIKit
public struct DisplaySize {
public static let iphone4 = CGSize(width: 320, height: 480)
public static let iphone5 = CGSize(width: 320, height: 568)
public static let iphone6 = CGSize(width: 375, height: 667)
public static let iphone6plus = CGSize(width: 414, height: 736)
public static let iphoneX = CGSize(width: 375, height: 812)
public static let iPad9 = CGSize(width: 768, height: 1024)
public static let ipad10 = CGSize(width: 834, height: 1112)
public static let ipad12 = CGSize(width: 1024, height: 1366)
public static let unknown = CGSize.zero
public static let iphone4 = CGSize(width: 320, height: 480)
public static let iphone5 = CGSize(width: 320, height: 568)
public static let iphone6 = CGSize(width: 375, height: 667)
public static let iphone6plus = CGSize(width: 414, height: 736)
public static let iphoneX = CGSize(width: 375, height: 812)
public static let iphoneXR = CGSize(width: 414, height: 896)
public static let iPad9 = CGSize(width: 768, height: 1024)
public static let ipad10 = CGSize(width: 834, height: 1112)
public static let ipad12 = CGSize(width: 1024, height: 1366)
public static let unknown = CGSize.zero
}
public enum DisplayType {
case unknown
case iphone4
case iphone5
case iphone6
case iphone6plus
case iphoneX
case ipad9
case ipad10
case ipad12
case unknown
case iphone4
case iphone5
case iphone6
case iphone6plus
case iphoneX
case iphoneXR
case iphoneXM
case ipad9
case ipad10
case ipad12
}
public enum Display {
public static var width : CGFloat { return UIScreen.main.bounds.size.width }
public static var height : CGFloat { return UIScreen.main.bounds.size.height }
public static var maxLength : CGFloat { return max(width, height) }
public static var minLength : CGFloat { return min(width, height) }
public static var navbarSize : CGFloat { return Display.height == 812 ? 88 : 64 }
public static var bottombarSize : CGFloat { return Display.height == 812 ? 34 : 0 }
public static var zoomed : Bool { return UIScreen.main.nativeScale >= UIScreen.main.scale }
public static var retina : Bool { return UIScreen.main.scale >= 2.0 }
public static var phone : Bool { return UIDevice.current.userInterfaceIdiom == .phone }
public static var pad : Bool { return UIDevice.current.userInterfaceIdiom == .pad }
public static var displayType: DisplayType {
if phone && maxLength < 568 {
return .iphone4
} else if phone && maxLength == 568 {
return .iphone5
} else if phone && maxLength == 667 {
return .iphone6
} else if phone && maxLength == 736 {
return .iphone6plus
} else if phone && maxLength == 812 {
return .iphoneX
} else if pad && maxLength == 1024 {
return .ipad9
} else if pad && maxLength == 1112 {
return .ipad10
} else if pad && maxLength == 1366 {
return .ipad12
public static var width : CGFloat { return UIScreen.main.bounds.size.width }
public static var height : CGFloat { return UIScreen.main.bounds.size.height }
public static var maxLength : CGFloat { return max(width, height) }
public static var minLength : CGFloat { return min(width, height) }
public static var zoomed : Bool { return UIScreen.main.nativeScale >= UIScreen.main.scale }
public static var retina : Bool { return UIScreen.main.scale >= 2.0 }
public static var phone : Bool { return UIDevice.current.userInterfaceIdiom == .phone }
public static var pad : Bool { return UIDevice.current.userInterfaceIdiom == .pad }
public static var navbarSize : CGFloat {
return (Display.height == 812 || Display.height == 896) ? 88 : 64
}
public static var bottombarSize : CGFloat {
return (Display.height == 812 || Display.height == 896) ? 34 : 0
}
return .unknown
}
public static var displayType: DisplayType {
if phone && maxLength < 568 {
return .iphone4
} else if phone && maxLength == 568 {
return .iphone5
} else if phone && maxLength == 667 {
return .iphone6
} else if phone && maxLength == 736 {
return .iphone6plus
} else if phone && maxLength == 896 {
return .iphoneXR
} else if phone && maxLength == 812 {
return .iphoneX
} else if pad && maxLength == 1024 {
return .ipad9
} else if pad && maxLength == 1112 {
return .ipad10
} else if pad && maxLength == 1366 {
return .ipad12
}
return .unknown
}
}
@@ -3,7 +3,7 @@ import UIKit
public extension CALayer {
@discardableResult
public func add(to superlayer: CALayer) -> Self {
func add(to superlayer: CALayer) -> Self {
superlayer.addSublayer(self)
return self
}
@@ -11,7 +11,7 @@ public extension CALayer {
public extension CATransaction {
public static func withoutActions(_ block: () -> Void) {
static func withoutActions(_ block: () -> Void) {
begin()
setDisableActions(true)
block()
@@ -201,10 +201,10 @@ extension CATransition {
case fade, moveIn, push, reveal
var value: String {
switch self {
case .fade: return kCATransitionFade
case .moveIn: return kCATransitionMoveIn
case .push: return kCATransitionPush
case .reveal: return kCATransitionReveal
case .fade: return convertFromCATransitionType(CATransitionType.fade)
case .moveIn: return convertFromCATransitionType(CATransitionType.moveIn)
case .push: return convertFromCATransitionType(CATransitionType.push)
case .reveal: return convertFromCATransitionType(CATransitionType.reveal)
}
}
}
@@ -213,19 +213,19 @@ extension CATransition {
case fromRight, fromLeft, fromTop, fromBottom
var value: String {
switch self {
case .fromRight: return kCATransitionFromRight
case .fromLeft: return kCATransitionFromLeft
case .fromTop: return kCATransitionFromTop
case .fromBottom: return kCATransitionFromBottom
case .fromRight: return convertFromCATransitionSubtype(CATransitionSubtype.fromRight)
case .fromLeft: return convertFromCATransitionSubtype(CATransitionSubtype.fromLeft)
case .fromTop: return convertFromCATransitionSubtype(CATransitionSubtype.fromTop)
case .fromBottom: return convertFromCATransitionSubtype(CATransitionSubtype.fromBottom)
}
}
}
public convenience init(type: Kind, subtype: Subkind? = nil) {
self.init()
self.type = type.value
self.type = convertToCATransitionType(type.value)
if let subtype = subtype {
self.subtype = subtype.value
self.subtype = convertToOptionalCATransitionSubtype(subtype.value)
}
}
@@ -247,3 +247,24 @@ extension CATransaction {
commit()
}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertFromCATransitionType(_ input: CATransitionType) -> String {
return input.rawValue
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertFromCATransitionSubtype(_ input: CATransitionSubtype) -> String {
return input.rawValue
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToCATransitionType(_ input: String) -> CATransitionType {
return CATransitionType(rawValue: input)
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToOptionalCATransitionSubtype(_ input: String?) -> CATransitionSubtype? {
guard let input = input else { return nil }
return CATransitionSubtype(rawValue: input)
}
@@ -1,11 +1,3 @@
//
// CoreGraphics+ext.swift
// Vendefy
//
// Created by Dmitriy Kalachev on 4/8/18.
// Copyright © 2018 Ramotion. All rights reserved.
//
import UIKit
public extension CGSize {
@@ -14,13 +6,13 @@ public extension CGSize {
self.init(width: value, height: value)
}
init(_ width: CGFloat, _ height: CGFloat) {
self.init(width: width, height: height)
}
var asRect: CGRect {
return CGRect(size: self)
}
init(_ width: CGFloat, _ height: CGFloat) {
self.init(width: width, height: height)
}
func centered(in rect: CGRect) -> CGRect {
var result = self.asRect
@@ -29,14 +21,29 @@ public extension CGSize {
return result
}
func centered(in size: CGSize) -> CGRect {
return centered(in: CGRect(origin: .zero, size: size))
}
var asPixelsForMainScreen: CGSize {
return self * UIScreen.main.scale
}
var center: CGPoint {
return CGPoint(x: width / 2, y: height / 2)
}
static var greatestFiniteMagnitude: CGSize {
return CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
}
var area: CGFloat {
return width * height
}
}
public extension UIEdgeInsets {
public init(value: CGFloat) {
init(value: CGFloat) {
self.init(top: value, left: value, bottom: value, right: value)
}
}
@@ -59,6 +66,13 @@ public extension CGRect {
return CGPoint(x: midX, y: midY)
}
func insetBy(insets: UIEdgeInsets) -> CGRect {
let x = origin.x + insets.left
let y = origin.y + insets.top
let w = size.width - insets.left - insets.right
let h = size.height - insets.top - insets.bottom
return CGRect(x: x, y: y, width: w, height: h)
}
}
public extension CGAffineTransform {
@@ -66,7 +80,6 @@ public extension CGAffineTransform {
init(scale: CGFloat) {
self.init(scaleX: scale, y: scale)
}
}
public func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
@@ -13,7 +13,7 @@ extension UIAlertController {
- returns: self
*/
@discardableResult
public func addAction(title: String?, style: UIAlertActionStyle = .default, handler: ((UIAlertAction) -> Void)? = nil) -> Self {
public func addAction(title: String?, style: UIAlertAction.Style = .default, handler: ((UIAlertAction) -> Void)? = nil) -> Self {
addAction(UIAlertAction(title: title, style: style, handler: handler))
return self
}
@@ -9,7 +9,7 @@ extension UIButton {
- parameter color: The background color to use for the specified state.
- parameter state: The state that uses the specified image.
*/
public func setBackgroundColor(_ color: UIColor, for state: UIControlState) {
public func setBackgroundColor(_ color: UIColor, for state: UIControl.State) {
setBackgroundImage(UIImage(color: color), for: state)
}
}
@@ -7,7 +7,7 @@ public extension UIButton {
/// - bottom: title below button image
/// - left: title to the left of button image
/// - right: title to the right of button image
public enum Position: Int {
enum Position: Int {
case top, bottom, left, right
}
@@ -20,7 +20,7 @@ public extension UIButton {
/// - titlePosition: UIViewContentModeTop, UIViewContentModeBottom, UIViewContentModeLeft or UIViewContentModeRight
/// - additionalSpacing: Spacing between image and title
/// - state: State to apply this behaviour
public func set(image: UIImage?, title: String, titlePosition: Position, spacing: CGFloat, state: UIControlState){
func set(image: UIImage?, title: String, titlePosition: Position, spacing: CGFloat, state: UIControl.State){
imageView?.contentMode = .center
setImage(image, for: state)
setTitle(title, for: state)
@@ -39,7 +39,7 @@ public extension UIButton {
/// - titlePosition: UIViewContentModeTop, UIViewContentModeBottom, UIViewContentModeLeft or UIViewContentModeRight
/// - additionalSpacing: Spacing between image and title
/// - state: State to apply this behaviour
public func set(image: UIImage?, attributedTitle title: NSAttributedString, titlePosition: Position, spacing: CGFloat, state: UIControlState){
func set(image: UIImage?, attributedTitle title: NSAttributedString, titlePosition: Position, spacing: CGFloat, state: UIControl.State){
imageView?.contentMode = .center
setImage(image, for: state)
@@ -57,7 +57,7 @@ public extension UIButton {
// Use predefined font, otherwise use the default
let titleFont: UIFont = titleLabel?.font ?? UIFont()
let titleSize: CGSize = title.size(withAttributes: [NSAttributedStringKey.font: titleFont])
let titleSize: CGSize = title.size(withAttributes: [NSAttributedString.Key.font: titleFont])
arrange(titleSize: titleSize, imageRect: imageRect, atPosition: position, spacing: spacing)
}
@@ -44,7 +44,7 @@ public extension UICollectionView {
_ type: T.Type,
withIdentifier reuseIdentifier: String = T.reuseIdentifier)
{
registerSupplement(T.self, kind: UICollectionElementKindSectionHeader)
registerSupplement(T.self, kind: UICollectionView.elementKindSectionHeader)
}
func dequeueHeader<T: UICollectionReusableView>(
@@ -52,14 +52,14 @@ public extension UICollectionView {
withIdentifier reuseIdentifier: String = T.reuseIdentifier,
for indexPath: IndexPath) -> T
{
return dequeueSupplement(kind: UICollectionElementKindSectionHeader, for: indexPath)
return dequeueSupplement(kind: UICollectionView.elementKindSectionHeader, for: indexPath)
}
func registerFooter<T: UICollectionReusableView>(
_ type: T.Type,
withIdentifier reuseIdentifier: String = T.reuseIdentifier)
{
registerSupplement(T.self, kind: UICollectionElementKindSectionFooter)
registerSupplement(T.self, kind: UICollectionView.elementKindSectionFooter)
}
func dequeueFooter<T: UICollectionReusableView>(
@@ -67,7 +67,7 @@ public extension UICollectionView {
withIdentifier reuseIdentifier: String = T.reuseIdentifier,
for indexPath: IndexPath) -> T
{
return dequeueSupplement(kind: UICollectionElementKindSectionFooter, for: indexPath)
return dequeueSupplement(kind: UICollectionView.elementKindSectionFooter, for: indexPath)
}
}
@@ -43,7 +43,7 @@ extension UIView {
case tap
case longPress
case pan
case swipe(UISwipeGestureRecognizerDirection)
case swipe(UISwipeGestureRecognizer.Direction)
case tapCount(Int)
}
@@ -106,7 +106,7 @@ extension UIImage {
- parameter orientiation: The orientation to use for the scaled image (optional, defaults to the image's `imageOrientation` property).
- returns: A copy of self, scaled by the scaleFactor (with an optional image orientation).
*/
public func scaled(by scaleFactor: CGFloat, withOrientation orientation: UIImageOrientation? = nil) -> UIImage? {
public func scaled(by scaleFactor: CGFloat, withOrientation orientation: UIImage.Orientation? = nil) -> UIImage? {
guard let coreImage = cgImage else { return nil }
return UIImage(cgImage: coreImage, scale: 1/scaleFactor, orientation: orientation ?? imageOrientation)
@@ -15,7 +15,7 @@ public extension UINavigationController {
}
func replace(_ vc: UIViewController, with replacementVC: UIViewController, animated: Bool = true) {
guard let index = self.viewControllers.index(of: vc)
guard let index = self.viewControllers.firstIndex(of: vc)
else { return }
var viewControllers = self.viewControllers
@@ -4,11 +4,8 @@ extension UIScrollView {
func scrollToBottom() {
layoutIfNeeded()
let minimumYOffset = -max(Display.navbarSize, contentInset.top)
contentOffset = CGPoint(x: 0,
y: max(minimumYOffset, bounds.minY + contentSize.height + contentInset.top - bounds.height))
let y = max(minimumYOffset, bounds.minY + contentSize.height + contentInset.top - bounds.height)
contentOffset = CGPoint(x: 0, y: y)
}
}
@@ -3,31 +3,31 @@ import UIKit
public extension UIView {
@discardableResult
public func add(to superview: UIView) -> Self {
func add(to superview: UIView) -> Self {
superview.addSubview(self)
return self
}
@discardableResult
public func insert(to superview: UIView, at index: Int) -> Self {
func insert(to superview: UIView, at index: Int) -> Self {
superview.insertSubview(self, at: index)
return self
}
@discardableResult
public func insert(to superview: UIView, above view: UIView) -> Self {
func insert(to superview: UIView, above view: UIView) -> Self {
superview.insertSubview(self, aboveSubview: view)
return self
}
@discardableResult
public func insert(to superview: UIView, below view: UIView) -> Self {
func insert(to superview: UIView, below view: UIView) -> Self {
superview.insertSubview(self, belowSubview: view)
return self
}
@discardableResult
public func add(to stackview: UIStackView) -> Self {
func add(to stackview: UIStackView) -> Self {
stackview.addArrangedSubview(self)
return self
}
@@ -56,9 +56,9 @@ extension UIViewController {
extension UIViewController {
public func add(_ child: UIViewController) {
addChildViewController(child)
addChild(child)
view.addSubview(child.view)
child.didMove(toParentViewController: self)
child.didMove(toParent: self)
}
public func remove() {
@@ -66,8 +66,8 @@ extension UIViewController {
return
}
willMove(toParentViewController: nil)
removeFromParentViewController()
willMove(toParent: nil)
removeFromParent()
view.removeFromSuperview()
}
}
@@ -16,7 +16,7 @@ extension UIWindow {
//mpdal controller have strong reference to it presenting controller, to correct memory management we must dismiss it before replace parent controller.
previous?.dismiss(animated: false, completion: nil)
previous?.view.removeFromSuperview()
previous?.removeFromParentViewController()
previous?.removeFromParent()
}
guard let snapShot: UIView = subviews.last?.snapshotView() else {
@@ -0,0 +1,35 @@
import Foundation
import UIKit
class GestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate {
var recognizerShouldBegin: ((UIGestureRecognizer) -> Bool)? = nil
var shouldRecognizeSimultaneously: ((UIGestureRecognizer, UIGestureRecognizer) -> Bool)? = nil
var shouldRequireFailureOf: ((UIGestureRecognizer, UIGestureRecognizer) -> Bool)? = nil
var shouldRequireFailureBy: ((UIGestureRecognizer, UIGestureRecognizer) -> Bool)? = nil
var shouldReceiveTouch: ((UIGestureRecognizer, UITouch) -> Bool)? = nil
var shouldReceivePress: ((UIGestureRecognizer, UIPress) -> Bool)? = nil
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return recognizerShouldBegin?(gestureRecognizer) ?? true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return shouldRecognizeSimultaneously?(gestureRecognizer, otherGestureRecognizer) ?? true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return shouldRequireFailureOf?(gestureRecognizer, otherGestureRecognizer) ?? true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return shouldRequireFailureBy?(gestureRecognizer, otherGestureRecognizer) ?? false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return shouldReceiveTouch?(gestureRecognizer, touch) ?? true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive press: UIPress) -> Bool {
return shouldReceivePress?(gestureRecognizer, press) ?? true
}
}
@@ -32,22 +32,22 @@ public class InputVisibilityController: NSObject {
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillAppear),
name: .UIKeyboardWillShow,
name: UIResponder.keyboardWillShowNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillDisappear),
name: .UIKeyboardWillHide,
name: UIResponder.keyboardWillHideNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(applicationWillResign),
name: .UIApplicationWillResignActive,
name: UIApplication.willResignActiveNotification,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(keyboardWillChangeFrame),
name: .UIKeyboardWillChangeFrame,
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil)
}
@@ -81,7 +81,7 @@ public class InputVisibilityController: NSObject {
}
@objc func keyboardWillChangeFrame(notification: Notification) {
let keyboardEndFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let keyboardEndFrame = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let goingUp = (keyboardEndFrame?.origin.y ?? 0) < UIScreen.main.bounds.height
self.moveViewUp(up: goingUp, usingKeyboardNotification: notification)
}
@@ -92,7 +92,7 @@ public class InputVisibilityController: NSObject {
}
let userInfo = notification.userInfo!
let keyboardEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
let keyboardEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
var toBeVisibleView = self.toBeVisibleView
@@ -106,11 +106,11 @@ public class InputVisibilityController: NSObject {
// the old way of animation will match the keyboard animation timing and curve
if !self.disableKeyboardMoveUpAnimation {
UIView.beginAnimations(nil, context: nil)
if let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double {
if let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double {
UIView.setAnimationDuration(duration)
}
if let animationValue = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int {
if let animationCurve = UIViewAnimationCurve(rawValue: animationValue) {
if let animationValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int {
if let animationCurve = UIView.AnimationCurve(rawValue: animationValue) {
UIView.setAnimationCurve(animationCurve)
}
}
@@ -182,7 +182,7 @@ public extension UIView {
static var viewExtension = "viewExtensionKeyboardVisibilityController"
}
public var keyboardVisibilityController: InputVisibilityController? {
var keyboardVisibilityController: InputVisibilityController? {
get {
return objc_getAssociatedObject(self, &KeyboardAssociatedKey.viewExtension) as? InputVisibilityController ?? nil
}
@@ -192,7 +192,7 @@ public extension UIView {
}
@discardableResult
public func addInputVisibilityController() -> InputVisibilityController {
func addInputVisibilityController() -> InputVisibilityController {
var keyboardController = self.keyboardVisibilityController
if keyboardController == nil {
@@ -211,14 +211,14 @@ public extension UIView {
return keyboardVisibilityController!
}
public func removeKeyboardVisibilityController() {
func removeKeyboardVisibilityController() {
keyboardVisibilityController = nil
}
}
public extension UIView {
public func findFirstResponder() -> UIView? {
func findFirstResponder() -> UIView? {
if isFirstResponder {
return self
}
@@ -18,28 +18,28 @@ public enum KeyboardEventType {
public var notificationName: NSNotification.Name {
switch self {
case .willShow: return .UIKeyboardWillShow
case .didShow: return .UIKeyboardDidShow
case .willHide: return .UIKeyboardWillHide
case .didHide: return .UIKeyboardDidHide
case .willChangeFrame: return .UIKeyboardWillChangeFrame
case .didChangeFrame: return .UIKeyboardDidChangeFrame
case .willShow: return UIResponder.keyboardWillShowNotification
case .didShow: return UIResponder.keyboardDidShowNotification
case .willHide: return UIResponder.keyboardWillHideNotification
case .didHide: return UIResponder.keyboardDidHideNotification
case .willChangeFrame: return UIResponder.keyboardWillChangeFrameNotification
case .didChangeFrame: return UIResponder.keyboardDidChangeFrameNotification
}
}
init?(name: NSNotification.Name) {
switch name {
case NSNotification.Name.UIKeyboardWillShow:
case UIResponder.keyboardWillShowNotification:
self = .willShow
case NSNotification.Name.UIKeyboardDidShow:
case UIResponder.keyboardDidShowNotification:
self = .didShow
case NSNotification.Name.UIKeyboardWillHide:
case UIResponder.keyboardWillHideNotification:
self = .willHide
case NSNotification.Name.UIKeyboardDidHide:
case UIResponder.keyboardDidHideNotification:
self = .didHide
case NSNotification.Name.UIKeyboardWillChangeFrame:
case UIResponder.keyboardWillChangeFrameNotification:
self = .willChangeFrame
case NSNotification.Name.UIKeyboardDidChangeFrame:
case UIResponder.keyboardDidChangeFrameNotification:
self = .didChangeFrame
default:
return nil
@@ -62,27 +62,27 @@ public struct KeyboardEvent {
public let type: KeyboardEventType
public let keyboardFrameBegin: CGRect
public let keyboardFrameEnd: CGRect
public let curve: UIViewAnimationCurve
public let curve: UIView.AnimationCurve
public let duration: TimeInterval
public let isLocal: Bool
public var options: UIViewAnimationOptions {
return UIViewAnimationOptions(rawValue: UInt(curve.rawValue << 16))
public var options: UIView.AnimationOptions {
return UIView.AnimationOptions(rawValue: UInt(curve.rawValue << 16))
}
init?(notification: Notification) {
guard let userInfo = (notification as NSNotification).userInfo else { return nil }
guard let type = KeyboardEventType(name: notification.name) else { return nil }
guard let begin = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue else { return nil }
guard let end = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return nil }
guard let begin = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue else { return nil }
guard let end = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return nil }
guard
let curveInt = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.intValue,
let curve = UIViewAnimationCurve(rawValue: curveInt)
let curveInt = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.intValue,
let curve = UIView.AnimationCurve(rawValue: curveInt)
else { return nil }
guard
let durationDouble = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
let durationDouble = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
else { return nil }
guard let isLocalInt = (userInfo[UIKeyboardIsLocalUserInfoKey] as? NSNumber)?.intValue
guard let isLocalInt = (userInfo[UIResponder.keyboardIsLocalUserInfoKey] as? NSNumber)?.intValue
else { return nil }
self.type = type
+3 -3
View File
@@ -4,7 +4,7 @@ import UIKit
public extension UIView {
public func snapshotImage(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2, afterScreenUpdates: Bool = false) -> UIImage? {
func snapshotImage(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2, afterScreenUpdates: Bool = false) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, scale)
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
@@ -12,7 +12,7 @@ public extension UIView {
return snapshotImage
}
public func snapshotView(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2, afterScreenUpdates: Bool = false) -> UIView? {
func snapshotView(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2, afterScreenUpdates: Bool = false) -> UIView? {
if let snapshotImage = snapshotImage(opaque: opaque, scale: scale, afterScreenUpdates: afterScreenUpdates) {
return UIImageView(image: snapshotImage)
} else {
@@ -20,7 +20,7 @@ public extension UIView {
}
}
public func snapshotLayer(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2) -> UIImage? {
func snapshotLayer(opaque: Bool = true, scale: CGFloat = UIScreen.main.scale * 2) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
+17 -5
View File
@@ -2,27 +2,37 @@ import Foundation
import UIKit
public protocol Togglable: class {
var isOn: Bool { get }
func selectedToggle(select: Bool)
}
extension UIControl: Togglable {
public var isOn: Bool { return isSelected }
@objc public func selectedToggle(select: Bool) {
isSelected = select
}
}
#if !os(tvOS)
extension UISwitch {
public override func selectedToggle(select: Bool) {
setOn(select, animated: true)
}
}
#endif
public struct Toggler {
var togglers = [Togglable]()
public var index: Int {
return togglers.firstIndex(where: { $0.isOn }) ?? 0
}
public init(default index: Int = 0, togglers: [Togglable]) {
self.togglers = togglers
toggleControl(toggle: togglers[index], togglers: togglers)
onAt(index: index)
}
public func on(toggle: Togglable) {
@@ -30,7 +40,9 @@ public struct Toggler {
}
public func onAt(index: Int) {
toggleControl(toggle: togglers[index], togglers: togglers)
if let toggler = togglers.at(index) {
toggleControl(toggle: toggler, togglers: togglers)
}
}
public mutating func add(toggle: Togglable) {
@@ -62,10 +74,10 @@ public class ButtonsTogglerView: UIStackView {
public required init(defaultIndex index: Int = 0,
buttons: [UIButton],
axis: UILayoutConstraintAxis = .horizontal,
axis: NSLayoutConstraint.Axis = .horizontal,
spacing: CGFloat = 30,
distribution: UIStackViewDistribution = .equalSpacing,
alignment: UIStackViewAlignment = .fill) {
distribution: UIStackView.Distribution = .equalSpacing,
alignment: UIStackView.Alignment = .fill) {
currentIndex = index
toggler = Toggler(default: index, togglers: buttons)
+100 -95
View File
@@ -3,134 +3,139 @@ import UIKit
extension UIViewController {
public func findUp<T>() -> T? where T: UIViewController {
return findUpVC(self)
return UISearchUtilities.findUpVC(self)
}
public func findDown<T>() -> T? where T: UIViewController {
return findDownVC(self)
return UISearchUtilities.findDownVC(self)
}
}
func findUpVC<T>(_ base: UIViewController) -> T? where T: UIViewController {
var vc: UIViewController? = base
while vc != nil {
if let vc = vc?.parent as? T {
return vc
} else {
vc = vc?.parent
}
}
return nil
}
func findDownVC<T>(_ base: UIViewController) -> T? where T: UIViewController {
var result: T?
for c in base.childViewControllers {
if let r = c as? T {
result = r
} else {
result = findDownVC(c)
struct UISearchUtilities {
static func findUpVC<T>(_ base: UIViewController) -> T? where T: UIViewController {
var vc: UIViewController? = base
while vc != nil {
if let vc = vc?.parent as? T {
return vc
} else {
vc = vc?.parent
}
}
return nil
}
if result != nil {
break
static func findDownVC<T>(_ base: UIViewController) -> T? where T: UIViewController {
var result: T?
for c in base.children {
if let r = c as? T {
result = r
} else {
result = findDownVC(c)
}
if result != nil {
break
}
}
return result
}
static func findDownVC<T>() -> T? where T: UIViewController {
guard let base = UIApplication.shared.keyWindow?.rootViewController else { return nil }
if let result = base as? T {
return result
}
return findDownVC(base)
}
static func first(inView view: UIView, where condition: @escaping (UIView) -> Bool) -> UIView? {
guard !condition(view) else { return view }
var result: UIView?
for v in view.subviews {
if condition(v) {
result = v
} else {
result = first(inView: v, where: condition)
}
if result != nil {
break
}
}
return result
}
static func findView<T>(_ root: UIView) -> T? where T: UIView {
var result: T?
for v in root.subviews {
if let r = v as? T {
result = r
} else {
result = findView(v)
}
if result != nil {
break
}
}
return result
}
static func findSuperView<T>(_ base: UIView) -> T? where T: UIView {
var v: UIView? = base
while v != nil {
if let v = v?.superview as? T {
return v
} else {
v = v?.superview
}
}
return nil
}
static func findAllViews<T>(_ root: UIView) -> [T] where T: UIView {
var result: [T] = []
for v in root.subviews {
if let r = v as? T {
result.append(r)
}
let rv: [T] = findAllViews(v)
result.append(contentsOf: rv)
}
return result
}
}
return result
}
func findDownVC<T>() -> T? where T: UIViewController {
guard let base = UIApplication.shared.keyWindow?.rootViewController else { return nil }
if let result = base as? T {
return result
}
return findDownVC(base)
}
extension UIView {
public func first(where condition: @escaping (UIView) -> Bool) -> UIView? {
return Utopia.first(inView: self, where: condition)
return UISearchUtilities.first(inView: self, where: condition)
}
public func find<T>() -> T? where T: UIView {
return Utopia.findView(self)
return UISearchUtilities.findView(self)
}
public func findSuperView<T>() -> T? where T: UIView {
return Utopia.findSuperView(self)
return UISearchUtilities.findSuperView(self)
}
public func findAll<T>() -> [T] where T: UIView {
return findAllViews(self)
return UISearchUtilities.findAllViews(self)
}
}
func first(inView view: UIView, where condition: @escaping (UIView) -> Bool) -> UIView? {
guard !condition(view) else { return view }
var result: UIView?
for v in view.subviews {
if condition(v) {
result = v
} else {
result = first(inView: v, where: condition)
}
if result != nil {
break
}
}
return result
}
func findView<T>(_ root: UIView) -> T? where T: UIView {
var result: T?
for v in root.subviews {
if let r = v as? T {
result = r
} else {
result = findView(v)
}
if result != nil {
break
}
}
return result
}
func findSuperView<T>(_ base: UIView) -> T? where T: UIView {
var v: UIView? = base
while v != nil {
if let v = v?.superview as? T {
return v
} else {
v = v?.superview
}
}
return nil
}
func findAllViews<T>(_ root: UIView) -> [T] where T: UIView {
var result: [T] = []
for v in root.subviews {
if let r = v as? T {
result.append(r)
}
let rv: [T] = findAllViews(v)
result.append(contentsOf: rv)
}
return result
}
extension UIView {
public func findConstraints(attribute: NSLayoutAttribute) -> [NSLayoutConstraint] {
public func findConstraints(attribute: NSLayoutConstraint.Attribute) -> [NSLayoutConstraint] {
let result = constraints.filter { $0.firstAttribute == attribute && $0.firstItem as? NSObject == self }
return result
}
public func findSuperviewConstraints(attribute: NSLayoutAttribute) -> [NSLayoutConstraint] {
public func findSuperviewConstraints(attribute: NSLayoutConstraint.Attribute) -> [NSLayoutConstraint] {
let result = superview?.constraints.filter {
($0.firstAttribute == attribute && $0.firstItem as? NSObject == self) ||
($0.secondAttribute == attribute && $0.secondItem as? NSObject == self)
+14 -1
View File
@@ -109,7 +109,20 @@ import UIKit
setNeedsDisplay()
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
backgroundColor = .clear
}
// MARK: - UIView
override open func draw(_ rect: CGRect) {
+4 -4
View File
@@ -2,19 +2,19 @@ import Foundation
public extension Bundle {
public var appName: String {
var appName: String {
return infoDictionary?["CFBundleName"] as! String
}
public var bundleId: String {
var bundleId: String {
return bundleIdentifier!
}
public var versionNumber: String {
var versionNumber: String {
return infoDictionary?["CFBundleShortVersionString"] as! String
}
public var buildNumber: String {
var buildNumber: String {
return infoDictionary?["CFBundleVersion"] as! String
}
}
@@ -1,35 +0,0 @@
//
// DiffAware.swift
// DeepDiff
//
// Created by Khoa Pham on 03.01.2018.
// Copyright © 2018 Khoa Pham. All rights reserved.
//
import Foundation
public protocol DiffAware {
func diff<T: Hashable>(old: Array<T>, new: Array<T>) -> [Change<T>]
}
extension DiffAware {
func preprocess<T: Hashable>(old: Array<T>, new: Array<T>) -> [Change<T>]? {
switch (old.isEmpty, new.isEmpty) {
case (true, true):
// empty
return []
case (true, false):
// all .insert
return new.enumerated().map { index, item in
return .insert(Insert(item: item, index: index))
}
case (false, true):
// all .delete
return old.enumerated().map { index, item in
return .delete(Delete(item: item, index: index))
}
default:
return nil
}
}
}
@@ -1,278 +0,0 @@
import Foundation
// https://gist.github.com/ndarville/3166060
public final class Heckel: DiffAware {
// OC and NC can assume three values: 1, 2, and many.
enum Counter {
case zero, one, many
func increment() -> Counter {
switch self {
case .zero:
return .one
case .one:
return .many
case .many:
return self
}
}
}
// The symbol table stores three entries for each line
class TableEntry: Equatable {
// The value entry for each line in table has two counters.
// They specify the line's number of occurrences in O and N: OC and NC.
var oldCounter: Counter = .zero
var newCounter: Counter = .zero
// Aside from the two counters, the line's entry
// also includes a reference to the line's line number in O: OLNO.
// OLNO is only interesting, if OC == 1.
// Alternatively, OLNO would have to assume multiple values or none at all.
var indexesInOld: [Int] = []
static func ==(lhs: TableEntry, rhs: TableEntry) -> Bool {
return lhs.oldCounter == rhs.oldCounter && lhs.newCounter == rhs.newCounter && lhs.indexesInOld == rhs.indexesInOld
}
}
// The arrays OA and NA have one entry for each line in their respective files, O and N.
// The arrays contain either:
enum ArrayEntry: Equatable {
// a pointer to the line's symbol table entry, table[line]
case tableEntry(TableEntry)
// the line's number in the other file (N for OA, O for NA)
case indexInOther(Int)
public static func == (lhs: ArrayEntry, rhs: ArrayEntry) -> Bool {
switch (lhs, rhs) {
case (.tableEntry(let l), .tableEntry(let r)):
return l == r
case (.indexInOther(let l), .indexInOther(let r)):
return l == r
default:
return false
}
}
}
public init() {}
public func diff<T: Hashable>(old: Array<T>, new: Array<T>) -> [Change<T>] {
// The Symbol Table
// Each line works as the key in the table look-up, i.e. as table[line].
var table: [Int: TableEntry] = [:]
// The arrays OA and NA have one entry for each line in their respective files, O and N
var oldArray = [ArrayEntry]()
var newArray = [ArrayEntry]()
perform1stPass(new: new, table: &table, newArray: &newArray)
perform2ndPass(old: old, table: &table, oldArray: &oldArray)
perform345Pass(newArray: &newArray, oldArray: &oldArray)
let changes = perform6thPass(new: new, old: old, newArray: newArray, oldArray: oldArray)
return changes
}
private func perform1stPass<T: Hashable>(
new: Array<T>,
table: inout [Int: TableEntry],
newArray: inout [ArrayEntry]) {
// 1st pass
// a. Each line i of file N is read in sequence
new.forEach { item in
// b. An entry for each line i is created in the table, if it doesn't already exist
let entry = table[item.hashValue] ?? TableEntry()
// c. NC for the line's table entry is incremented
entry.newCounter = entry.newCounter.increment()
// d. NA[i] is set to point to the table entry of line i
newArray.append(.tableEntry(entry))
//
table[item.hashValue] = entry
}
}
private func perform2ndPass<T: Hashable>(
old: Array<T>,
table: inout [Int: TableEntry],
oldArray: inout [ArrayEntry]) {
// 2nd pass
// Similar to first pass, except it acts on files
old.enumerated().forEach { tuple in
// old
let entry = table[tuple.element.hashValue] ?? TableEntry()
// oldCounter
entry.oldCounter = entry.oldCounter.increment()
// lineNumberInOld which is set to the line's number
entry.indexesInOld.append(tuple.offset)
// oldArray
oldArray.append(.tableEntry(entry))
//
table[tuple.element.hashValue] = entry
}
}
private func perform345Pass(newArray: inout [ArrayEntry], oldArray: inout [ArrayEntry]) {
// 3rd pass
// a. We use Observation 1:
// If a line occurs only once in each file, then it must be the same line,
// although it may have been moved.
// We use this observation to locate unaltered lines that we
// subsequently exclude from further treatment.
// b. Using this, we only process the lines where OC == NC == 1
// c. As the lines between O and N "must be the same line,
// although it may have been moved", we alter the table pointers
// in OA and NA to the number of the line in the other file.
// d. We also locate unique virtual lines
// immediately before the first and
// immediately after the last lines of the files ???
//
// 4th pass
// a. We use Observation 2:
// If a line has been found to be unaltered,
// and the lines immediately adjacent to it in both files are identical,
// then these lines must be the same line.
// This information can be used to find blocks of unchanged lines.
// b. Using this, we process each entry in ascending order.
// c. If
// NA[i] points to OA[j], and
// NA[i+1] and OA[j+1] contain identical table entry pointers
// then
// OA[j+1] is set to line i+1, and
// NA[i+1] is set to line j+1
//
// 5th pass
// Similar to fourth pass, except:
// It processes each entry in descending order
// It uses j-1 and i-1 instead of j+1 and i+1
newArray.enumerated().forEach { (indexOfNew, item) in
switch item {
case .tableEntry(let entry):
guard !entry.indexesInOld.isEmpty else {
return
}
let indexOfOld = entry.indexesInOld.removeFirst()
let isObservation1 = entry.newCounter == .one && entry.oldCounter == .one
let isObservation2 = entry.newCounter != .zero && entry.oldCounter != .zero && newArray[indexOfNew] == oldArray[indexOfOld]
guard isObservation1 || isObservation2 else {
return
}
newArray[indexOfNew] = .indexInOther(indexOfOld)
oldArray[indexOfOld] = .indexInOther(indexOfNew)
case .indexInOther(_):
break
}
}
}
private func perform6thPass<T: Hashable>(
new: Array<T>,
old: Array<T>,
newArray: [ArrayEntry],
oldArray: [ArrayEntry]) -> [Change<T>] {
// 6th pass
// At this point following our five passes,
// we have the necessary information contained in NA to tell the differences between O and N.
// This pass uses NA and OA to tell when a line has changed between O and N,
// and how far the change extends.
// a. Determining a New Line
// Recall our initial description of NA in which we said that the array has either:
// one entry for each line of file N containing either
// a pointer to table[line]
// the line's number in file O
// Using these two cases, we know that if NA[i] refers
// to an entry in table (case 1), then line i must be new
// We know this, because otherwise, NA[i] would have contained
// the line's number in O (case 2), if it existed in O and N
// b. Determining the Boundaries of the New Line
// We now know that we are dealing with a new line, but we have yet to figure where the change ends.
// Recall Observation 2:
// If NA[i] points to OA[j], but NA[i+1] does not
// point to OA[j+1], then line i is the boundary for the alteration.
// You can look at it this way:
// i : The quick brown fox | j : The quick brown fox
// i+1: jumps over the lazy dog | j+1: jumps over the loafing cat
// Here, NA[i] == OA[j], but NA[i+1] != OA[j+1].
// This means our boundary is between the two lines.
var changes = [Change<T>]()
var deleteOffsets = Array(repeating: 0, count: old.count)
// deletions
do {
var runningOffset = 0
oldArray.enumerated().forEach { oldTuple in
deleteOffsets[oldTuple.offset] = runningOffset
guard case .tableEntry = oldTuple.element else {
return
}
changes.append(.delete(Delete(
item: old[oldTuple.offset],
index: oldTuple.offset
)))
runningOffset += 1
}
}
// insertions, replaces, moves
do {
var runningOffset = 0
newArray.enumerated().forEach { newTuple in
switch newTuple.element {
case .tableEntry:
runningOffset += 1
changes.append(.insert(Insert(
item: new[newTuple.offset],
index: newTuple.offset
)))
case .indexInOther(let oldIndex):
if old[oldIndex] != new[newTuple.offset] {
changes.append(.replace(Replace(
oldItem: old[oldIndex],
newItem: new[newTuple.offset],
index: newTuple.offset
)))
}
let deleteOffset = deleteOffsets[oldIndex]
// The object is not at the expected position, so move it.
if (oldIndex - deleteOffset + runningOffset) != newTuple.offset {
changes.append(.move(Move(
item: new[newTuple.offset],
fromIndex: oldIndex,
toIndex: newTuple.offset
)))
}
}
}
}
return changes
}
}
@@ -1,153 +0,0 @@
import Foundation
// https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
public final class WagnerFischer: DiffAware {
private let reduceMove: Bool
public init(reduceMove: Bool = false) {
self.reduceMove = reduceMove
}
public func diff<T: Hashable>(old: Array<T>, new: Array<T>) -> [Change<T>] {
let previousRow = Row<T>()
previousRow.seed(with: new)
let currentRow = Row<T>()
// row in matrix
old.enumerated().forEach { indexInOld, oldItem in
// reset current row
currentRow.reset(
count: previousRow.slots.count,
indexInOld: indexInOld,
oldItem: oldItem
)
// column in matrix
new.enumerated().forEach { indexInNew, newItem in
if isEqual(oldItem: old[indexInOld], newItem: new[indexInNew]) {
currentRow.update(indexInNew: indexInNew, previousRow: previousRow)
} else {
currentRow.updateWithMin(
previousRow: previousRow,
indexInNew: indexInNew,
newItem: newItem,
indexInOld: indexInOld,
oldItem: oldItem
)
}
}
// set previousRow
previousRow.slots = currentRow.slots
}
let changes = currentRow.lastSlot()
if reduceMove {
return MoveReducer<T>().reduce(changes: changes)
} else {
return changes
}
}
// MARK: - Helper
private func isEqual<T: Hashable>(oldItem: T, newItem: T) -> Bool {
// Same items must have same hashValue
if oldItem.hashValue != newItem.hashValue {
return false
} else {
// Different hashValue does not always mean different items
return oldItem == newItem
}
}
}
// We can adapt the algorithm to use less space, O(m) instead of O(mn),
// since it only requires that the previous row and current row be stored at any one time
class Row<T> {
/// Each slot is a collection of Change
var slots: [[Change<T>]] = []
/// Seed with .insert from new
func seed(with new: Array<T>) {
// First slot should be empty
slots = Array(repeatElement([], count: new.count + 1))
// Each slot increases in the number of changes
new.enumerated().forEach { index, item in
let slotIndex = convert(indexInNew: index)
slots[slotIndex] = combine(
slot: slots[slotIndex-1],
change: .insert(Insert(item: item, index: index))
)
}
}
/// Reset with empty slots
/// First slot is .delete
func reset(count: Int, indexInOld: Int, oldItem: T) {
if slots.isEmpty {
slots = Array(repeatElement([], count: count))
}
slots[0] = combine(
slot: slots[0],
change: .delete(Delete(item: oldItem, index: indexInOld))
)
}
/// Use .replace from previousRow
func update(indexInNew: Int, previousRow: Row) {
let slotIndex = convert(indexInNew: indexInNew)
slots[slotIndex] = previousRow.slots[slotIndex - 1]
}
/// Choose the min
func updateWithMin(previousRow: Row, indexInNew: Int, newItem: T, indexInOld: Int, oldItem: T) {
let slotIndex = convert(indexInNew: indexInNew)
let topSlot = previousRow.slots[slotIndex]
let leftSlot = slots[slotIndex - 1]
let topLeftSlot = previousRow.slots[slotIndex - 1]
let minCount = min(topSlot.count, leftSlot.count, topLeftSlot.count)
// Order of cases does not matter
switch minCount {
case topSlot.count:
slots[slotIndex] = combine(
slot: topSlot,
change: .delete(Delete(item: oldItem, index: indexInOld))
)
case leftSlot.count:
slots[slotIndex] = combine(
slot: leftSlot,
change: .insert(Insert(item: newItem, index: indexInNew))
)
case topLeftSlot.count:
slots[slotIndex] = combine(
slot: topLeftSlot,
change: .replace(Replace(oldItem: oldItem, newItem: newItem, index: indexInNew))
)
default:
assertionFailure()
}
}
/// Add one more change
func combine<T>(slot: [Change<T>], change: Change<T>) -> [Change<T>] {
var slot = slot
slot.append(change)
return slot
}
//// Last slot
func lastSlot() -> [Change<T>] {
return slots[slots.count - 1]
}
/// Convert to slotIndex, as slots has 1 extra at the beginning
func convert(indexInNew: Int) -> Int {
return indexInNew + 1
}
}
@@ -1,68 +0,0 @@
import Foundation
public struct Insert<T> {
public let item: T
public let index: Int
}
public struct Delete<T> {
public let item: T
public let index: Int
}
public struct Replace<T> {
public let oldItem: T
public let newItem: T
public let index: Int
}
public struct Move<T> {
public let item: T
public let fromIndex: Int
public let toIndex: Int
}
/// The computed changes from diff
///
/// - insert: Insert an item at index
/// - delete: Delete an item from index
/// - replace: Replace an item at index with another item
/// - move: Move the same item from this index to another index
public enum Change<T> {
case insert(Insert<T>)
case delete(Delete<T>)
case replace(Replace<T>)
case move(Move<T>)
public var insert: Insert<T>? {
if case .insert(let insert) = self {
return insert
}
return nil
}
public var delete: Delete<T>? {
if case .delete(let delete) = self {
return delete
}
return nil
}
public var replace: Replace<T>? {
if case .replace(let replace) = self {
return replace
}
return nil
}
public var move: Move<T>? {
if case .move(let move) = self {
return move
}
return nil
}
}
@@ -1,19 +0,0 @@
import Foundation
extension Collection {
func executeIfPresent(_ closure: (Self) -> Void) {
if !isEmpty {
closure(self)
}
}
}
extension Array where Element == Int {
var asIndexSet: IndexSet {
return reduce(IndexSet()) { set, item in
var set = set
set.insert(item)
return set
}
}
}
@@ -1,19 +0,0 @@
import Foundation
/// Perform diff between old and new collections
///
/// - Parameters:
/// - old: Old collection
/// - new: New collection
/// - Returns: A set of changes
public func diff<T: Hashable>(
old: Array<T>,
new: Array<T>,
algorithm: DiffAware = Heckel()) -> [Change<T>] {
if let changes = algorithm.preprocess(old: old, new: new) {
return changes
}
return algorithm.diff(old: old, new: new)
}
@@ -1,35 +0,0 @@
import Foundation
struct MoveReducer<T> {
func reduce<T: Equatable>(changes: [Change<T>]) -> [Change<T>] {
// Find pairs of .insert and .delete with same item
let inserts = changes.compactMap({ $0.insert })
if inserts.isEmpty {
return changes
}
var changes = changes
inserts.forEach { insert in
if let insertIndex = changes.index(where: { $0.insert?.item == insert.item }),
let deleteIndex = changes.index(where: { $0.delete?.item == insert.item }) {
let insertChange = changes[insertIndex].insert!
let deleteChange = changes[deleteIndex].delete!
let move = Move<T>(item: insert.item, fromIndex: deleteChange.index, toIndex: insertChange.index)
// .insert can be before or after .delete
let minIndex = min(insertIndex, deleteIndex)
let maxIndex = max(insertIndex, deleteIndex)
// remove both .insert and .delete, and replace by .move
changes.remove(at: minIndex)
changes.remove(at: maxIndex.advanced(by: -1))
changes.insert(.move(move), at: minIndex)
}
}
return changes
}
}
@@ -1,54 +0,0 @@
import Foundation
public struct ChangeWithIndexPath {
public let inserts: [IndexPath]
public let deletes: [IndexPath]
public let replaces: [IndexPath]
public let moves: [(from: IndexPath, to: IndexPath)]
public init(
inserts: [IndexPath],
deletes: [IndexPath],
replaces:[IndexPath],
moves: [(from: IndexPath, to: IndexPath)]) {
self.inserts = inserts
self.deletes = deletes
self.replaces = replaces
self.moves = moves
}
}
public class IndexPathConverter {
public init() {
}
public func convert<T>(changes: [Change<T>], section: Int) -> ChangeWithIndexPath {
let inserts = changes.compactMap({ $0.insert }).map({ $0.index.toIndexPath(section: section) })
let deletes = changes.compactMap({ $0.delete }).map({ $0.index.toIndexPath(section: section) })
let replaces = changes.compactMap({ $0.replace }).map({ $0.index.toIndexPath(section: section) })
let moves = changes.compactMap({ $0.move }).map({
(
from: $0.fromIndex.toIndexPath(section: section),
to: $0.toIndex.toIndexPath(section: section)
)
})
return ChangeWithIndexPath(
inserts: inserts,
deletes: deletes,
replaces: replaces,
moves: moves
)
}
}
extension Int {
fileprivate func toIndexPath(section: Int) -> IndexPath {
return IndexPath(item: self, section: section)
}
}
@@ -1,39 +0,0 @@
import Foundation
public struct ChangeWithIndexSet {
public let inserts: IndexSet
public let deletes: IndexSet
public let replaces: IndexSet
public let moves: [(from: Int, to: Int)]
public init(
inserts: IndexSet,
deletes: IndexSet,
replaces: IndexSet,
moves: [(from: Int, to: Int)]) {
self.inserts = inserts
self.deletes = deletes
self.replaces = replaces
self.moves = moves
}
public init<T>(changes: [Change<T>]) {
inserts = changes
.compactMap { $0.insert?.index }
.asIndexSet
deletes = changes
.compactMap { $0.delete?.index }
.asIndexSet
replaces = changes
.compactMap { $0.replace?.index }
.asIndexSet
moves = changes
.compactMap { $0.move }
.map { (from: $0.fromIndex, to: $0.toIndex) }
}
}
@@ -1,93 +0,0 @@
import UIKit
public extension UICollectionView {
/// Animate reload in a batch update
///
/// - Parameters:
/// - changes: The changes from diff
/// - section: The section that all calculated IndexPath belong
/// - completion: Called when operation completes
public func reload<T: Hashable>(
changes: [Change<T>],
section: Int = 0,
calledInsideBatch: Bool = false,
completion: @escaping (Bool) -> Void = { _ in }) {
let changesWithIndexPath = IndexPathConverter().convert(changes: changes, section: section)
if calledInsideBatch {
internalBatchUpdates(changesWithIndexPath: changesWithIndexPath)
} else {
performBatchUpdates({
internalBatchUpdates(changesWithIndexPath: changesWithIndexPath)
}, completion: completion)
}
}
/// Animate sections reload in a batch update
///
/// - Parameters:
/// - changes: The changes from diff
/// - completion: Called when operation completes
public func reloadSections<T: Hashable>(
changes: [Change<T>],
calledInsideBatch: Bool = false,
completion: @escaping (Bool) -> Void = { _ in }) {
let changesWithIndexSet = ChangeWithIndexSet(changes: changes)
if calledInsideBatch {
internalBatchUpdates(changesWithIndexSet: changesWithIndexSet)
} else {
performBatchUpdates({
internalBatchUpdates(changesWithIndexSet: changesWithIndexSet)
}, completion: completion)
}
}
// MARK: - Helper
private func internalBatchUpdates(changesWithIndexPath: ChangeWithIndexPath) {
changesWithIndexPath.deletes.executeIfPresent {
deleteItems(at: $0)
}
changesWithIndexPath.inserts.executeIfPresent {
insertItems(at: $0)
}
changesWithIndexPath.moves.executeIfPresent {
$0.forEach { move in
moveItem(at: move.from, to: move.to)
}
}
changesWithIndexPath.replaces.executeIfPresent {
reloadItems(at: $0)
}
}
private func internalBatchUpdates(changesWithIndexSet: ChangeWithIndexSet) {
changesWithIndexSet.deletes.executeIfPresent {
deleteSections($0)
}
changesWithIndexSet.inserts.executeIfPresent {
insertSections($0)
}
changesWithIndexSet.moves.executeIfPresent {
$0.forEach { move in
moveSection(move.from, toSection: move.to)
}
}
changesWithIndexSet.replaces.executeIfPresent {
reloadSections($0)
}
}
}
@@ -1,70 +0,0 @@
import UIKit
public extension UITableView {
/// Animate reload in a batch update
///
/// - Parameters:
/// - changes: The changes from diff
/// - section: The section that all calculated IndexPath belong
/// - insertionAnimation: The animation for insert rows
/// - deletionAnimation: The animation for delete rows
/// - replacementAnimation: The animation for reload rows
/// - completion: Called when operation completes
public func reload<T: Hashable>(
changes: [Change<T>],
section: Int = 0,
insertionAnimation: UITableViewRowAnimation = .automatic,
deletionAnimation: UITableViewRowAnimation = .automatic,
replacementAnimation: UITableViewRowAnimation = .automatic,
completion: @escaping (Bool) -> Void) {
let changesWithIndexPath = IndexPathConverter().convert(changes: changes, section: section)
// reloadRows needs to be called outside the batch
if #available(iOS 11, tvOS 11, *) {
performBatchUpdates({
internalBatchUpdates(changesWithIndexPath: changesWithIndexPath,
insertionAnimation: insertionAnimation,
deletionAnimation: deletionAnimation)
}, completion: completion)
changesWithIndexPath.replaces.executeIfPresent {
self.reloadRows(at: $0, with: replacementAnimation)
}
} else {
beginUpdates()
internalBatchUpdates(changesWithIndexPath: changesWithIndexPath,
insertionAnimation: insertionAnimation,
deletionAnimation: deletionAnimation)
endUpdates()
changesWithIndexPath.replaces.executeIfPresent {
reloadRows(at: $0, with: replacementAnimation)
}
completion(true)
}
}
// MARK: - Helper
private func internalBatchUpdates(changesWithIndexPath: ChangeWithIndexPath,
insertionAnimation: UITableViewRowAnimation,
deletionAnimation: UITableViewRowAnimation) {
changesWithIndexPath.deletes.executeIfPresent {
deleteRows(at: $0, with: deletionAnimation)
}
changesWithIndexPath.inserts.executeIfPresent {
insertRows(at: $0, with: insertionAnimation)
}
changesWithIndexPath.moves.executeIfPresent {
$0.forEach { move in
moveRow(at: move.from, to: move.to)
}
}
}
}
+15
View File
@@ -1,4 +1,5 @@
import Foundation
import UIKit
public enum Math {
public static func lerp<T: FloatingPoint>(from: T, to: T, progress: T) -> T {
@@ -34,3 +35,17 @@ public enum Progress {
return centeredProgress
}
}
public enum Inertia {
public static func applyResistance(for source: CGFloat, with scrollPosition: CGFloat, decelerationRate: UIScrollView.DecelerationRate = .fast, maximumScrollDistance: CGFloat = 120) -> CGFloat {
let resistantDistance = (decelerationRate.rawValue * abs(scrollPosition) * maximumScrollDistance) / (maximumScrollDistance + decelerationRate.rawValue * abs(scrollPosition))
return source + (scrollPosition < 0 ? -resistantDistance : resistantDistance)
}
// Distance travelled after deceleration to zero velocity at a constant rate
public static func project(initialVelocity: CGFloat, decelerationRate: UIScrollView.DecelerationRate = .fast) -> CGFloat {
return (initialVelocity / 1000.0) * decelerationRate.rawValue / (1.0 - decelerationRate.rawValue)
}
}
@@ -0,0 +1,515 @@
import Foundation
extension NSAttributedString {
/**
Returns a new mutable string with characters from a given character set removed.
See http://panupan.com/2012/06/04/trim-leading-and-trailing-whitespaces-from-nsmutableattributedstring/
- Parameters:
- charSet: The character set with which to remove characters.
- returns: A new string with the matching characters removed.
*/
public func trimmingCharacters(in set: CharacterSet) -> NSAttributedString {
let modString = NSMutableAttributedString(attributedString: self)
modString.trimCharacters(in: set)
return NSAttributedString(attributedString: modString)
}
}
extension NSMutableAttributedString {
/**
Modifies this instance of the string to remove characters from a given character set from
the beginning and end of the string.
See http://panupan.com/2012/06/04/trim-leading-and-trailing-whitespaces-from-nsmutableattributedstring/
- Parameters:
- charSet: The character set with which to remove characters.
*/
public func trimCharacters(in set: CharacterSet) {
var range = (string as NSString).rangeOfCharacter(from: set)
// Trim leading characters from character set.
while range.length != 0 && range.location == 0 {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: set)
}
// Trim trailing characters from character set.
range = (string as NSString).rangeOfCharacter(from: set, options: .backwards)
while range.length != 0 && NSMaxRange(range) == length {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: set, options: .backwards)
}
}
}
extension NSMutableAttributedString {
private var range: NSRange {
return NSRange(location: 0, length: length)
}
private var paragraphStyle: NSMutableParagraphStyle {
let style = attributes(at: 0, effectiveRange: nil)[.paragraphStyle] as? NSMutableParagraphStyle
return style ?? NSMutableParagraphStyle()
}
}
// MARK: - Font
extension NSMutableAttributedString {
/**
Applies a font to the entire string.
- parameter font: The font.
*/
@discardableResult
public func font(_ font: UIFont) -> Self {
if length > 0 {
addAttribute(.font, value: font, range: range)
}
return self
}
/**
Applies a font to the entire string.
- parameter name: The font name.
- parameter size: The font size.
Note: If the specified font name cannot be loaded, this method will fallback to the system font at the specified size.
*/
@discardableResult
public func font(name: String, size: CGFloat) -> Self {
if length > 0 {
addAttribute(.font, value: UIFont(name: name, size: size) ?? .systemFont(ofSize: size), range: range)
}
return self
}
}
// MARK: - Paragraph style
extension NSMutableAttributedString {
/**
Applies a text alignment to the entire string.
- parameter alignment: The text alignment.
*/
@discardableResult
public func alignment(_ alignment: NSTextAlignment) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.alignment = alignment
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies line spacing to the entire string.
- parameter lineSpacing: The line spacing amount.
*/
@discardableResult
public func lineSpacing(_ lineSpacing: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.lineSpacing = lineSpacing
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies paragraph spacing to the entire string.
- parameter paragraphSpacing: The paragraph spacing amount.
*/
@discardableResult
public func paragraphSpacing(_ paragraphSpacing: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.paragraphSpacing = paragraphSpacing
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a line break mode to the entire string.
- parameter mode: The line break mode.
*/
@discardableResult
public func lineBreak(_ mode: NSLineBreakMode) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.lineBreakMode = mode
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a line height multiplier to the entire string.
- parameter multiple: The line height multiplier.
*/
@discardableResult
public func lineHeight(multiple: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.lineHeightMultiple = multiple
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a first line head indent to the string.
- parameter indent: The first line head indent amount.
*/
@discardableResult
public func firstLineHeadIndent(_ indent: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.firstLineHeadIndent = indent
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a head indent to the string.
- parameter indent: The head indent amount.
*/
@discardableResult
public func headIndent(_ indent: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.headIndent = indent
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a tail indent to the string.
- parameter indent: The tail indent amount.
*/
@discardableResult
public func tailIndent(_ indent: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.tailIndent = indent
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a minimum line height to the entire string.
- parameter height: The minimum line height.
*/
@discardableResult
public func minimumLineHeight(_ height: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.minimumLineHeight = height
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a maximum line height to the entire string.
- parameter height: The maximum line height.
*/
@discardableResult
public func maximumLineHeight(_ height: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.maximumLineHeight = height
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a base writing direction to the entire string.
- parameter direction: The base writing direction.
*/
@discardableResult
public func baseWritingDirection(_ direction: NSWritingDirection) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.baseWritingDirection = direction
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
/**
Applies a paragraph spacing before amount to the string.
- parameter spacing: The distance between the paragraphs top and the beginning of its text content.
*/
@discardableResult
public func paragraphSpacingBefore(_ spacing: CGFloat) -> Self {
if length > 0 {
let paragraphStyle = self.paragraphStyle
paragraphStyle.paragraphSpacingBefore = spacing
addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
return self
}
}
// MARK: - Foreground color
extension NSMutableAttributedString {
/**
Applies the given color over the entire string, as the foreground color.
- parameter color: The color to apply.
*/
@discardableResult @nonobjc
public func color(_ color: UIColor, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.foregroundColor, value: color.withAlphaComponent(alpha), range: range)
}
return self
}
/**
Applies the given color over the entire string, as the foreground color.
- parameter color: The color to apply.
*/
@discardableResult
public func color(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.foregroundColor, value: UIColor(red: red, green: green, blue: blue, alpha: alpha), range: range)
}
return self
}
/**
Applies the given color over the entire string, as the foreground color.
- parameter color: The color to apply.
*/
@discardableResult
public func color(white: CGFloat, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.foregroundColor, value: UIColor(white: white, alpha: alpha), range: range)
}
return self
}
}
// MARK: - Underline, kern, strikethrough, stroke, shadow, text effect
extension NSMutableAttributedString {
/**
Applies a single underline under the entire string.
- parameter style: The `NSUnderlineStyle` to apply. Defaults to `.styleSingle`.
*/
@discardableResult
public func underline(style: NSUnderlineStyle = .single, color: UIColor? = nil) -> Self {
if length > 0 {
addAttribute(.underlineStyle, value: style.rawValue, range: range)
if let color = color {
addAttribute(.underlineColor, value: color, range: range)
}
}
return self
}
/**
Applies a kern (spacing) value to the entire string.
- parameter value: The space between each character in the string.
*/
@discardableResult
public func kern(_ value: CGFloat) -> Self {
if length > 0 {
addAttribute(.kern, value: value, range: range)
}
return self
}
/**
Applies a strikethrough to the entire string.
- parameter style: The `NSUnderlineStyle` to apply. Defaults to `.styleSingle`.
- parameter color: The underline color. Defaults to the color of the text.
*/
@discardableResult
public func strikethrough(style: NSUnderlineStyle = .single, color: UIColor? = nil) -> Self {
if length > 0 {
addAttribute(.strikethroughStyle, value: style.rawValue, range: range)
if let color = color {
addAttribute(.strikethroughColor, value: color, range: range)
}
}
return self
}
/**
Applies a stroke to the entire string.
- parameter color: The stroke color.
- parameter width: The stroke width.
*/
@discardableResult
public func stroke(color: UIColor, width: CGFloat) -> Self {
if length > 0 {
addAttributes([
.strokeColor : color,
.strokeWidth : width
], range: range)
}
return self
}
/**
Applies a shadow to the entire string.
- parameter color: The shadow color.
- parameter radius: The shadow blur radius.
- parameter offset: The shadow offset.
*/
@discardableResult
public func shadow(color: UIColor, radius: CGFloat, offset: CGSize) -> Self {
if length > 0 {
let shadow = NSShadow()
shadow.shadowColor = color
shadow.shadowBlurRadius = radius
shadow.shadowOffset = offset
addAttribute(.shadow, value: shadow, range: range)
}
return self
}
}
// MARK: - Background color
extension NSMutableAttributedString {
/**
Applies a background color to the entire string.
- parameter color: The color to apply.
*/
@discardableResult @nonobjc
public func backgroundColor(_ color: UIColor, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.backgroundColor, value: color.withAlphaComponent(alpha), range: range)
}
return self
}
/**
Applies a background color to the entire string.
- parameter red: The red color component.
- parameter green: The green color component.
- parameter blue: The blue color component.
- parameter alpha: The alpha component.
*/
@discardableResult
public func backgroundColor(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.backgroundColor, value: UIColor(red: red, green: green, blue: blue, alpha: alpha), range: range)
}
return self
}
/**
Applies a background color to the entire string.
- parameter white: The white color component.
- parameter alpha: The alpha component.
*/
@discardableResult
public func backgroundColor(white: CGFloat, alpha: CGFloat = 1) -> Self {
if length > 0 {
addAttribute(.backgroundColor, value: UIColor(white: white, alpha: alpha), range: range)
}
return self
}
}
extension NSMutableAttributedString {
/**
Applies a baseline offset to the entire string.
- parameter offset: The offset value.
*/
@discardableResult
public func baselineOffset(_ offset: Float) -> Self {
if length > 0 {
addAttribute(.baselineOffset, value: NSNumber(value: offset), range: range)
}
return self
}
}
public func +(lhs: NSMutableAttributedString, rhs: NSAttributedString) -> NSMutableAttributedString {
let lhs = NSMutableAttributedString(attributedString: lhs)
lhs.append(rhs)
return lhs
}
public func +=(lhs: NSMutableAttributedString, rhs: NSAttributedString) {
lhs.append(rhs)
}
@@ -3,7 +3,7 @@ import UIKit
extension NSMutableAttributedString {
public func addAttributes(for string: String, attributes: [NSAttributedStringKey : Any]) -> Self {
public func addAttributes(for string: String, attributes: [NSAttributedString.Key : Any]) -> Self {
let range = (self.string as NSString).range(of: string)
addAttributes(attributes, range: range)
return self
@@ -12,9 +12,9 @@ extension NSMutableAttributedString {
public func setAsLink(textToFind:String, linkURL:String, color: UIColor, font: UIFont? = nil) -> Self {
let foundRange = self.mutableString.range(of: textToFind)
if foundRange.location != NSNotFound {
self.addAttribute(NSAttributedStringKey.link, value: linkURL, range: foundRange)
self.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: foundRange)
if let font = font { self.addAttribute(NSAttributedStringKey.font, value: font, range: foundRange) }
self.addAttribute(NSAttributedString.Key.link, value: linkURL, range: foundRange)
self.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: foundRange)
if let font = font { self.addAttribute(NSAttributedString.Key.font, value: font, range: foundRange) }
}
return self
}
@@ -28,11 +28,4 @@ extension NSAttributedString {
let result = boundingRect(with: size, options: [ .usesLineFragmentOrigin, .usesFontLeading ], context: nil).size
return result
}
// public static func + (left: NSAttributedString, right: NSAttributedString) -> NSAttributedString {
// return (left.mutableCopy() as! NSMutableAttributedString).then {
// $0.append(right)
// }
// }
}
+2 -39
View File
@@ -80,7 +80,7 @@ public class Reachability {
public var whenReachable: NetworkReachable?
public var whenUnreachable: NetworkUnreachable?
@available(*, deprecated: 4.0, renamed: "allowsCellularConnection")
@available(*, deprecated, renamed: "allowsCellularConnection")
public let reachableOnWWAN: Bool = true
/// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
@@ -89,7 +89,7 @@ public class Reachability {
// The notification center on which "reachability changed" events are being posted
public var notificationCenter: NotificationCenter = NotificationCenter.default
@available(*, deprecated: 4.0, renamed: "connection.description")
@available(*, deprecated, renamed: "connection.description")
public var currentReachabilityString: String {
return "\(connection)"
}
@@ -204,43 +204,6 @@ public extension Reachability {
SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
}
// MARK: - *** Connection test methods ***
@available(*, deprecated: 4.0, message: "Please use `connection != .none`")
var isReachable: Bool {
guard isReachableFlagSet else { return false }
if isConnectionRequiredAndTransientFlagSet {
return false
}
if isRunningOnDevice {
if isOnWWANFlagSet && !reachableOnWWAN {
// We don't want to connect when on cellular connection
return false
}
}
return true
}
@available(*, deprecated: 4.0, message: "Please use `connection == .cellular`")
var isReachableViaWWAN: Bool {
// Check we're not on the simulator, we're REACHABLE and check we're on WWAN
return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
}
@available(*, deprecated: 4.0, message: "Please use `connection == .wifi`")
var isReachableViaWiFi: Bool {
// Check we're reachable
guard isReachableFlagSet else { return false }
// If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
guard isRunningOnDevice else { return true }
// Check we're NOT on WWAN
return !isOnWWANFlagSet
}
var description: String {
let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X"
let R = isReachableFlagSet ? "R" : "-"
@@ -0,0 +1,330 @@
import Foundation
extension String {
/// Converts self to an unsigned byte array.
public var bytes: [UInt8] {
return utf8.map { $0 }
}
/// Converts self to an NSMutableAttributedString.
public var attributed: NSMutableAttributedString {
return NSMutableAttributedString(string: self)
}
/// Converts self to an NSString.
public var ns: NSString {
return self as NSString
}
/**
Converts string to camel-case.
Examples:
```
"os version".camelCasedString // "osVersion"
"HelloWorld".camelCasedString // "helloWorld"
"someword With Characters".camelCasedString // "somewordWithCharacters"
```
*/
public var camelCased: String {
guard !isEmpty else { return self }
if contains(" ") {
let first = self[0].lowercased()
let cammel = capitalized.replacingOccurrences(of: " ", with: "")
let rest = String(cammel.dropFirst())
return first + rest
} else {
let first = self[0].lowercased()
let rest = String(dropFirst())
return first + rest
}
}
/**
The base64 encoded version of self.
Credit: http://stackoverflow.com/a/29365954
*/
public var base64Encoded: String? {
let utf8str = data(using: .utf8)
return utf8str?.base64EncodedString()
}
/**
The decoded value of a base64 encoded string
Credit: http://stackoverflow.com/a/29365954
*/
public var base64Decoded: String? {
guard let data = Data(base64Encoded: self, options: []) else { return nil }
return String(data: data, encoding: .utf8)
}
/**
Returns true if every character within the string is a numeric character. Empty strings are
considered non-numeric.
*/
public var isNumeric: Bool {
guard !isEmpty else { return false }
return trimmingCharacters(in: .decimalDigits).isEmpty
}
/**
Replaces all occurences of the pattern on self in-place.
Examples:
```
"hello".regexInPlace("[aeiou]", "*") // "h*ll*"
"hello".regexInPlace("([aeiou])", "<$1>") // "h<e>ll<o>"
```
*/
public mutating func formRegex(_ pattern: String, _ replacement: String) {
do {
let expression = try NSRegularExpression(pattern: pattern, options: [])
let range = NSRange(location: 0, length: count)
self = expression.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replacement)
}
catch { return }
}
/**
Returns a string containing replacements for all pattern matches.
Examples:
```
"hello".regex("[aeiou]", "*") // "h*ll*"
"hello".regex("([aeiou])", "<$1>") // "h<e>ll<o>"
```
*/
public func regex(_ pattern: String, _ replacement: String) -> String {
var replacementString = self
replacementString.formRegex(pattern, replacement)
return replacementString
}
/**
Replaces pattern-matched strings, operated upon by a closure, on self in-place.
- parameter pattern: The pattern to match against.
- parameter matches: The closure in which to handle matched strings.
Example:
```
"hello".regexInPlace(".") {
let s = $0.unicodeScalars
let v = s[s.startIndex].value
return "\(v) "
} // "104 101 108 108 111 "
*/
public mutating func formRegex(_ pattern: String, _ matches: (String) -> String) {
let expression: NSRegularExpression
do {
expression = try NSRegularExpression(pattern: "(\(pattern))", options: [])
}
catch {
print("regex error: \(error)")
return
}
let range = NSMakeRange(0, self.count)
var startOffset = 0
let results = expression.matches(in: self, options: [], range: range)
for result in results {
var endOffset = startOffset
for i in 1..<result.numberOfRanges {
var resultRange = result.range
resultRange.location += startOffset
let startIndex = self.index(self.startIndex, offsetBy: resultRange.location)
let endIndex = self.index(self.startIndex, offsetBy: resultRange.location + resultRange.length)
let replacementRange = startIndex ..< endIndex
let match = expression.replacementString(for: result, in: self, offset: startOffset, template: "$\(i)")
let replacement = matches(match)
self.replaceSubrange(replacementRange, with: replacement)
endOffset += replacement.count - resultRange.length
}
startOffset = endOffset
}
}
/**
Returns a string with pattern-matched strings, operated upon by a closure.
- parameter pattern: The pattern to match against.
- parameter matches: The closure in which to handle matched strings.
- returns: String containing replacements for the matched pattern.
Example:
```
"hello".regex(".") {
let s = $0.unicodeScalars
let v = s[s.startIndex].value
return "\(v) "
} // "104 101 108 108 111 "
*/
public func regex(_ pattern: String, _ matches: (String) -> String) -> String {
var replacementString = self
replacementString.formRegex(pattern, matches)
return replacementString
}
/// Substring at index
public subscript(i: Int) -> String {
let index = safeIndex(offset: i)
let contains = index.flatMap(indices.contains) ?? false
return contains ? String(self[index!]) : ""
}
/// Substring for range
public subscript(r: Range<Int>) -> String {
return self[r.lowerBound ... (r.upperBound - 1)]
}
/// Substring for closed range
public subscript(r: ClosedRange<Int>) -> String {
let startIndex = safeIndex(offset: r.lowerBound)
let endIndex = safeIndex(offset: r.upperBound)
let containsStart = startIndex.flatMap(indices.contains) ?? false
let containsEnd = endIndex.flatMap(indices.contains) ?? false
switch (containsStart, containsEnd) {
case (true, true): return String(self[startIndex! ... endIndex!])
case (true, false): return String(self[startIndex!...])
case (false, true): return String(self[...endIndex!])
case (false, false): return ""
}
}
/// Substring for countable partial range
public subscript(r: CountablePartialRangeFrom<Int>) -> String {
let index = safeIndex(offset: r.lowerBound)
let contains = index.flatMap(indices.contains) ?? false
return contains ? String(self[index!...]) : ""
}
/// Substring for partial range through upper bound
public subscript(r: PartialRangeThrough<Int>) -> String {
let index = safeIndex(offset: r.upperBound)
let contains = index.flatMap(indices.contains) ?? false
return contains ? String(self[...index!]) : ""
}
/// Substring for partial range up to upper bound
public subscript(r: PartialRangeUpTo<Int>) -> String {
let index = safeIndex(offset: r.upperBound)
let contains = index.flatMap(indices.contains) ?? false
return contains ? String(self[..<index!]) : ""
}
/**
Truncates the string to length characters, optionally appending a trailing string. If the string is shorter
than the required length, then this function is a non-op.
- parameter length: The length of string required.
- parameter trailing: An optional addition to the end of the string (increasing "length"), such as ellipsis.
- returns: The truncated string.
Examples:
```
"hello there".truncated(to: 5) // "hello"
"hello there".truncated(to: 5, trailing: "...") // "hello..."
```
*/
public func truncated(to length: Int, trailing: String = "") -> String {
guard !isEmpty && count > length else { return self }
return self[..<length] + trailing
}
public mutating func truncate(to length: Int, trailing: String = "") {
self = truncated(to: length, trailing: trailing)
}
/**
A bridge for invoking `String.localizedStandardContainsString()`, which is available in iOS 9 and later. If you need to
support iOS versions prior to iOS 9, use `compatibleStandardContainsString()` as a means to bridge functionality.
If you can support iOS 9 or greater only, use `localizedStandardContainsString()` directly.
From Apple's Swift 2.1 documentation:
`localizedStandardContainsString()` is the most appropriate method for doing user-level string searches, similar to how searches are done generally in the system. The search is locale-aware, case and diacritic insensitive. The exact list of search options applied may change over time.
- parameter string: The string to determine if is contained by self.
- returns: Returns true if self contains string, taking the current locale into account.
*/
public func compatibleStandardContains(_ string: String) -> Bool {
if #available(iOS 9.0, *) {
return localizedStandardContains(string)
}
return range(of: string, options: [.caseInsensitive, .diacriticInsensitive], locale: .current) != nil
}
/**
Convert an NSRange to a Range. There is still a mismatch between the regular expression libraries
and NSString/String. This makes it easier to convert between the two. Using this allows complex
strings (including emoji, regonial indicattors, etc.) to be manipulated without having to resort
to NSString instances.
Note that it may not always be possible to convert from an NSRange as they are not exactly the same.
Taken from:
http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index
- parameter nsRange: The NSRange instance to covert to a Range.
- returns: The Range, if it was possible to convert. Otherwise nil.
*/
public func range(from nsRange: NSRange) -> Range<String.Index>? {
return Range(nsRange, in: self)
}
/**
Convert a Range to an NSRange. There is still a mismatch between the regular expression libraries
and NSString/String. This makes it easier to convert between the two. Using this allows complex
strings (including emoji, regonial indicators, etc.) to be manipulated without having to resort
to NSString instances.
Taken from:
http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index
- parameter range: The Range instance to conver to an NSRange.
- returns: The NSRange converted from the input. This will always succeed.
*/
public func nsRange(from range: Range<String.Index>) -> NSRange {
return NSRange(range, in: self)
}
}
private extension String {
func safeIndex(offset: Int) -> String.Index? {
return index(startIndex, offsetBy: offset.limited(0, .max), limitedBy: endIndex)
}
}
+4 -4
View File
@@ -17,13 +17,13 @@ extension CAMediaTimingFunction {
public static func timingFunction(withCurve curve: TimingFunction) -> CAMediaTimingFunction {
switch curve {
case .linear:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
return CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
case .easeIn:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
return CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
case .easeOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
return CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
case .easeInOut:
return CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
case .spring:
return CAMediaTimingFunction(controlPoints: 0.5, 1.1 + Float(1/3), 1, 1)
case .easeInSine:
@@ -0,0 +1,460 @@
import Foundation
import UIKit
extension UIColor {
/**
Returns a UIColor from the given hexidecimal integer.
- parameter hex: The hex component of the color object, specified as a value from 0x000000 to 0xFFFFFF.
- parameter alpha: The opacity component of the color object, specified as a value from 0.0 to 1.0 (optional).
- returns: A UIColor initialized with the given color value.
*/
public convenience init(hex: UInt32, alpha: CGFloat = 1) {
assert((0...0xFFFFFF).contains(hex), "hex must be a value between 0x000000 and 0xFFFFFF")
assert((0...1).contains(alpha), "alpha must be a value between 0.0 and 1.0")
let (r, g, b) = Model.hex(hex).rgb
self.init(red: r/255, green: g/255, blue: b/255, alpha: alpha)
}
/**
Returns a UIColor from a hue-saturation-lightness (HSL) set.
- parameter hue: The hue component of the color object, specified as a value from 0.0 to 1.0.
- parameter saturation: The saturation component of the color object, specified as a value from 0.0 to 1.0.
- parameter lightness: The lightness component of the color object, specified as a value from 0.0 to 1.0.
- parameter alpha: The opacity component of the color object, specified as a value from 0.0 to 1.0 (optional).
- returns: A UIColor initialized with the given color value.
*/
public convenience init(hue: CGFloat, saturation: CGFloat, lightness: CGFloat, alpha: CGFloat = 1) {
assert((0...1).contains(hue), "hue value must be a value between 0.0 and 1.0")
assert((0...1).contains(saturation), "saturation must be a value between 0.0 and 1.0")
assert((0...1).contains(lightness), "lightness must be a value between 0.0 and 1.0")
assert((0...1).contains(alpha), "alpha must be a value between 0.0 and 1.0")
let (r, g, b) = Model.hsl(hue, saturation, lightness).rgb
self.init(red: r/255, green: g/255, blue: b/255, alpha: alpha)
}
/**
Returns a UIColor from a cyan-magenta-yellow-key (CMYK) set.
- parameter cyan: The cyan component of the color object, specified as a value from 0.0 to 1.0.
- parameter magenta: The magenta component of the color object, specified as a value from 0.0 to 1.0.
- parameter yellow: The yellow component of the color object, specified as a value from 0.0 to 1.0.
- parameter key: The key (black) component of the color object, specified as a value from 0.0 to 1.0.
- parameter alpha: The opacity component of the color object, specified as a value from 0.0 to 1.0 (optional).
- returns: A UIColor initialized with the given color value.
*/
public convenience init(cyan: CGFloat, magenta: CGFloat, yellow: CGFloat, key: CGFloat, alpha: CGFloat = 1) {
assert((0...1).contains(cyan), "cyan value must be a value between 0.0 and 1.0")
assert((0...1).contains(magenta), "magenta must be a value between 0.0 and 1.0")
assert((0...1).contains(yellow), "yellow must be a value between 0.0 and 1.0")
assert((0...1).contains(key), "key must be a value between 0.0 and 1.0")
assert((0...1).contains(alpha), "alpha must be a value between 0.0 and 1.0")
let (r, g, b) = Model.cmyk(cyan, magenta, yellow, key).rgb
self.init(red: r/255, green: g/255, blue: b/255, alpha: alpha)
}
/**
Returns a UIColor from a given hex color string.
- parameter hexString: The hex color string, e.g.: "#9443FB" or "9443FB".
- returns: A UIColor initialized with the color specified by the hexString.
*/
public convenience init(hexString: String, alpha: CGFloat = 1) {
var hexString = hexString
if hexString.hasPrefix("#") {
hexString = hexString[1...]
}
let scanner = Scanner(string: hexString)
var hexEquivalent: UInt32 = 0
if !scanner.scanHexInt32(&hexEquivalent) {
assertionFailure("hexString did not contain a valid hex value")
}
self.init(hex: hexEquivalent, alpha: alpha)
}
/**
Returns a UIColor initialized with color components divided by 255.0.
- parameter red: Integer representation of the red component in range of 0-255.
- parameter green: Integer representation of the green component in range of 0-255.
- parameter blue: Integer representation of the blue component in range of 0-255.
*/
public convenience init(red: UInt8, green: UInt8, blue: UInt8) {
self.init(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: 1.0)
}
/// Returns a random UIColor with hue, saturation, and brightness ranging from 0.5 to 1.0.
public static var random: UIColor {
let component = { CGFloat(arc4random() % 128)/256.0 + 0.5 }
return UIColor(hue: component(), saturation: component(), brightness: component(), alpha: 1)
}
/**
Lightens the given color by the given percentage.
- parameter amount: The percentage by which to lighten the color. Valid values are from `0.0` to `1.0`, or for a more readable format `0%` to `100%`.
- returns: The lightened color.
*/
public final func lightened(by amount: CGFloat) -> UIColor {
assert((0...1).contains(amount), "amount must be in range 0-100%")
let (h, s, l) = hsl
return UIColor(hue: h, saturation: s, lightness: l * (1 + amount), alpha: rgba.a)
}
/**
Darkens the given color by the given percentage.
- parameter amount: The percentage by which to darken the color. Valid values are from `0.0` to `1.0`, or for a more readable format `0%` to `100%`.
- returns: The darkened color.
*/
public final func darkened(by amount: CGFloat) -> UIColor {
assert((0...1).contains(amount), "amount must be in range 0-100%")
let (h, s, l) = hsl
return UIColor(hue: h, saturation: s, lightness: l * (1 - amount), alpha: rgba.a)
}
/**
Returns the color represenation as a hexadecimal string, prefixed with '#'.
- returns: The hexadecimal string representation of the color.
*/
public final var hexString: String {
return String(format:"#%06x", hex)
}
/**
Returns the color representation as a 32-bit integer.
- returns: A UInt32 that represents the hexadecimal color.
*/
public final var hex: UInt32 {
let (r, g, b, _) = self.rgba
return Model.rgb(r * 255, g * 255, b * 255).hex
}
/**
Returns the RGBA (red, green, blue, alpha) components, specified as values from 0.0 to 1.0.
- returns: The RGBA components as a tuple (r, g, b, a).
*/
public final var rgba: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
getRed(&r, green: &g, blue: &b, alpha: &a)
return (r, g, b, a)
}
/**
Returns the HSL (hue, saturation, lightness) components, specified as values from 0.0 to 1.0.
- returns: The HSL components as a tuple (h, s, l).
*/
public final var hsl: (h: CGFloat, s: CGFloat, l: CGFloat) {
let (r, g, b, _) = rgba
return Model.rgb(r * 255, g * 255, b * 255).hsl
}
/**
Returns the HSB (hue, saturation, brightness) components, specified as values from 0.0 to 1.0.
- returns: The HSB components as a tuple (h, s, b).
*/
public final var hsb: (h: CGFloat, s: CGFloat, b: CGFloat) {
let (r, g, b, _) = rgba
return Model.rgb(r * 255, g * 255, b * 255).hsb
}
/**
Returns the CMYK (cyan, magenta, yellow, key) components, specified as values from 0.0 to 1.0.
- returns: The CMYK components as a tuple (c, m, y, k).
*/
public final var cmyk: (c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat) {
let (r, g, b, _) = rgba
return Model.rgb(r * 255, g * 255, b * 255).cmyk
}
/**
Returns an alpha-adjusted UIColor.
- returns: A UIColor with an adjust alpha component (shorthand for `colorWithAlphaComponent`).
*/
public final func alpha(_ alpha: CGFloat) -> UIColor {
return withAlphaComponent(alpha)
}
/**
Returns a UIColor from the given hexidecimal integer.
- parameter hex: The color value.
- parameter alpha: The alpha component.
- returns: A UIColor initialized with the given hex value.
*/
public static func hex(_ hex: UInt32, alpha: CGFloat = 1) -> UIColor {
return UIColor(hex: hex, alpha: alpha)
}
/**
Returns a UIColor from the given RGB components.
- parameter red: The red component in range of 0-255.
- parameter green: The green component in range of 0-255.
- parameter blue: The blue component in range of 0-255.
- parameter alpha: The alpha component.
- returns: A UIColor initialized with the given RGB components.
*/
public static func rgb(_ red: UInt8, _ green: UInt8, _ blue: UInt8, alpha: CGFloat = 1) -> UIColor {
return UIColor(red: red, green: green, blue: blue).alpha(alpha)
}
}
// MARK: - Model
extension UIColor {
/**
Model is an enum for describing and converting color models.
- `rgb`: Red, Green, Blue color representation
- `hsl`: Hue, Saturation, Lightness color representation
- `hsb`: Hue, Saturation, Brightness color representation
- `cmyk`: Cyan, Magenta, Yellow, Key (Black) color representation
- `hex`: UInt32 (hex) color representation
*/
public enum Model {
/// Red, Green, Blue
case rgb(CGFloat, CGFloat, CGFloat)
/// Hue, Saturation, Lightness
case hsl(CGFloat, CGFloat, CGFloat)
/// Hue, Saturation, Brightness
case hsb(CGFloat, CGFloat, CGFloat)
/// Cyan, Magenta, Yellow, Key (Black)
case cmyk(CGFloat, CGFloat, CGFloat, CGFloat)
/// UInt32 (hex)
case hex(UInt32)
/// Returns the model as an RGB tuple
public var rgb: (r: CGFloat, g: CGFloat, b: CGFloat) {
switch self {
case .rgb(let rgb):
return rgb
case .hsl(let h, let s, let l):
return convert(hsl: h, s, l)
case .hsb(let h, let s, let b):
return convert(hsb: h, s, b)
case .cmyk(let c, let m, let y, let k):
return convert(cmyk: c, m, y, k)
case .hex(let hex):
return convert(hex: hex)
}
}
/// Returns the model as an HSL tuple
public var hsl: (h: CGFloat, s: CGFloat, l: CGFloat) {
switch self {
case .rgb(let r, let g, let b):
return convert(rgb: r, g, b)
case .hsl(let hsl):
return hsl
case .hsb, .cmyk, .hex:
let (r, g, b) = self.rgb
return convert(rgb: r, g, b)
}
}
/// Returns the model as an HSB tuple
public var hsb: (h: CGFloat, s: CGFloat, b: CGFloat) {
switch self {
case .rgb(let r, let g, let b):
return convert(rgb: r, g, b)
case .hsl, .cmyk, .hex:
let (r, g, b) = self.rgb
return convert(rgb: r, g, b)
case .hsb(let hsb):
return hsb
}
}
/// Returns the model as a CMYK tuple
public var cmyk: (c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat) {
switch self {
case .rgb(let r, let g, let b):
return convert(rgb: r, g, b)
case .hsl, .hsb, .hex:
let (r, g, b) = self.rgb
return convert(rgb: r, g, b)
case .cmyk(let cmyk):
return cmyk
}
}
/// Returns the model as a UInt32 (hex) value
public var hex: UInt32 {
switch self {
case .rgb(let r, let g, let b):
return convert(rgb: r, g, b)
case .hsl, .hsb, .cmyk:
let (r, g, b) = self.rgb
return convert(rgb: r, g, b)
case .hex(let hex):
return hex
}
}
}
}
// MARK: - Private color model conversions
/// Converts RGB to HSL (https://en.wikipedia.org/wiki/HSL_and_HSV)
private func convert(rgb r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> (h: CGFloat, s: CGFloat, l: CGFloat) {
let r = r / 255
let g = g / 255
let b = b / 255
let max = Swift.max(r, g, b)
let min = Swift.min(r, g, b)
var h, s: CGFloat
let l = (max + min) / 2
if max == min {
h = 0
s = 0
}
else {
let d = max - min
s = (l > 0.5) ? d / (2 - max - min) : d / (max + min)
switch max {
case r: h = (g - b) / d + (g < b ? 6 : 0)
case g: h = (b - r) / d + 2
case b: h = (r - g) / d + 4
default: h = 0
}
h /= 6;
}
return (h, s, l)
}
/// Converts HSL to RGB (https://en.wikipedia.org/wiki/HSL_and_HSV)
private func convert(hsl h: CGFloat, _ s: CGFloat, _ l: CGFloat) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
let r, g, b: CGFloat
if s == 0 {
r = l
g = l
b = l
}
else {
let c = (1 - abs(2 * l - 1)) * s
let x = c * (1 - abs((h * 6).truncatingRemainder(dividingBy: 2) - 1))
let m = l - c/2
switch h * 6 {
case 0..<1: (r, g, b) = (c, x, 0) + m
case 1..<2: (r, g, b) = (x, c, 0) + m
case 2..<3: (r, g, b) = (0, c, x) + m
case 3..<4: (r, g, b) = (0, x, c) + m
case 4..<5: (r, g, b) = (x, 0, c) + m
case 5..<6: (r, g, b) = (c, 0, x) + m
default: (r, g, b) = (0, 0, 0) + m
}
}
return (round(r * 255), round(g * 255), round(b * 255))
}
/// Converts RGB to HSB (https://en.wikipedia.org/wiki/HSL_and_HSV)
private func convert(rgb r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> (h: CGFloat, s: CGFloat, b: CGFloat) {
var h, s, v: CGFloat
let r = r / 255
let g = g / 255
let b = b / 255
let max = Swift.max(r, g, b)
let min = Swift.min(r, g, b)
let d = max - min
if d == 0 {
h = 0
s = 0
}
else {
s = (max == 0) ? 0 : d / max
switch max {
case r: h = ((g - b) / d) + (g < b ? 6 : 0)
case g: h = ((b - r) / d) + 2
case b: h = ((r - g) / d) + 4
default: h = 0
}
h /= 6;
}
v = max
return (h, s, v)
}
/// Converts HSB to RGB (https://en.wikipedia.org/wiki/HSL_and_HSV)
private func convert(hsb h: CGFloat, _ s: CGFloat, _ b: CGFloat) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
let c = b * s
let x = c * (1 - abs((h * 6).truncatingRemainder(dividingBy: 2) - 1))
let m = b - c
var r, g, b: CGFloat
switch h * 6 {
case 0..<1: (r, g, b) = (c, x, 0) + m
case 1..<2: (r, g, b) = (x, c, 0) + m
case 2..<3: (r, g, b) = (0, c, x) + m
case 3..<4: (r, g, b) = (0, x, c) + m
case 4..<5: (r, g, b) = (x, 0, c) + m
case 5..<6: (r, g, b) = (c, 0, x) + m
default: (r, g, b) = (0, 0, 0) + m
}
return (round(r * 255), round(g * 255), round(b * 255))
}
/// Converts UInt32 to RGB
private func convert(hex: UInt32) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
let r = CGFloat((hex >> 16) & 0xFF)
let g = CGFloat((hex >> 8) & 0xFF)
let b = CGFloat(hex & 0xFF)
return (r, g, b)
}
/// Converts RGB to UInt32
private func convert(rgb r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> UInt32 {
return (UInt32(r) << 16) | (UInt32(g) << 8) | UInt32(b)
}
/// Converts RGB to CMYK (http://www.rapidtables.com/convert/color/rgb-to-cmyk.htm)
private func convert(rgb r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> (c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat) {
let r = r / 255
let g = g / 255
let b = b / 255
let k = 1 - max(r, g, b)
let c = (k == 1) ? 0 : (1 - r - k) / (1 - k)
let m = (k == 1) ? 0 : (1 - g - k) / (1 - k)
let y = (k == 1) ? 0 : (1 - b - k) / (1 - k)
return (c, m, y, k)
}
/// Converts CMYK to RGB (http://www.rapidtables.com/convert/color/cmyk-to-rgb.htm)
private func convert(cmyk c: CGFloat, _ m: CGFloat, _ y: CGFloat, _ k: CGFloat) -> (r: CGFloat, g: CGFloat, b: CGFloat) {
let r = 255 * (1 - c) * (1 - k)
let g = 255 * (1 - m) * (1 - k)
let b = 255 * (1 - y) * (1 - k)
return (round(r), round(g), round(b))
}
/// Private operator for HSL and HSB conversion
private func +(lhs: (CGFloat, CGFloat, CGFloat), rhs: CGFloat) -> (CGFloat, CGFloat, CGFloat) {
return (lhs.0 + rhs, lhs.1 + rhs, lhs.2 + rhs)
}
-14
View File
@@ -1,14 +0,0 @@
import Foundation
import UIKit
extension UIColor {
/// Returns a random UIColor
public static var random: UIColor {
let hue : CGFloat = CGFloat(arc4random() % 256) / 256 // use 256 to get full range from 0.0 to 1.0
let saturation : CGFloat = CGFloat(arc4random() % 128) / 256 + 0.5 // from 0.5 to 1.0 to stay away from white
let brightness : CGFloat = CGFloat(arc4random() % 128) / 256 + 0.5 // from 0.5 to 1.0 to stay away from black
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1)
}
}