├── .github └── workflows │ └── Tests.yml ├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── eon.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ └── eon.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── FlowLayout.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ ├── andrejorgensen.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── eon.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── FlowLayout.xcscheme └── xcuserdata │ └── eon.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── FlowLayout ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── ViewController+Create.swift └── ViewController.swift ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── FlowLayout │ ├── Header │ ├── ButtonContainer │ │ ├── ButtonContainer+Const.swift │ │ ├── ButtonContainer+Create.swift │ │ ├── ButtonContainer+Interaction.swift │ │ ├── ButtonContainer+Setter.swift │ │ ├── ButtonContainer+TypeAlias.swift │ │ ├── ButtonContainer.swift │ │ └── HeaderButton │ │ │ ├── CustomButton.swift │ │ │ ├── HeaderButton+Const.swift │ │ │ ├── HeaderButton+Interactive.swift │ │ │ ├── HeaderButton+Setter.swift │ │ │ ├── HeaderButton+Type.swift │ │ │ └── HeaderButton.swift │ ├── Header+Const.swift │ ├── Header+Create.swift │ ├── Header+Setter.swift │ ├── Header.swift │ ├── HeaderTitle │ │ ├── HeaderTitle+Const.swift │ │ ├── HeaderTitle+Setter.swift │ │ └── HeaderTitle.swift │ └── Slider │ │ ├── Slider+Animation.swift │ │ ├── Slider+Const.swift │ │ ├── Slider+Create.swift │ │ ├── Slider+Setter.swift │ │ ├── Slider.swift │ │ └── SliderBar │ │ └── SliderBar.swift │ ├── HorView │ ├── HorView+Collection.swift │ ├── HorView+Create.swift │ ├── HorView+Event.swift │ ├── HorView+Getter.swift │ ├── HorView+Register.swift │ ├── HorView+Scroll.swift │ ├── HorView+Setter.swift │ ├── HorView+Style.swift │ ├── HorView+Update.swift │ ├── HorView.swift │ └── utils │ │ └── ColumnCellType.swift │ ├── cell │ └── horizontal │ │ ├── HorCell │ │ ├── HorCell+Collection.swift │ │ ├── HorCell+Const.swift │ │ ├── HorCell+Core.swift │ │ ├── HorCell+Create.swift │ │ ├── HorCell+Getter.swift │ │ ├── HorCell+Scroll.swift │ │ ├── HorCell+Type.swift │ │ ├── HorCell+Update.swift │ │ └── HorCell.swift │ │ ├── VerCell │ │ ├── VerCell.swift │ │ └── custom │ │ │ ├── ImageCell │ │ │ ├── ImageCell+Create.swift │ │ │ └── ImageCell.swift │ │ │ └── PrimaryVerCell │ │ │ ├── PrimaryVerCell+Core.swift │ │ │ └── PrimaryVerCell.swift │ │ └── custom │ │ ├── PrimaryCellData │ │ ├── CellDataKind.swift │ │ └── PrimaryCellData.swift │ │ ├── PrimaryHorCell │ │ ├── PrimaryHorCell+Core.swift │ │ ├── PrimaryHorCell+Getter.swift │ │ ├── PrimaryHorCell+Interaction.swift │ │ └── PrimaryHorCell.swift │ │ ├── SecondaryHorCell │ │ ├── SecondaryHorCell+Const.swift │ │ ├── SecondaryHorCell+Create.swift │ │ └── SecondaryHorCell.swift │ │ └── TertiaryHorCell │ │ └── TertiaryHorCell.swift │ └── common │ ├── CGShapeUtil.swift │ ├── UIColorParser.swift │ ├── UIView+Extension.swift │ └── image │ ├── UIImage+Extension.swift │ └── UIImageView+Extension.swift ├── Tests └── FlowLayoutTests │ └── FlowLayoutTests.swift └── demo └── CustomView.swift /.github/workflows/Tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: "0 20 * * 2-2" 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: macos-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Build 19 | run: swift build -v 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | whitelist_rules: 2 | - anyobject_protocol 3 | - array_init 4 | #- attributes 5 | - block_based_kvo 6 | - class_delegate_protocol 7 | - closing_brace 8 | - closure_end_indentation 9 | - closure_parameter_position 10 | - closure_spacing 11 | - collection_alignment 12 | - colon 13 | - comma 14 | - compiler_protocol_init 15 | # - conditional_returns_on_newline 16 | - contains_over_first_not_nil 17 | - control_statement 18 | - deployment_target 19 | - discarded_notification_center_observer 20 | - discouraged_direct_init 21 | - discouraged_object_literal 22 | - discouraged_optional_boolean 23 | # - discouraged_optional_collection 24 | - duplicate_imports 25 | - dynamic_inline 26 | - empty_count 27 | - empty_enum_arguments 28 | - empty_parameters 29 | - empty_parentheses_with_trailing_closure 30 | - empty_string 31 | - empty_xctest_method 32 | - explicit_init 33 | - fallthrough 34 | - fatal_error_message 35 | - first_where 36 | - for_where 37 | - generic_type_name 38 | - identical_operands 39 | - identifier_name 40 | - implicit_getter 41 | - implicit_return 42 | - inert_defer 43 | - is_disjoint 44 | - joined_default_parameter 45 | - last_where 46 | - leading_whitespace 47 | - legacy_cggeometry_functions 48 | - legacy_constant 49 | - legacy_constructor 50 | - legacy_hashing 51 | - legacy_nsgeometry_functions 52 | - legacy_random 53 | - literal_expression_end_indentation 54 | - lower_acl_than_parent 55 | - mark 56 | - modifier_order 57 | - multiline_arguments 58 | # - multiline_function_chains 59 | - multiline_literal_brackets 60 | - multiline_parameters 61 | - multiline_parameters_brackets 62 | - multiple_closures_with_trailing_closure 63 | - nimble_operator 64 | - no_extension_access_modifier 65 | - no_fallthrough_only 66 | - notification_center_detachment 67 | - number_separator 68 | - object_literal 69 | - opening_brace 70 | - operator_usage_whitespace 71 | - operator_whitespace 72 | - overridden_super_call 73 | - pattern_matching_keywords 74 | - private_action 75 | # - private_outlet 76 | - private_unit_test 77 | - prohibited_super_call 78 | - protocol_property_accessors_order 79 | - redundant_discardable_let 80 | - redundant_nil_coalescing 81 | - redundant_objc_attribute 82 | - redundant_optional_initialization 83 | - redundant_set_access_control 84 | - redundant_string_enum_value 85 | - redundant_type_annotation 86 | - redundant_void_return 87 | - required_enum_case 88 | - return_arrow_whitespace 89 | - shorthand_operator 90 | - sorted_first_last 91 | # - statement_position 92 | - static_operator 93 | # - strong_iboutlet 94 | - superfluous_disable_command 95 | - switch_case_alignment 96 | # - switch_case_on_newline 97 | - syntactic_sugar 98 | - todo 99 | - toggle_bool 100 | - trailing_closure 101 | - trailing_comma 102 | - trailing_newline 103 | - trailing_semicolon 104 | - trailing_whitespace 105 | - type_name 106 | # - unavailable_function 107 | - unneeded_break_in_switch 108 | - unneeded_parentheses_in_closure_argument 109 | #- untyped_error_in_catch 110 | - unused_closure_parameter 111 | - unused_control_flow_label 112 | - unused_enumerated 113 | - unused_optional_binding 114 | - unused_setter_value 115 | - valid_ibinspectable 116 | - vertical_parameter_alignment 117 | - vertical_parameter_alignment_on_call 118 | - vertical_whitespace_closing_braces 119 | - vertical_whitespace_opening_braces 120 | - void_return 121 | - weak_computed_property 122 | - weak_delegate 123 | - xct_specific_matcher 124 | - xctfail_message 125 | - yoda_condition 126 | analyzer_rules: 127 | - unused_import 128 | - unused_private_declaration 129 | force_cast: warning 130 | force_unwrapping: warning 131 | number_separator: 132 | minimum_length: 5 133 | object_literal: 134 | image_literal: false 135 | discouraged_object_literal: 136 | color_literal: false 137 | identifier_name: 138 | max_length: 139 | warning: 100 140 | error: 100 141 | min_length: 142 | warning: 1 143 | error: 1 144 | validates_start_with_lowercase: false 145 | allowed_symbols: 146 | - '_' 147 | excluded: 148 | - 'x' 149 | - 'y' 150 | - 'a' 151 | - 'b' 152 | - 'x1' 153 | - 'x2' 154 | - 'y1' 155 | - 'y2' 156 | macOS_deployment_target: '10.12' 157 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcuserdata/eon.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eonist/FlowLayout/f3148776c7852196b47383b92651661372402e3e/.swiftpm/xcode/package.xcworkspace/xcuserdata/eon.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /.swiftpm/xcode/xcuserdata/eon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FlowLayout.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F116EBD7211046B40091439E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F116EBD6211046B40091439E /* AppDelegate.swift */; }; 11 | F116EBDE211046B70091439E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F116EBDD211046B70091439E /* Assets.xcassets */; }; 12 | F116EBE1211046B70091439E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F116EBDF211046B70091439E /* LaunchScreen.storyboard */; }; 13 | F116EC3C2112E4A40091439E /* ViewController+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = F116EC3B2112E4A40091439E /* ViewController+Create.swift */; }; 14 | F12441F52248001700D808D8 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12441F42248001700D808D8 /* ViewController.swift */; }; 15 | F12442442249031700D808D8 /* CustomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12442432249031700D808D8 /* CustomView.swift */; }; 16 | F14965AB24323AAC008C1847 /* HorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655624323AAC008C1847 /* HorView.swift */; }; 17 | F14965AC24323AAC008C1847 /* HorView+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655724323AAC008C1847 /* HorView+Collection.swift */; }; 18 | F14965AD24323AAC008C1847 /* ColumnCellType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655824323AAC008C1847 /* ColumnCellType.swift */; }; 19 | F14965AE24323AAC008C1847 /* HorView+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655924323AAC008C1847 /* HorView+Style.swift */; }; 20 | F14965AF24323AAC008C1847 /* HorView+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655A24323AAC008C1847 /* HorView+Create.swift */; }; 21 | F14965B024323AAC008C1847 /* HorView+Register.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655B24323AAC008C1847 /* HorView+Register.swift */; }; 22 | F14965B124323AAC008C1847 /* HorView+Getter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655C24323AAC008C1847 /* HorView+Getter.swift */; }; 23 | F14965B224323AAC008C1847 /* HorView+Scroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655D24323AAC008C1847 /* HorView+Scroll.swift */; }; 24 | F14965B324323AAC008C1847 /* HorView+Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655E24323AAC008C1847 /* HorView+Update.swift */; }; 25 | F14965B424323AAC008C1847 /* HorView+Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149655F24323AAC008C1847 /* HorView+Setter.swift */; }; 26 | F14965B524323AAC008C1847 /* ImageCell+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656524323AAC008C1847 /* ImageCell+Create.swift */; }; 27 | F14965B624323AAC008C1847 /* ImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656624323AAC008C1847 /* ImageCell.swift */; }; 28 | F14965B724323AAC008C1847 /* PrimaryVerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656824323AAC008C1847 /* PrimaryVerCell.swift */; }; 29 | F14965B824323AAC008C1847 /* PrimaryVerCell+Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656924323AAC008C1847 /* PrimaryVerCell+Core.swift */; }; 30 | F14965B924323AAC008C1847 /* VerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656A24323AAC008C1847 /* VerCell.swift */; }; 31 | F14965BA24323AAC008C1847 /* HorCell+Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656C24323AAC008C1847 /* HorCell+Update.swift */; }; 32 | F14965BB24323AAC008C1847 /* HorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656D24323AAC008C1847 /* HorCell.swift */; }; 33 | F14965BC24323AAC008C1847 /* HorCell+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656E24323AAC008C1847 /* HorCell+Collection.swift */; }; 34 | F14965BD24323AAC008C1847 /* HorCell+Getter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149656F24323AAC008C1847 /* HorCell+Getter.swift */; }; 35 | F14965BE24323AAC008C1847 /* HorCell+Scroll.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657024323AAC008C1847 /* HorCell+Scroll.swift */; }; 36 | F14965BF24323AAC008C1847 /* HorCell+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657124323AAC008C1847 /* HorCell+Const.swift */; }; 37 | F14965C024323AAC008C1847 /* HorCell+Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657224323AAC008C1847 /* HorCell+Core.swift */; }; 38 | F14965C124323AAC008C1847 /* HorCell+Type.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657324323AAC008C1847 /* HorCell+Type.swift */; }; 39 | F14965C224323AAC008C1847 /* HorCell+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657424323AAC008C1847 /* HorCell+Create.swift */; }; 40 | F14965C324323AAC008C1847 /* SecondaryHorCell+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657724323AAC008C1847 /* SecondaryHorCell+Create.swift */; }; 41 | F14965C424323AAC008C1847 /* SecondaryHorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657824323AAC008C1847 /* SecondaryHorCell.swift */; }; 42 | F14965C524323AAC008C1847 /* SecondaryHorCell+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657924323AAC008C1847 /* SecondaryHorCell+Const.swift */; }; 43 | F14965C624323AAC008C1847 /* TertiaryHorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657B24323AAC008C1847 /* TertiaryHorCell.swift */; }; 44 | F14965C724323AAC008C1847 /* PrimaryHorCell+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657D24323AAC008C1847 /* PrimaryHorCell+Interaction.swift */; }; 45 | F14965C824323AAC008C1847 /* PrimaryHorCell+Getter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657E24323AAC008C1847 /* PrimaryHorCell+Getter.swift */; }; 46 | F14965C924323AAC008C1847 /* PrimaryHorCell+Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149657F24323AAC008C1847 /* PrimaryHorCell+Core.swift */; }; 47 | F14965CA24323AAC008C1847 /* PrimaryHorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658024323AAC008C1847 /* PrimaryHorCell.swift */; }; 48 | F14965CB24323AAC008C1847 /* CellDataKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658224323AAC008C1847 /* CellDataKind.swift */; }; 49 | F14965CC24323AAC008C1847 /* PrimaryCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658324323AAC008C1847 /* PrimaryCellData.swift */; }; 50 | F14965CD24323AAC008C1847 /* CGShapeUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658524323AAC008C1847 /* CGShapeUtil.swift */; }; 51 | F14965CE24323AAC008C1847 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658624323AAC008C1847 /* UIView+Extension.swift */; }; 52 | F14965CF24323AAC008C1847 /* UIColorParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658724323AAC008C1847 /* UIColorParser.swift */; }; 53 | F14965D024323AAC008C1847 /* UIImageView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658924323AAC008C1847 /* UIImageView+Extension.swift */; }; 54 | F14965D124323AAC008C1847 /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658A24323AAC008C1847 /* UIImage+Extension.swift */; }; 55 | F14965D224323AAC008C1847 /* SliderBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658E24323AAC008C1847 /* SliderBar.swift */; }; 56 | F14965D324323AAC008C1847 /* Slider+Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149658F24323AAC008C1847 /* Slider+Setter.swift */; }; 57 | F14965D424323AAC008C1847 /* Slider+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659024323AAC008C1847 /* Slider+Animation.swift */; }; 58 | F14965D524323AAC008C1847 /* Slider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659124323AAC008C1847 /* Slider.swift */; }; 59 | F14965D624323AAC008C1847 /* Slider+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659224323AAC008C1847 /* Slider+Create.swift */; }; 60 | F14965D724323AAC008C1847 /* Header+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659324323AAC008C1847 /* Header+Create.swift */; }; 61 | F14965D824323AAC008C1847 /* ButtonContainer+Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659524323AAC008C1847 /* ButtonContainer+Setter.swift */; }; 62 | F14965D924323AAC008C1847 /* HeaderButton+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659724323AAC008C1847 /* HeaderButton+Const.swift */; }; 63 | F14965DA24323AAC008C1847 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659824323AAC008C1847 /* CustomButton.swift */; }; 64 | F14965DB24323AAC008C1847 /* HeaderButton+Type.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659924323AAC008C1847 /* HeaderButton+Type.swift */; }; 65 | F14965DC24323AAC008C1847 /* HeaderButton+Interactive.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659A24323AAC008C1847 /* HeaderButton+Interactive.swift */; }; 66 | F14965DD24323AAC008C1847 /* HeaderButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659B24323AAC008C1847 /* HeaderButton.swift */; }; 67 | F14965DE24323AAC008C1847 /* HeaderButton+Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659C24323AAC008C1847 /* HeaderButton+Setter.swift */; }; 68 | F14965DF24323AAC008C1847 /* ButtonContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659D24323AAC008C1847 /* ButtonContainer.swift */; }; 69 | F14965E024323AAC008C1847 /* ButtonContainer+TypeAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659E24323AAC008C1847 /* ButtonContainer+TypeAlias.swift */; }; 70 | F14965E124323AAC008C1847 /* ButtonContainer+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = F149659F24323AAC008C1847 /* ButtonContainer+Create.swift */; }; 71 | F14965E224323AAC008C1847 /* ButtonContainer+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14965A024323AAC008C1847 /* ButtonContainer+Interaction.swift */; }; 72 | F14965E324323AAC008C1847 /* ButtonContainer+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14965A124323AAC008C1847 /* ButtonContainer+Const.swift */; }; 73 | F14965E424323AAC008C1847 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14965A224323AAC008C1847 /* Header.swift */; }; 74 | F14965E524323AAC008C1847 /* Header+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14965A524323AAC008C1847 /* Header+Const.swift */; }; 75 | F14965E624323AAC008C1847 /* HeaderTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14965A724323AAC008C1847 /* HeaderTitle.swift */; }; 76 | F14965E724323AAC008C1847 /* HeaderTitle+Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14965A824323AAC008C1847 /* HeaderTitle+Setter.swift */; }; 77 | F14965E824323AAC008C1847 /* HeaderTitle+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14965A924323AAC008C1847 /* HeaderTitle+Const.swift */; }; 78 | F14965E924323AAC008C1847 /* Header+Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14965AA24323AAC008C1847 /* Header+Setter.swift */; }; 79 | F14965EC24323B0A008C1847 /* JSONSugar in Frameworks */ = {isa = PBXBuildFile; productRef = F14965EB24323B0A008C1847 /* JSONSugar */; }; 80 | F14965EF24323B22008C1847 /* ImageSugar in Frameworks */ = {isa = PBXBuildFile; productRef = F14965EE24323B22008C1847 /* ImageSugar */; }; 81 | F14965F224323D2B008C1847 /* CommonCell in Frameworks */ = {isa = PBXBuildFile; productRef = F14965F124323D2B008C1847 /* CommonCell */; }; 82 | F14965F524323D7B008C1847 /* NetworkSugar in Frameworks */ = {isa = PBXBuildFile; productRef = F14965F424323D7B008C1847 /* NetworkSugar */; }; 83 | F15CB292268D262D00C5C303 /* Spatial in Frameworks */ = {isa = PBXBuildFile; productRef = F15CB291268D262D00C5C303 /* Spatial */; }; 84 | F15CB295268D265200C5C303 /* With in Frameworks */ = {isa = PBXBuildFile; productRef = F15CB294268D265200C5C303 /* With */; }; 85 | F1FB41582443556000A433E2 /* HorView+Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FB41572443556000A433E2 /* HorView+Event.swift */; }; 86 | F1FB415A2443599600A433E2 /* Slider+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FB41592443599600A433E2 /* Slider+Const.swift */; }; 87 | /* End PBXBuildFile section */ 88 | 89 | /* Begin PBXCopyFilesBuildPhase section */ 90 | F12441E82247F0F800D808D8 /* Embed Frameworks */ = { 91 | isa = PBXCopyFilesBuildPhase; 92 | buildActionMask = 2147483647; 93 | dstPath = ""; 94 | dstSubfolderSpec = 10; 95 | files = ( 96 | ); 97 | name = "Embed Frameworks"; 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | /* End PBXCopyFilesBuildPhase section */ 101 | 102 | /* Begin PBXFileReference section */ 103 | F116EBD3211046B40091439E /* FlowLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlowLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; 104 | F116EBD6211046B40091439E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 105 | F116EBDD211046B70091439E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 106 | F116EBE0211046B70091439E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 107 | F116EBE2211046B70091439E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 108 | F116EC3B2112E4A40091439E /* ViewController+Create.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController+Create.swift"; sourceTree = ""; }; 109 | F12441F42248001700D808D8 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 110 | F12442432249031700D808D8 /* CustomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomView.swift; sourceTree = ""; }; 111 | F149655624323AAC008C1847 /* HorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorView.swift; sourceTree = ""; }; 112 | F149655724323AAC008C1847 /* HorView+Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorView+Collection.swift"; sourceTree = ""; }; 113 | F149655824323AAC008C1847 /* ColumnCellType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColumnCellType.swift; sourceTree = ""; }; 114 | F149655924323AAC008C1847 /* HorView+Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorView+Style.swift"; sourceTree = ""; }; 115 | F149655A24323AAC008C1847 /* HorView+Create.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorView+Create.swift"; sourceTree = ""; }; 116 | F149655B24323AAC008C1847 /* HorView+Register.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorView+Register.swift"; sourceTree = ""; }; 117 | F149655C24323AAC008C1847 /* HorView+Getter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorView+Getter.swift"; sourceTree = ""; }; 118 | F149655D24323AAC008C1847 /* HorView+Scroll.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorView+Scroll.swift"; sourceTree = ""; }; 119 | F149655E24323AAC008C1847 /* HorView+Update.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorView+Update.swift"; sourceTree = ""; }; 120 | F149655F24323AAC008C1847 /* HorView+Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorView+Setter.swift"; sourceTree = ""; }; 121 | F149656524323AAC008C1847 /* ImageCell+Create.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ImageCell+Create.swift"; sourceTree = ""; }; 122 | F149656624323AAC008C1847 /* ImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCell.swift; sourceTree = ""; }; 123 | F149656824323AAC008C1847 /* PrimaryVerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimaryVerCell.swift; sourceTree = ""; }; 124 | F149656924323AAC008C1847 /* PrimaryVerCell+Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PrimaryVerCell+Core.swift"; sourceTree = ""; }; 125 | F149656A24323AAC008C1847 /* VerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerCell.swift; sourceTree = ""; }; 126 | F149656C24323AAC008C1847 /* HorCell+Update.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorCell+Update.swift"; sourceTree = ""; }; 127 | F149656D24323AAC008C1847 /* HorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorCell.swift; sourceTree = ""; }; 128 | F149656E24323AAC008C1847 /* HorCell+Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorCell+Collection.swift"; sourceTree = ""; }; 129 | F149656F24323AAC008C1847 /* HorCell+Getter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorCell+Getter.swift"; sourceTree = ""; }; 130 | F149657024323AAC008C1847 /* HorCell+Scroll.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorCell+Scroll.swift"; sourceTree = ""; }; 131 | F149657124323AAC008C1847 /* HorCell+Const.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorCell+Const.swift"; sourceTree = ""; }; 132 | F149657224323AAC008C1847 /* HorCell+Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorCell+Core.swift"; sourceTree = ""; }; 133 | F149657324323AAC008C1847 /* HorCell+Type.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorCell+Type.swift"; sourceTree = ""; }; 134 | F149657424323AAC008C1847 /* HorCell+Create.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HorCell+Create.swift"; sourceTree = ""; }; 135 | F149657724323AAC008C1847 /* SecondaryHorCell+Create.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SecondaryHorCell+Create.swift"; sourceTree = ""; }; 136 | F149657824323AAC008C1847 /* SecondaryHorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryHorCell.swift; sourceTree = ""; }; 137 | F149657924323AAC008C1847 /* SecondaryHorCell+Const.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SecondaryHorCell+Const.swift"; sourceTree = ""; }; 138 | F149657B24323AAC008C1847 /* TertiaryHorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TertiaryHorCell.swift; sourceTree = ""; }; 139 | F149657D24323AAC008C1847 /* PrimaryHorCell+Interaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PrimaryHorCell+Interaction.swift"; sourceTree = ""; }; 140 | F149657E24323AAC008C1847 /* PrimaryHorCell+Getter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PrimaryHorCell+Getter.swift"; sourceTree = ""; }; 141 | F149657F24323AAC008C1847 /* PrimaryHorCell+Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PrimaryHorCell+Core.swift"; sourceTree = ""; }; 142 | F149658024323AAC008C1847 /* PrimaryHorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimaryHorCell.swift; sourceTree = ""; }; 143 | F149658224323AAC008C1847 /* CellDataKind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellDataKind.swift; sourceTree = ""; }; 144 | F149658324323AAC008C1847 /* PrimaryCellData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimaryCellData.swift; sourceTree = ""; }; 145 | F149658524323AAC008C1847 /* CGShapeUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGShapeUtil.swift; sourceTree = ""; }; 146 | F149658624323AAC008C1847 /* UIView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; 147 | F149658724323AAC008C1847 /* UIColorParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColorParser.swift; sourceTree = ""; }; 148 | F149658924323AAC008C1847 /* UIImageView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Extension.swift"; sourceTree = ""; }; 149 | F149658A24323AAC008C1847 /* UIImage+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 150 | F149658E24323AAC008C1847 /* SliderBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderBar.swift; sourceTree = ""; }; 151 | F149658F24323AAC008C1847 /* Slider+Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Slider+Setter.swift"; sourceTree = ""; }; 152 | F149659024323AAC008C1847 /* Slider+Animation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Slider+Animation.swift"; sourceTree = ""; }; 153 | F149659124323AAC008C1847 /* Slider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Slider.swift; sourceTree = ""; }; 154 | F149659224323AAC008C1847 /* Slider+Create.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Slider+Create.swift"; sourceTree = ""; }; 155 | F149659324323AAC008C1847 /* Header+Create.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Header+Create.swift"; sourceTree = ""; }; 156 | F149659524323AAC008C1847 /* ButtonContainer+Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ButtonContainer+Setter.swift"; sourceTree = ""; }; 157 | F149659724323AAC008C1847 /* HeaderButton+Const.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HeaderButton+Const.swift"; sourceTree = ""; }; 158 | F149659824323AAC008C1847 /* CustomButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = ""; }; 159 | F149659924323AAC008C1847 /* HeaderButton+Type.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HeaderButton+Type.swift"; sourceTree = ""; }; 160 | F149659A24323AAC008C1847 /* HeaderButton+Interactive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HeaderButton+Interactive.swift"; sourceTree = ""; }; 161 | F149659B24323AAC008C1847 /* HeaderButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderButton.swift; sourceTree = ""; }; 162 | F149659C24323AAC008C1847 /* HeaderButton+Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HeaderButton+Setter.swift"; sourceTree = ""; }; 163 | F149659D24323AAC008C1847 /* ButtonContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonContainer.swift; sourceTree = ""; }; 164 | F149659E24323AAC008C1847 /* ButtonContainer+TypeAlias.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ButtonContainer+TypeAlias.swift"; sourceTree = ""; }; 165 | F149659F24323AAC008C1847 /* ButtonContainer+Create.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ButtonContainer+Create.swift"; sourceTree = ""; }; 166 | F14965A024323AAC008C1847 /* ButtonContainer+Interaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ButtonContainer+Interaction.swift"; sourceTree = ""; }; 167 | F14965A124323AAC008C1847 /* ButtonContainer+Const.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ButtonContainer+Const.swift"; sourceTree = ""; }; 168 | F14965A224323AAC008C1847 /* Header.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = ""; }; 169 | F14965A524323AAC008C1847 /* Header+Const.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Header+Const.swift"; sourceTree = ""; }; 170 | F14965A724323AAC008C1847 /* HeaderTitle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderTitle.swift; sourceTree = ""; }; 171 | F14965A824323AAC008C1847 /* HeaderTitle+Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HeaderTitle+Setter.swift"; sourceTree = ""; }; 172 | F14965A924323AAC008C1847 /* HeaderTitle+Const.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HeaderTitle+Const.swift"; sourceTree = ""; }; 173 | F14965AA24323AAC008C1847 /* Header+Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Header+Setter.swift"; sourceTree = ""; }; 174 | F1FB41572443556000A433E2 /* HorView+Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HorView+Event.swift"; sourceTree = ""; }; 175 | F1FB41592443599600A433E2 /* Slider+Const.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Slider+Const.swift"; sourceTree = ""; }; 176 | /* End PBXFileReference section */ 177 | 178 | /* Begin PBXFrameworksBuildPhase section */ 179 | F116EBD0211046B40091439E /* Frameworks */ = { 180 | isa = PBXFrameworksBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | F15CB295268D265200C5C303 /* With in Frameworks */, 184 | F14965F524323D7B008C1847 /* NetworkSugar in Frameworks */, 185 | F14965F224323D2B008C1847 /* CommonCell in Frameworks */, 186 | F15CB292268D262D00C5C303 /* Spatial in Frameworks */, 187 | F14965EC24323B0A008C1847 /* JSONSugar in Frameworks */, 188 | F14965EF24323B22008C1847 /* ImageSugar in Frameworks */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXFrameworksBuildPhase section */ 193 | 194 | /* Begin PBXGroup section */ 195 | F116EBCA211046B40091439E = { 196 | isa = PBXGroup; 197 | children = ( 198 | F149655324323AAC008C1847 /* Sources */, 199 | F1244242224902C600D808D8 /* demo */, 200 | F116EBD5211046B40091439E /* FlowLayout */, 201 | F116EBD4211046B40091439E /* Products */, 202 | F124420F2248314E00D808D8 /* Frameworks */, 203 | ); 204 | sourceTree = ""; 205 | }; 206 | F116EBD4211046B40091439E /* Products */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | F116EBD3211046B40091439E /* FlowLayout.app */, 210 | ); 211 | name = Products; 212 | sourceTree = ""; 213 | }; 214 | F116EBD5211046B40091439E /* FlowLayout */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | F116EBD6211046B40091439E /* AppDelegate.swift */, 218 | F12441F42248001700D808D8 /* ViewController.swift */, 219 | F116EC3B2112E4A40091439E /* ViewController+Create.swift */, 220 | F116EBDD211046B70091439E /* Assets.xcassets */, 221 | F116EBDF211046B70091439E /* LaunchScreen.storyboard */, 222 | F116EBE2211046B70091439E /* Info.plist */, 223 | ); 224 | path = FlowLayout; 225 | sourceTree = ""; 226 | }; 227 | F124420F2248314E00D808D8 /* Frameworks */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | ); 231 | name = Frameworks; 232 | sourceTree = ""; 233 | }; 234 | F1244242224902C600D808D8 /* demo */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | F12442432249031700D808D8 /* CustomView.swift */, 238 | ); 239 | path = demo; 240 | sourceTree = ""; 241 | }; 242 | F149655324323AAC008C1847 /* Sources */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | F149655424323AAC008C1847 /* FlowLayout */, 246 | ); 247 | path = Sources; 248 | sourceTree = ""; 249 | }; 250 | F149655424323AAC008C1847 /* FlowLayout */ = { 251 | isa = PBXGroup; 252 | children = ( 253 | F149655524323AAC008C1847 /* HorView */, 254 | F149656024323AAC008C1847 /* cell */, 255 | F149658424323AAC008C1847 /* common */, 256 | F149658B24323AAC008C1847 /* Header */, 257 | ); 258 | path = FlowLayout; 259 | sourceTree = ""; 260 | }; 261 | F149655524323AAC008C1847 /* HorView */ = { 262 | isa = PBXGroup; 263 | children = ( 264 | F15CB28F268D1F3D00C5C303 /* utils */, 265 | F149655624323AAC008C1847 /* HorView.swift */, 266 | F149655724323AAC008C1847 /* HorView+Collection.swift */, 267 | F149655924323AAC008C1847 /* HorView+Style.swift */, 268 | F149655A24323AAC008C1847 /* HorView+Create.swift */, 269 | F149655B24323AAC008C1847 /* HorView+Register.swift */, 270 | F149655C24323AAC008C1847 /* HorView+Getter.swift */, 271 | F149655D24323AAC008C1847 /* HorView+Scroll.swift */, 272 | F1FB41572443556000A433E2 /* HorView+Event.swift */, 273 | F149655E24323AAC008C1847 /* HorView+Update.swift */, 274 | F149655F24323AAC008C1847 /* HorView+Setter.swift */, 275 | ); 276 | path = HorView; 277 | sourceTree = ""; 278 | }; 279 | F149656024323AAC008C1847 /* cell */ = { 280 | isa = PBXGroup; 281 | children = ( 282 | F149656124323AAC008C1847 /* horizontal */, 283 | ); 284 | path = cell; 285 | sourceTree = ""; 286 | }; 287 | F149656124323AAC008C1847 /* horizontal */ = { 288 | isa = PBXGroup; 289 | children = ( 290 | F149656224323AAC008C1847 /* VerCell */, 291 | F149656B24323AAC008C1847 /* HorCell */, 292 | F149657524323AAC008C1847 /* custom */, 293 | ); 294 | path = horizontal; 295 | sourceTree = ""; 296 | }; 297 | F149656224323AAC008C1847 /* VerCell */ = { 298 | isa = PBXGroup; 299 | children = ( 300 | F149656324323AAC008C1847 /* custom */, 301 | F149656A24323AAC008C1847 /* VerCell.swift */, 302 | ); 303 | path = VerCell; 304 | sourceTree = ""; 305 | }; 306 | F149656324323AAC008C1847 /* custom */ = { 307 | isa = PBXGroup; 308 | children = ( 309 | F149656424323AAC008C1847 /* ImageCell */, 310 | F149656724323AAC008C1847 /* PrimaryVerCell */, 311 | ); 312 | path = custom; 313 | sourceTree = ""; 314 | }; 315 | F149656424323AAC008C1847 /* ImageCell */ = { 316 | isa = PBXGroup; 317 | children = ( 318 | F149656524323AAC008C1847 /* ImageCell+Create.swift */, 319 | F149656624323AAC008C1847 /* ImageCell.swift */, 320 | ); 321 | path = ImageCell; 322 | sourceTree = ""; 323 | }; 324 | F149656724323AAC008C1847 /* PrimaryVerCell */ = { 325 | isa = PBXGroup; 326 | children = ( 327 | F149656824323AAC008C1847 /* PrimaryVerCell.swift */, 328 | F149656924323AAC008C1847 /* PrimaryVerCell+Core.swift */, 329 | ); 330 | path = PrimaryVerCell; 331 | sourceTree = ""; 332 | }; 333 | F149656B24323AAC008C1847 /* HorCell */ = { 334 | isa = PBXGroup; 335 | children = ( 336 | F149656D24323AAC008C1847 /* HorCell.swift */, 337 | F149656C24323AAC008C1847 /* HorCell+Update.swift */, 338 | F149656E24323AAC008C1847 /* HorCell+Collection.swift */, 339 | F149656F24323AAC008C1847 /* HorCell+Getter.swift */, 340 | F149657024323AAC008C1847 /* HorCell+Scroll.swift */, 341 | F149657124323AAC008C1847 /* HorCell+Const.swift */, 342 | F149657224323AAC008C1847 /* HorCell+Core.swift */, 343 | F149657324323AAC008C1847 /* HorCell+Type.swift */, 344 | F149657424323AAC008C1847 /* HorCell+Create.swift */, 345 | ); 346 | path = HorCell; 347 | sourceTree = ""; 348 | }; 349 | F149657524323AAC008C1847 /* custom */ = { 350 | isa = PBXGroup; 351 | children = ( 352 | F149657624323AAC008C1847 /* SecondaryHorCell */, 353 | F149657A24323AAC008C1847 /* TertiaryHorCell */, 354 | F149657C24323AAC008C1847 /* PrimaryHorCell */, 355 | F149658124323AAC008C1847 /* PrimaryCellData */, 356 | ); 357 | path = custom; 358 | sourceTree = ""; 359 | }; 360 | F149657624323AAC008C1847 /* SecondaryHorCell */ = { 361 | isa = PBXGroup; 362 | children = ( 363 | F149657724323AAC008C1847 /* SecondaryHorCell+Create.swift */, 364 | F149657824323AAC008C1847 /* SecondaryHorCell.swift */, 365 | F149657924323AAC008C1847 /* SecondaryHorCell+Const.swift */, 366 | ); 367 | path = SecondaryHorCell; 368 | sourceTree = ""; 369 | }; 370 | F149657A24323AAC008C1847 /* TertiaryHorCell */ = { 371 | isa = PBXGroup; 372 | children = ( 373 | F149657B24323AAC008C1847 /* TertiaryHorCell.swift */, 374 | ); 375 | path = TertiaryHorCell; 376 | sourceTree = ""; 377 | }; 378 | F149657C24323AAC008C1847 /* PrimaryHorCell */ = { 379 | isa = PBXGroup; 380 | children = ( 381 | F149657D24323AAC008C1847 /* PrimaryHorCell+Interaction.swift */, 382 | F149657E24323AAC008C1847 /* PrimaryHorCell+Getter.swift */, 383 | F149657F24323AAC008C1847 /* PrimaryHorCell+Core.swift */, 384 | F149658024323AAC008C1847 /* PrimaryHorCell.swift */, 385 | ); 386 | path = PrimaryHorCell; 387 | sourceTree = ""; 388 | }; 389 | F149658124323AAC008C1847 /* PrimaryCellData */ = { 390 | isa = PBXGroup; 391 | children = ( 392 | F149658224323AAC008C1847 /* CellDataKind.swift */, 393 | F149658324323AAC008C1847 /* PrimaryCellData.swift */, 394 | ); 395 | path = PrimaryCellData; 396 | sourceTree = ""; 397 | }; 398 | F149658424323AAC008C1847 /* common */ = { 399 | isa = PBXGroup; 400 | children = ( 401 | F149658524323AAC008C1847 /* CGShapeUtil.swift */, 402 | F149658624323AAC008C1847 /* UIView+Extension.swift */, 403 | F149658724323AAC008C1847 /* UIColorParser.swift */, 404 | F149658824323AAC008C1847 /* image */, 405 | ); 406 | path = common; 407 | sourceTree = ""; 408 | }; 409 | F149658824323AAC008C1847 /* image */ = { 410 | isa = PBXGroup; 411 | children = ( 412 | F149658924323AAC008C1847 /* UIImageView+Extension.swift */, 413 | F149658A24323AAC008C1847 /* UIImage+Extension.swift */, 414 | ); 415 | path = image; 416 | sourceTree = ""; 417 | }; 418 | F149658B24323AAC008C1847 /* Header */ = { 419 | isa = PBXGroup; 420 | children = ( 421 | F149658C24323AAC008C1847 /* Slider */, 422 | F149659424323AAC008C1847 /* ButtonContainer */, 423 | F14965A624323AAC008C1847 /* HeaderTitle */, 424 | F14965A224323AAC008C1847 /* Header.swift */, 425 | F14965AA24323AAC008C1847 /* Header+Setter.swift */, 426 | F149659324323AAC008C1847 /* Header+Create.swift */, 427 | F14965A524323AAC008C1847 /* Header+Const.swift */, 428 | ); 429 | path = Header; 430 | sourceTree = ""; 431 | }; 432 | F149658C24323AAC008C1847 /* Slider */ = { 433 | isa = PBXGroup; 434 | children = ( 435 | F149658D24323AAC008C1847 /* SliderBar */, 436 | F149659124323AAC008C1847 /* Slider.swift */, 437 | F149658F24323AAC008C1847 /* Slider+Setter.swift */, 438 | F149659024323AAC008C1847 /* Slider+Animation.swift */, 439 | F149659224323AAC008C1847 /* Slider+Create.swift */, 440 | F1FB41592443599600A433E2 /* Slider+Const.swift */, 441 | ); 442 | path = Slider; 443 | sourceTree = ""; 444 | }; 445 | F149658D24323AAC008C1847 /* SliderBar */ = { 446 | isa = PBXGroup; 447 | children = ( 448 | F149658E24323AAC008C1847 /* SliderBar.swift */, 449 | ); 450 | path = SliderBar; 451 | sourceTree = ""; 452 | }; 453 | F149659424323AAC008C1847 /* ButtonContainer */ = { 454 | isa = PBXGroup; 455 | children = ( 456 | F149659624323AAC008C1847 /* HeaderButton */, 457 | F149659D24323AAC008C1847 /* ButtonContainer.swift */, 458 | F149659524323AAC008C1847 /* ButtonContainer+Setter.swift */, 459 | F149659E24323AAC008C1847 /* ButtonContainer+TypeAlias.swift */, 460 | F149659F24323AAC008C1847 /* ButtonContainer+Create.swift */, 461 | F14965A024323AAC008C1847 /* ButtonContainer+Interaction.swift */, 462 | F14965A124323AAC008C1847 /* ButtonContainer+Const.swift */, 463 | ); 464 | path = ButtonContainer; 465 | sourceTree = ""; 466 | }; 467 | F149659624323AAC008C1847 /* HeaderButton */ = { 468 | isa = PBXGroup; 469 | children = ( 470 | F149659824323AAC008C1847 /* CustomButton.swift */, 471 | F149659B24323AAC008C1847 /* HeaderButton.swift */, 472 | F149659724323AAC008C1847 /* HeaderButton+Const.swift */, 473 | F149659924323AAC008C1847 /* HeaderButton+Type.swift */, 474 | F149659A24323AAC008C1847 /* HeaderButton+Interactive.swift */, 475 | F149659C24323AAC008C1847 /* HeaderButton+Setter.swift */, 476 | ); 477 | path = HeaderButton; 478 | sourceTree = ""; 479 | }; 480 | F14965A624323AAC008C1847 /* HeaderTitle */ = { 481 | isa = PBXGroup; 482 | children = ( 483 | F14965A724323AAC008C1847 /* HeaderTitle.swift */, 484 | F14965A824323AAC008C1847 /* HeaderTitle+Setter.swift */, 485 | F14965A924323AAC008C1847 /* HeaderTitle+Const.swift */, 486 | ); 487 | path = HeaderTitle; 488 | sourceTree = ""; 489 | }; 490 | F15CB28F268D1F3D00C5C303 /* utils */ = { 491 | isa = PBXGroup; 492 | children = ( 493 | F149655824323AAC008C1847 /* ColumnCellType.swift */, 494 | ); 495 | path = utils; 496 | sourceTree = ""; 497 | }; 498 | /* End PBXGroup section */ 499 | 500 | /* Begin PBXNativeTarget section */ 501 | F116EBD2211046B40091439E /* FlowLayout */ = { 502 | isa = PBXNativeTarget; 503 | buildConfigurationList = F116EBE5211046B70091439E /* Build configuration list for PBXNativeTarget "FlowLayout" */; 504 | buildPhases = ( 505 | F116EBCF211046B40091439E /* Sources */, 506 | F116EBD0211046B40091439E /* Frameworks */, 507 | F116EBD1211046B40091439E /* Resources */, 508 | F12441E82247F0F800D808D8 /* Embed Frameworks */, 509 | 9D05884322F326C400F0A2BE /* ShellScript */, 510 | ); 511 | buildRules = ( 512 | ); 513 | dependencies = ( 514 | ); 515 | name = FlowLayout; 516 | packageProductDependencies = ( 517 | F14965EB24323B0A008C1847 /* JSONSugar */, 518 | F14965EE24323B22008C1847 /* ImageSugar */, 519 | F14965F124323D2B008C1847 /* CommonCell */, 520 | F14965F424323D7B008C1847 /* NetworkSugar */, 521 | F15CB291268D262D00C5C303 /* Spatial */, 522 | F15CB294268D265200C5C303 /* With */, 523 | ); 524 | productName = FlowLayout; 525 | productReference = F116EBD3211046B40091439E /* FlowLayout.app */; 526 | productType = "com.apple.product-type.application"; 527 | }; 528 | /* End PBXNativeTarget section */ 529 | 530 | /* Begin PBXProject section */ 531 | F116EBCB211046B40091439E /* Project object */ = { 532 | isa = PBXProject; 533 | attributes = { 534 | LastSwiftUpdateCheck = 0940; 535 | LastUpgradeCheck = 1240; 536 | ORGANIZATIONNAME = eon; 537 | TargetAttributes = { 538 | F116EBD2211046B40091439E = { 539 | CreatedOnToolsVersion = 9.4.1; 540 | LastSwiftMigration = 1010; 541 | }; 542 | }; 543 | }; 544 | buildConfigurationList = F116EBCE211046B40091439E /* Build configuration list for PBXProject "FlowLayout" */; 545 | compatibilityVersion = "Xcode 9.3"; 546 | developmentRegion = en; 547 | hasScannedForEncodings = 0; 548 | knownRegions = ( 549 | en, 550 | Base, 551 | ); 552 | mainGroup = F116EBCA211046B40091439E; 553 | packageReferences = ( 554 | F14965EA24323B0A008C1847 /* XCRemoteSwiftPackageReference "JSONSugar" */, 555 | F14965ED24323B22008C1847 /* XCRemoteSwiftPackageReference "ImageSugar" */, 556 | F14965F024323D2B008C1847 /* XCRemoteSwiftPackageReference "CommonCell" */, 557 | F14965F324323D7B008C1847 /* XCRemoteSwiftPackageReference "NetworkSugar" */, 558 | F15CB290268D262D00C5C303 /* XCRemoteSwiftPackageReference "Spatial" */, 559 | F15CB293268D265200C5C303 /* XCRemoteSwiftPackageReference "with" */, 560 | ); 561 | productRefGroup = F116EBD4211046B40091439E /* Products */; 562 | projectDirPath = ""; 563 | projectRoot = ""; 564 | targets = ( 565 | F116EBD2211046B40091439E /* FlowLayout */, 566 | ); 567 | }; 568 | /* End PBXProject section */ 569 | 570 | /* Begin PBXResourcesBuildPhase section */ 571 | F116EBD1211046B40091439E /* Resources */ = { 572 | isa = PBXResourcesBuildPhase; 573 | buildActionMask = 2147483647; 574 | files = ( 575 | F116EBE1211046B70091439E /* LaunchScreen.storyboard in Resources */, 576 | F116EBDE211046B70091439E /* Assets.xcassets in Resources */, 577 | ); 578 | runOnlyForDeploymentPostprocessing = 0; 579 | }; 580 | /* End PBXResourcesBuildPhase section */ 581 | 582 | /* Begin PBXShellScriptBuildPhase section */ 583 | 9D05884322F326C400F0A2BE /* ShellScript */ = { 584 | isa = PBXShellScriptBuildPhase; 585 | buildActionMask = 2147483647; 586 | files = ( 587 | ); 588 | inputFileListPaths = ( 589 | ); 590 | inputPaths = ( 591 | ); 592 | outputFileListPaths = ( 593 | ); 594 | outputPaths = ( 595 | ); 596 | runOnlyForDeploymentPostprocessing = 0; 597 | shellPath = /bin/sh; 598 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 599 | }; 600 | /* End PBXShellScriptBuildPhase section */ 601 | 602 | /* Begin PBXSourcesBuildPhase section */ 603 | F116EBCF211046B40091439E /* Sources */ = { 604 | isa = PBXSourcesBuildPhase; 605 | buildActionMask = 2147483647; 606 | files = ( 607 | F14965DA24323AAC008C1847 /* CustomButton.swift in Sources */, 608 | F14965E324323AAC008C1847 /* ButtonContainer+Const.swift in Sources */, 609 | F14965E424323AAC008C1847 /* Header.swift in Sources */, 610 | F14965CA24323AAC008C1847 /* PrimaryHorCell.swift in Sources */, 611 | F14965BD24323AAC008C1847 /* HorCell+Getter.swift in Sources */, 612 | F14965D324323AAC008C1847 /* Slider+Setter.swift in Sources */, 613 | F14965C724323AAC008C1847 /* PrimaryHorCell+Interaction.swift in Sources */, 614 | F116EC3C2112E4A40091439E /* ViewController+Create.swift in Sources */, 615 | F14965C824323AAC008C1847 /* PrimaryHorCell+Getter.swift in Sources */, 616 | F14965C024323AAC008C1847 /* HorCell+Core.swift in Sources */, 617 | F14965C224323AAC008C1847 /* HorCell+Create.swift in Sources */, 618 | F14965B224323AAC008C1847 /* HorView+Scroll.swift in Sources */, 619 | F12442442249031700D808D8 /* CustomView.swift in Sources */, 620 | F14965CD24323AAC008C1847 /* CGShapeUtil.swift in Sources */, 621 | F14965D224323AAC008C1847 /* SliderBar.swift in Sources */, 622 | F14965BC24323AAC008C1847 /* HorCell+Collection.swift in Sources */, 623 | F14965E724323AAC008C1847 /* HeaderTitle+Setter.swift in Sources */, 624 | F14965B724323AAC008C1847 /* PrimaryVerCell.swift in Sources */, 625 | F14965AB24323AAC008C1847 /* HorView.swift in Sources */, 626 | F1FB415A2443599600A433E2 /* Slider+Const.swift in Sources */, 627 | F14965BB24323AAC008C1847 /* HorCell.swift in Sources */, 628 | F14965D624323AAC008C1847 /* Slider+Create.swift in Sources */, 629 | F14965E024323AAC008C1847 /* ButtonContainer+TypeAlias.swift in Sources */, 630 | F14965D724323AAC008C1847 /* Header+Create.swift in Sources */, 631 | F14965C324323AAC008C1847 /* SecondaryHorCell+Create.swift in Sources */, 632 | F14965C924323AAC008C1847 /* PrimaryHorCell+Core.swift in Sources */, 633 | F14965D924323AAC008C1847 /* HeaderButton+Const.swift in Sources */, 634 | F14965D124323AAC008C1847 /* UIImage+Extension.swift in Sources */, 635 | F12441F52248001700D808D8 /* ViewController.swift in Sources */, 636 | F14965C124323AAC008C1847 /* HorCell+Type.swift in Sources */, 637 | F14965E824323AAC008C1847 /* HeaderTitle+Const.swift in Sources */, 638 | F14965CE24323AAC008C1847 /* UIView+Extension.swift in Sources */, 639 | F14965C624323AAC008C1847 /* TertiaryHorCell.swift in Sources */, 640 | F14965BA24323AAC008C1847 /* HorCell+Update.swift in Sources */, 641 | F14965AD24323AAC008C1847 /* ColumnCellType.swift in Sources */, 642 | F14965B524323AAC008C1847 /* ImageCell+Create.swift in Sources */, 643 | F14965AC24323AAC008C1847 /* HorView+Collection.swift in Sources */, 644 | F14965D524323AAC008C1847 /* Slider.swift in Sources */, 645 | F14965AF24323AAC008C1847 /* HorView+Create.swift in Sources */, 646 | F14965C424323AAC008C1847 /* SecondaryHorCell.swift in Sources */, 647 | F1FB41582443556000A433E2 /* HorView+Event.swift in Sources */, 648 | F14965E124323AAC008C1847 /* ButtonContainer+Create.swift in Sources */, 649 | F14965B924323AAC008C1847 /* VerCell.swift in Sources */, 650 | F14965B124323AAC008C1847 /* HorView+Getter.swift in Sources */, 651 | F14965E224323AAC008C1847 /* ButtonContainer+Interaction.swift in Sources */, 652 | F14965D824323AAC008C1847 /* ButtonContainer+Setter.swift in Sources */, 653 | F14965C524323AAC008C1847 /* SecondaryHorCell+Const.swift in Sources */, 654 | F14965B424323AAC008C1847 /* HorView+Setter.swift in Sources */, 655 | F14965DB24323AAC008C1847 /* HeaderButton+Type.swift in Sources */, 656 | F14965D024323AAC008C1847 /* UIImageView+Extension.swift in Sources */, 657 | F14965E924323AAC008C1847 /* Header+Setter.swift in Sources */, 658 | F14965DF24323AAC008C1847 /* ButtonContainer.swift in Sources */, 659 | F14965B324323AAC008C1847 /* HorView+Update.swift in Sources */, 660 | F14965BE24323AAC008C1847 /* HorCell+Scroll.swift in Sources */, 661 | F14965DE24323AAC008C1847 /* HeaderButton+Setter.swift in Sources */, 662 | F14965B824323AAC008C1847 /* PrimaryVerCell+Core.swift in Sources */, 663 | F14965CB24323AAC008C1847 /* CellDataKind.swift in Sources */, 664 | F14965B024323AAC008C1847 /* HorView+Register.swift in Sources */, 665 | F14965B624323AAC008C1847 /* ImageCell.swift in Sources */, 666 | F14965DC24323AAC008C1847 /* HeaderButton+Interactive.swift in Sources */, 667 | F14965E624323AAC008C1847 /* HeaderTitle.swift in Sources */, 668 | F14965AE24323AAC008C1847 /* HorView+Style.swift in Sources */, 669 | F14965BF24323AAC008C1847 /* HorCell+Const.swift in Sources */, 670 | F14965CC24323AAC008C1847 /* PrimaryCellData.swift in Sources */, 671 | F14965CF24323AAC008C1847 /* UIColorParser.swift in Sources */, 672 | F14965DD24323AAC008C1847 /* HeaderButton.swift in Sources */, 673 | F116EBD7211046B40091439E /* AppDelegate.swift in Sources */, 674 | F14965E524323AAC008C1847 /* Header+Const.swift in Sources */, 675 | F14965D424323AAC008C1847 /* Slider+Animation.swift in Sources */, 676 | ); 677 | runOnlyForDeploymentPostprocessing = 0; 678 | }; 679 | /* End PBXSourcesBuildPhase section */ 680 | 681 | /* Begin PBXVariantGroup section */ 682 | F116EBDF211046B70091439E /* LaunchScreen.storyboard */ = { 683 | isa = PBXVariantGroup; 684 | children = ( 685 | F116EBE0211046B70091439E /* Base */, 686 | ); 687 | name = LaunchScreen.storyboard; 688 | sourceTree = ""; 689 | }; 690 | /* End PBXVariantGroup section */ 691 | 692 | /* Begin XCBuildConfiguration section */ 693 | F116EBE3211046B70091439E /* Debug */ = { 694 | isa = XCBuildConfiguration; 695 | buildSettings = { 696 | ALWAYS_SEARCH_USER_PATHS = NO; 697 | CLANG_ANALYZER_NONNULL = YES; 698 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 699 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 700 | CLANG_CXX_LIBRARY = "libc++"; 701 | CLANG_ENABLE_MODULES = YES; 702 | CLANG_ENABLE_OBJC_ARC = YES; 703 | CLANG_ENABLE_OBJC_WEAK = YES; 704 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 705 | CLANG_WARN_BOOL_CONVERSION = YES; 706 | CLANG_WARN_COMMA = YES; 707 | CLANG_WARN_CONSTANT_CONVERSION = YES; 708 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 709 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 710 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 711 | CLANG_WARN_EMPTY_BODY = YES; 712 | CLANG_WARN_ENUM_CONVERSION = YES; 713 | CLANG_WARN_INFINITE_RECURSION = YES; 714 | CLANG_WARN_INT_CONVERSION = YES; 715 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 716 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 717 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 718 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 719 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 720 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 721 | CLANG_WARN_STRICT_PROTOTYPES = YES; 722 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 723 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 724 | CLANG_WARN_UNREACHABLE_CODE = YES; 725 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 726 | CODE_SIGN_IDENTITY = "iPhone Developer"; 727 | COPY_PHASE_STRIP = NO; 728 | DEBUG_INFORMATION_FORMAT = dwarf; 729 | ENABLE_STRICT_OBJC_MSGSEND = YES; 730 | ENABLE_TESTABILITY = YES; 731 | GCC_C_LANGUAGE_STANDARD = gnu11; 732 | GCC_DYNAMIC_NO_PIC = NO; 733 | GCC_NO_COMMON_BLOCKS = YES; 734 | GCC_OPTIMIZATION_LEVEL = 0; 735 | GCC_PREPROCESSOR_DEFINITIONS = ( 736 | "DEBUG=1", 737 | "$(inherited)", 738 | ); 739 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 740 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 741 | GCC_WARN_UNDECLARED_SELECTOR = YES; 742 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 743 | GCC_WARN_UNUSED_FUNCTION = YES; 744 | GCC_WARN_UNUSED_VARIABLE = YES; 745 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 746 | MTL_ENABLE_DEBUG_INFO = YES; 747 | ONLY_ACTIVE_ARCH = YES; 748 | SDKROOT = iphoneos; 749 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 750 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 751 | }; 752 | name = Debug; 753 | }; 754 | F116EBE4211046B70091439E /* Release */ = { 755 | isa = XCBuildConfiguration; 756 | buildSettings = { 757 | ALWAYS_SEARCH_USER_PATHS = NO; 758 | CLANG_ANALYZER_NONNULL = YES; 759 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 760 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 761 | CLANG_CXX_LIBRARY = "libc++"; 762 | CLANG_ENABLE_MODULES = YES; 763 | CLANG_ENABLE_OBJC_ARC = YES; 764 | CLANG_ENABLE_OBJC_WEAK = YES; 765 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 766 | CLANG_WARN_BOOL_CONVERSION = YES; 767 | CLANG_WARN_COMMA = YES; 768 | CLANG_WARN_CONSTANT_CONVERSION = YES; 769 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 770 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 771 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 772 | CLANG_WARN_EMPTY_BODY = YES; 773 | CLANG_WARN_ENUM_CONVERSION = YES; 774 | CLANG_WARN_INFINITE_RECURSION = YES; 775 | CLANG_WARN_INT_CONVERSION = YES; 776 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 777 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 778 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 779 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 780 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 781 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 782 | CLANG_WARN_STRICT_PROTOTYPES = YES; 783 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 784 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 785 | CLANG_WARN_UNREACHABLE_CODE = YES; 786 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 787 | CODE_SIGN_IDENTITY = "iPhone Developer"; 788 | COPY_PHASE_STRIP = NO; 789 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 790 | ENABLE_NS_ASSERTIONS = NO; 791 | ENABLE_STRICT_OBJC_MSGSEND = YES; 792 | GCC_C_LANGUAGE_STANDARD = gnu11; 793 | GCC_NO_COMMON_BLOCKS = YES; 794 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 795 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 796 | GCC_WARN_UNDECLARED_SELECTOR = YES; 797 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 798 | GCC_WARN_UNUSED_FUNCTION = YES; 799 | GCC_WARN_UNUSED_VARIABLE = YES; 800 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 801 | MTL_ENABLE_DEBUG_INFO = NO; 802 | SDKROOT = iphoneos; 803 | SWIFT_COMPILATION_MODE = wholemodule; 804 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 805 | VALIDATE_PRODUCT = YES; 806 | }; 807 | name = Release; 808 | }; 809 | F116EBE6211046B70091439E /* Debug */ = { 810 | isa = XCBuildConfiguration; 811 | buildSettings = { 812 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 813 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 814 | CODE_SIGN_STYLE = Automatic; 815 | DEVELOPMENT_TEAM = B5CC9TD7P7; 816 | INFOPLIST_FILE = FlowLayout/Info.plist; 817 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 818 | LD_RUNPATH_SEARCH_PATHS = ( 819 | "$(inherited)", 820 | "@executable_path/Frameworks", 821 | ); 822 | PRODUCT_BUNDLE_IDENTIFIER = codes.eon.FlowLay; 823 | PRODUCT_NAME = "$(TARGET_NAME)"; 824 | SWIFT_VERSION = 5.0; 825 | TARGETED_DEVICE_FAMILY = 1; 826 | }; 827 | name = Debug; 828 | }; 829 | F116EBE7211046B70091439E /* Release */ = { 830 | isa = XCBuildConfiguration; 831 | buildSettings = { 832 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 833 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 834 | CODE_SIGN_STYLE = Automatic; 835 | DEVELOPMENT_TEAM = B5CC9TD7P7; 836 | INFOPLIST_FILE = FlowLayout/Info.plist; 837 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 838 | LD_RUNPATH_SEARCH_PATHS = ( 839 | "$(inherited)", 840 | "@executable_path/Frameworks", 841 | ); 842 | PRODUCT_BUNDLE_IDENTIFIER = codes.eon.FlowLay; 843 | PRODUCT_NAME = "$(TARGET_NAME)"; 844 | SWIFT_VERSION = 5.0; 845 | TARGETED_DEVICE_FAMILY = 1; 846 | }; 847 | name = Release; 848 | }; 849 | /* End XCBuildConfiguration section */ 850 | 851 | /* Begin XCConfigurationList section */ 852 | F116EBCE211046B40091439E /* Build configuration list for PBXProject "FlowLayout" */ = { 853 | isa = XCConfigurationList; 854 | buildConfigurations = ( 855 | F116EBE3211046B70091439E /* Debug */, 856 | F116EBE4211046B70091439E /* Release */, 857 | ); 858 | defaultConfigurationIsVisible = 0; 859 | defaultConfigurationName = Release; 860 | }; 861 | F116EBE5211046B70091439E /* Build configuration list for PBXNativeTarget "FlowLayout" */ = { 862 | isa = XCConfigurationList; 863 | buildConfigurations = ( 864 | F116EBE6211046B70091439E /* Debug */, 865 | F116EBE7211046B70091439E /* Release */, 866 | ); 867 | defaultConfigurationIsVisible = 0; 868 | defaultConfigurationName = Release; 869 | }; 870 | /* End XCConfigurationList section */ 871 | 872 | /* Begin XCRemoteSwiftPackageReference section */ 873 | F14965EA24323B0A008C1847 /* XCRemoteSwiftPackageReference "JSONSugar" */ = { 874 | isa = XCRemoteSwiftPackageReference; 875 | repositoryURL = "https://github.com/eonist/JSONSugar.git"; 876 | requirement = { 877 | branch = master; 878 | kind = branch; 879 | }; 880 | }; 881 | F14965ED24323B22008C1847 /* XCRemoteSwiftPackageReference "ImageSugar" */ = { 882 | isa = XCRemoteSwiftPackageReference; 883 | repositoryURL = "https://github.com/eonist/ImageSugar.git"; 884 | requirement = { 885 | branch = master; 886 | kind = branch; 887 | }; 888 | }; 889 | F14965F024323D2B008C1847 /* XCRemoteSwiftPackageReference "CommonCell" */ = { 890 | isa = XCRemoteSwiftPackageReference; 891 | repositoryURL = "https://github.com/eonist/CommonCell.git"; 892 | requirement = { 893 | branch = master; 894 | kind = branch; 895 | }; 896 | }; 897 | F14965F324323D7B008C1847 /* XCRemoteSwiftPackageReference "NetworkSugar" */ = { 898 | isa = XCRemoteSwiftPackageReference; 899 | repositoryURL = "https://github.com/eonist/NetworkSugar.git"; 900 | requirement = { 901 | branch = master; 902 | kind = branch; 903 | }; 904 | }; 905 | F15CB290268D262D00C5C303 /* XCRemoteSwiftPackageReference "Spatial" */ = { 906 | isa = XCRemoteSwiftPackageReference; 907 | repositoryURL = "https://github.com/eonist/Spatial.git"; 908 | requirement = { 909 | branch = master; 910 | kind = branch; 911 | }; 912 | }; 913 | F15CB293268D265200C5C303 /* XCRemoteSwiftPackageReference "with" */ = { 914 | isa = XCRemoteSwiftPackageReference; 915 | repositoryURL = "https://github.com/eonist/with"; 916 | requirement = { 917 | branch = master; 918 | kind = branch; 919 | }; 920 | }; 921 | /* End XCRemoteSwiftPackageReference section */ 922 | 923 | /* Begin XCSwiftPackageProductDependency section */ 924 | F14965EB24323B0A008C1847 /* JSONSugar */ = { 925 | isa = XCSwiftPackageProductDependency; 926 | package = F14965EA24323B0A008C1847 /* XCRemoteSwiftPackageReference "JSONSugar" */; 927 | productName = JSONSugar; 928 | }; 929 | F14965EE24323B22008C1847 /* ImageSugar */ = { 930 | isa = XCSwiftPackageProductDependency; 931 | package = F14965ED24323B22008C1847 /* XCRemoteSwiftPackageReference "ImageSugar" */; 932 | productName = ImageSugar; 933 | }; 934 | F14965F124323D2B008C1847 /* CommonCell */ = { 935 | isa = XCSwiftPackageProductDependency; 936 | package = F14965F024323D2B008C1847 /* XCRemoteSwiftPackageReference "CommonCell" */; 937 | productName = CommonCell; 938 | }; 939 | F14965F424323D7B008C1847 /* NetworkSugar */ = { 940 | isa = XCSwiftPackageProductDependency; 941 | package = F14965F324323D7B008C1847 /* XCRemoteSwiftPackageReference "NetworkSugar" */; 942 | productName = NetworkSugar; 943 | }; 944 | F15CB291268D262D00C5C303 /* Spatial */ = { 945 | isa = XCSwiftPackageProductDependency; 946 | package = F15CB290268D262D00C5C303 /* XCRemoteSwiftPackageReference "Spatial" */; 947 | productName = Spatial; 948 | }; 949 | F15CB294268D265200C5C303 /* With */ = { 950 | isa = XCSwiftPackageProductDependency; 951 | package = F15CB293268D265200C5C303 /* XCRemoteSwiftPackageReference "with" */; 952 | productName = With; 953 | }; 954 | /* End XCSwiftPackageProductDependency section */ 955 | }; 956 | rootObject = F116EBCB211046B40091439E /* Project object */; 957 | } 958 | -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CommonCell", 6 | "repositoryURL": "https://github.com/eonist/CommonCell.git", 7 | "state": { 8 | "branch": "master", 9 | "revision": "e226a1520deb37374884463fca12118bae00c7c9", 10 | "version": null 11 | } 12 | }, 13 | { 14 | "package": "FileSugar", 15 | "repositoryURL": "https://github.com/eonist/FileSugar.git", 16 | "state": { 17 | "branch": "master", 18 | "revision": "523b7325aa211c34d2622c365915969122ed02bc", 19 | "version": null 20 | } 21 | }, 22 | { 23 | "package": "ImageSugar", 24 | "repositoryURL": "https://github.com/eonist/ImageSugar.git", 25 | "state": { 26 | "branch": "master", 27 | "revision": "badf21a795fd627f4a44c55c57c6f3626261662e", 28 | "version": null 29 | } 30 | }, 31 | { 32 | "package": "JSONSugar", 33 | "repositoryURL": "https://github.com/eonist/JSONSugar.git", 34 | "state": { 35 | "branch": "master", 36 | "revision": "f6ebe7fde5cbe574ae9516c73962ccd01e7208b1", 37 | "version": null 38 | } 39 | }, 40 | { 41 | "package": "NetworkSugar", 42 | "repositoryURL": "https://github.com/eonist/NetworkSugar.git", 43 | "state": { 44 | "branch": "master", 45 | "revision": "4da36c5b8fa9c2c1ac3784cbb2474ee66e51b6ff", 46 | "version": null 47 | } 48 | }, 49 | { 50 | "package": "ReusableCell", 51 | "repositoryURL": "https://github.com/eonist/ReusableCell.git", 52 | "state": { 53 | "branch": "master", 54 | "revision": "26413744c41ddcfaffaeb561c904449d784ef1d0", 55 | "version": null 56 | } 57 | }, 58 | { 59 | "package": "Spatial", 60 | "repositoryURL": "https://github.com/eonist/Spatial.git", 61 | "state": { 62 | "branch": "master", 63 | "revision": "d5db4874deba1ac3adb50124367870105ff46132", 64 | "version": null 65 | } 66 | }, 67 | { 68 | "package": "With", 69 | "repositoryURL": "https://github.com/eonist/with", 70 | "state": { 71 | "branch": "master", 72 | "revision": "db0935ecba9577470664105c25cf2c29481ec0fb", 73 | "version": null 74 | } 75 | } 76 | ] 77 | }, 78 | "version": 1 79 | } 80 | -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/project.xcworkspace/xcuserdata/andrejorgensen.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eonist/FlowLayout/f3148776c7852196b47383b92651661372402e3e/FlowLayout.xcodeproj/project.xcworkspace/xcuserdata/andrejorgensen.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/project.xcworkspace/xcuserdata/eon.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eonist/FlowLayout/f3148776c7852196b47383b92651661372402e3e/FlowLayout.xcodeproj/project.xcworkspace/xcuserdata/eon.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/xcshareddata/xcschemes/FlowLayout.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/xcuserdata/eon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /FlowLayout.xcodeproj/xcuserdata/eon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FlowLayout.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | FlowLayout.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | F116EBD2211046B40091439E 21 | 22 | primary 23 | 24 | 25 | F12442022248314700D808D8 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /FlowLayout/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | @UIApplicationMain 3 | 4 | final class AppDelegate: UIResponder, UIApplicationDelegate { 5 | lazy var window: UIWindow? = { 6 | let win = UIWindow(frame: UIScreen.main.bounds) 7 | let vc = ViewController() 8 | win.rootViewController = vc 9 | win.makeKeyAndVisible() // Important: ⚠️️ since we have no Main storyboard because this app uses programatic UI 10 | return win 11 | }() 12 | func applicationDidFinishLaunching(_ application: UIApplication) { 13 | _ = window 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FlowLayout/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /FlowLayout/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /FlowLayout/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FlowLayout/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /FlowLayout/ViewController+Create.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Spatial 3 | import With 4 | /** 5 | * Create 6 | */ 7 | extension ViewController { 8 | /** 9 | * Creates the FlowView 10 | */ 11 | func createFlowView() -> HorView { 12 | with(.init()) { 13 | view.addSubview($0) 14 | $0.anchorAndSize(to: view) 15 | } 16 | } 17 | /** 18 | * Creates custom flow view 19 | */ 20 | func createCustomFlowView() -> CustomView { 21 | with(.init()) { 22 | view.addSubview($0) 23 | $0.anchorAndSize(to: view) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FlowLayout/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | /** 3 | * FlowLayout 4 | * - Description: FlowLayout (Bi-directional layout framework) (Great for prototyping UX ideas and apps) 5 | * - Definition: bidirectional: functioning in two directions. 6 | * - Fixme: ⚠️️ Set the view, don't add to it, also set it as RootVC, like in weather, and reaname to VC 👈 7 | * - Fixme: ⚠️️ ReUse cell: https://tech.busuu.com/dealing-with-different-kinds-of-cells-in-swift-part-2-of-3-3fe73b0c50c6 8 | * - Fixme: ⚠️️ Apple video on advance collection views: https://developer.apple.com/videos/play/wwdc2014/232/ 9 | * - Fixme: ⚠️️ mixed sizes in Collection: https://octodev.net/custom-collectionviewlayout/ 10 | */ 11 | final class ViewController: UIViewController { 12 | lazy var flowView: HorView = createCustomFlowView() // createFlowView() 13 | } 14 | /** 15 | * Overrides 16 | */ 17 | extension ViewController { 18 | override var prefersStatusBarHidden: Bool { true } // hides statusbar 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | view = MainView() 22 | view.backgroundColor = .lightGray 23 | _ = flowView 24 | } 25 | /** 26 | * Set title 27 | * - Note: I guess this must be set from this method in order for it to work 28 | */ 29 | override func viewWillAppear(_ animated: Bool) { 30 | super.viewWillAppear(animated) 31 | // By using the setNav... method as oppose to the isNav... we get better animation 32 | self.navigationController?.setNavigationBarHidden(true, animated: true) // self.navigationController?.isNavigationBarHidden = false// 33 | navigationItem.title = "Main" // - Fixme: ⚠️️ use enum? 34 | } 35 | } 36 | final class MainView: UIView {} 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Eonist 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CommonCell", 6 | "repositoryURL": "https://github.com/eonist/CommonCell.git", 7 | "state": { 8 | "branch": "master", 9 | "revision": "b252d13b872e20e702776f1d51908c3140f820cd", 10 | "version": null 11 | } 12 | }, 13 | { 14 | "package": "FileSugar", 15 | "repositoryURL": "https://github.com/eonist/FileSugar.git", 16 | "state": { 17 | "branch": "master", 18 | "revision": "523b7325aa211c34d2622c365915969122ed02bc", 19 | "version": null 20 | } 21 | }, 22 | { 23 | "package": "ImageSugar", 24 | "repositoryURL": "https://github.com/eonist/ImageSugar.git", 25 | "state": { 26 | "branch": "master", 27 | "revision": "badf21a795fd627f4a44c55c57c6f3626261662e", 28 | "version": null 29 | } 30 | }, 31 | { 32 | "package": "JSONSugar", 33 | "repositoryURL": "https://github.com/eonist/JSONSugar.git", 34 | "state": { 35 | "branch": "master", 36 | "revision": "f6ebe7fde5cbe574ae9516c73962ccd01e7208b1", 37 | "version": null 38 | } 39 | }, 40 | { 41 | "package": "NetworkSugar", 42 | "repositoryURL": "https://github.com/eonist/NetworkSugar.git", 43 | "state": { 44 | "branch": "master", 45 | "revision": "4da36c5b8fa9c2c1ac3784cbb2474ee66e51b6ff", 46 | "version": null 47 | } 48 | }, 49 | { 50 | "package": "ReusableCell", 51 | "repositoryURL": "https://github.com/eonist/ReusableCell.git", 52 | "state": { 53 | "branch": "master", 54 | "revision": "26413744c41ddcfaffaeb561c904449d784ef1d0", 55 | "version": null 56 | } 57 | }, 58 | { 59 | "package": "Spatial", 60 | "repositoryURL": "https://github.com/eonist/Spatial.git", 61 | "state": { 62 | "branch": "master", 63 | "revision": "d5db4874deba1ac3adb50124367870105ff46132", 64 | "version": null 65 | } 66 | }, 67 | { 68 | "package": "With", 69 | "repositoryURL": "https://github.com/eonist/With.git", 70 | "state": { 71 | "branch": "master", 72 | "revision": "db0935ecba9577470664105c25cf2c29481ec0fb", 73 | "version": null 74 | } 75 | } 76 | ] 77 | }, 78 | "version": 1 79 | } 80 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "FlowLayout", 7 | platforms: [.iOS(.v12), .macOS(.v10_13)], 8 | products: [ 9 | .library( 10 | name: "FlowLayout", 11 | targets: ["FlowLayout"]) 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/eonist/CommonCell.git", .branch("master")), 15 | .package(url: "https://github.com/eonist/ImageSugar.git", .branch("master")), 16 | .package(url: "https://github.com/eonist/NetworkSugar.git", .branch("master")), 17 | .package(url: "https://github.com/eonist/JSONSugar.git", .branch("master")), 18 | .package(url: "https://github.com/eonist/With.git", .branch("master")), 19 | .package(url: "https://github.com/eonist/Spatial.git", .branch("master")) 20 | //.package(url: "https://github.com/eonist/RefreshControlKit.git", .branch("master")), 21 | //.package(url: "https://github.com/eonist/TestRunner.git", .branch("master")), 22 | //.package(url: "https://github.com/eonist/UITestSugar.git", .branch("master")) 23 | ], 24 | targets: [ 25 | .target( 26 | name: "FlowLayout", 27 | dependencies: ["CommonCell", "ImageSugar", "NetworkSugar", "JSONSugar", "With", "Spatial"]) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlowLayout 2 | ![Lang](https://img.shields.io/badge/Language-Swift%205.0-orange.svg) 3 | [![version badge](https://img.shields.io/badge/Version-1.0-blue.svg?longCache=true)](https://img.shields.io/badge/SDK-0.1-blue.svg?longCache=true) 4 | ![platform](https://img.shields.io/badge/Platform-iOS_12.2-blue.svg) 5 | [![codebeat badge](https://codebeat.co/badges/8139ef2c-3fcb-449d-bfa0-190b99b6f6a0)](https://codebeat.co/projects/github-com-eonist-flowlayout-master) 6 | [![SPM compatible](https://img.shields.io/badge/SPM-compatible-4BC51D.svg?style=flat)](https://github.com/apple/swift) 7 | [![codebeat badge](https://img.shields.io/badge/%235%20Producthunt-Aug--05--2018-red.svg)](https://www.producthunt.com/posts/flowlayout) 8 | [![SwiftLint Sindre](https://img.shields.io/badge/SwiftLint-Sindre-hotpink.svg)](https://github.com/sindresorhus/swiftlint-sindre) [![Tests](https://github.com/eonist/FlowLayout/actions/workflows/Tests.yml/badge.svg)](https://github.com/eonist/FlowLayout/actions/workflows/Tests.yml) 9 | 10 | FlowLayout - A bi-directional layout framework for iOS | Product Hunt 11 | 12 | img 13 | 14 | ### Description 15 | The idea is to give aspiring app developers a dead simple "vanilla" swift library to start with. Just add some end point calls to instagram and you could literally have a minimal insta client in a day. 16 | 17 | ### Features 18 | - 100% Programmatic 👌 19 | - [3.82 GPA on Codebeat](https://codebeat.co/projects/github-com-eonist-flowlayout-master) 🏆 20 | - Dual UICollectionView setup ↕️ ↔️ 21 | - Responsive header (Compact/Normal) 📏 22 | - Pull-to-refresh (vertical/horizontal) 🔄 23 | - Constraint animation 📐 24 | - Works on all iPhone/iPad models 📱 25 | - View based (no ViewController) 🖼 26 | - Vanilla swift 🍦 27 | - 0% syntactic sugar 🍭 28 | - Mostly off the shelf components 🤯 29 | 30 | ### Installation: 31 | - SPM: `.package(url: "https://github.com/eonist/FlowLayout.git", .branch("master"))` 32 | 33 | ### Pull to refresh: 34 | img 35 | 36 | ### Compact mode: 37 | img 38 | 39 | ### Swipe to new pages: 40 | img 41 | 42 | ### Requires 43 | - the Constraint extension (Included) 44 | 45 | ### Install 46 | - Manual: open `FlowLayout.xcodeproj` 47 | - Carthage: `github "eonist/FlowLayout" "master"` 48 | - CocoaPod (coming soon) 49 | 50 | ### Credits 51 | - Incredible gif via [Gifski](https://github.com/sindresorhus/gifski-app) 52 | - The awesome people from [Swift-lang](https://slofile.com/slack/swift-lang) on Slack 53 | 54 | ### Todo 55 | - Add stockimages 56 | - Fix compact mode so that the offset is inherited in the next view 57 | - Make a new IRL video with the new fixes 58 | - Keep improving the structure and clarity 59 | 60 | ### Press 61 | - Producthunt: [https://www.producthunt.com/posts/flowlayout](https://www.producthunt.com/posts/flowlayout) 62 | - [MediaKit.zip](https://www.dropbox.com/s/5s59k5e0o6z5y0g/mediakit.zip?dl=0) via dropbox 63 | - Blog post about FlowLayout [eon.codes/blog](http://eon.codes/blog/2018/08/05/Flow-Layout/) 64 | 65 | ### License 66 | [MIT](https://en.wikipedia.org/wiki/MIT_License) 67 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/ButtonContainer+Const.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import QuartzCore 3 | 4 | extension ButtonContainer { 5 | static let defaultOnButtonTap: OnButtonTap = { _ in Swift.print("Callback is missing") } 6 | static let height: CGFloat = 60 7 | } 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/ButtonContainer+Create.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | import Spatial 5 | /** 6 | * Create 7 | */ 8 | extension ButtonContainer { 9 | /** 10 | * Creates buttons 11 | * - Fixme: ⚠️️ use with 12 | */ 13 | func createButtons() -> [HeaderButton] { 14 | let titles: [String] = ColumnCellType.allCases.map { $0.rawValue.capitalized } 15 | let buttons: [HeaderButton] = titles.map { title in // All vertically centered, 30p height each 16 | with(.init(title: title)) { 17 | $0.addTarget(self, action: #selector(onTouchInside), for: .touchUpInside) 18 | self.addSubview($0) 19 | } 20 | } 21 | let widthMultiplier: CGFloat = 1 / CGFloat(titles.count) // 75pix on iphone 8 22 | buttons.distributeAndSize(dir: .hor, height: ButtonContainer.height, align: .topLeft, alignTo: .topLeft, multiplier: .init(width: widthMultiplier, height: 1)) 23 | buttons.first?.setTitleColor(HorView.style.header.button.selectedFontColor, for: .normal) 24 | buttons.first?.titleLabel?.font = HorView.style.header.button.selectedFont 25 | return buttons 26 | } 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/ButtonContainer+Interaction.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Handler 5 | */ 6 | extension ButtonContainer { 7 | /** 8 | * Tap 9 | */ 10 | @objc func onTouchInside(sender: UIButton) { 11 | if let headerBtn = sender as? HeaderButton, let headerTitle = headerBtn.currentTitle { 12 | onButtonClick(headerTitle) 13 | } 14 | } 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/ButtonContainer+Setter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | 4 | extension ButtonContainer { 5 | /** 6 | * Set index 7 | */ 8 | func setIdx(idx: Int) { 9 | // Swift.print("BtnContainer.setIdx(\(idx))") 10 | let btnTitle: String? = ColumnCellType.allCases[idx].rawValue.capitalized 11 | // Swift.print("btnTitle: \(String(describing: btnTitle))") 12 | buttons.filter { $0.titleLabel?.text == btnTitle }.forEach { setActive(btn: $0) } 13 | } 14 | /** 15 | * Set active state 16 | */ 17 | func setActive(btn: UIButton) { 18 | // Swift.print("setActive: \(String(describing: setActive))") 19 | buttons.forEach { $0.setActive(isActive: $0 == btn) } 20 | } 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/ButtonContainer+TypeAlias.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * Callback signature 5 | */ 6 | extension ButtonContainer { 7 | typealias OnButtonTap = (_ buttonTitle: String) -> Void 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/ButtonContainer.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | import Spatial 5 | /** 6 | * This is the view that has the header buttons (aka primary, secondary, tierary) 7 | */ 8 | class ButtonContainer: UIView { 9 | lazy var buttons: [HeaderButton] = self.createButtons() 10 | var onButtonClick: OnButtonTap = defaultOnButtonTap 11 | override init(frame: CGRect) { 12 | super.init(frame: frame) 13 | let bg: UIView = .init() 14 | bg.backgroundColor = .green 15 | self.addSubview(bg) // Add a background to 16 | _ = buttons 17 | } 18 | /** 19 | * Boilerplate 20 | */ 21 | required init(coder: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/HeaderButton/CustomButton.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | import With 5 | 6 | class CustomButton: UIButton, ConstraintKind { 7 | // - Fixme: ⚠️️ use anchorAndSize var 8 | var anchor: (x: NSLayoutConstraint, y: NSLayoutConstraint)? 9 | var size: (w: NSLayoutConstraint, h: NSLayoutConstraint)? 10 | init(title: String) { 11 | super.init(frame: .zero) 12 | with(self) { 13 | $0.backgroundColor = .clear 14 | $0.titleLabel?.font = UIFont.systemFont(ofSize: 16.0) 15 | $0.titleLabel?.textAlignment = .center 16 | $0.setTitleColor(.black, for: .normal) 17 | $0.setTitle(title, for: .normal) 18 | } 19 | } 20 | required init?(coder aDecoder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/HeaderButton/HeaderButton+Const.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import QuartzCore 3 | /** 4 | * Const 5 | */ 6 | extension HeaderButton { 7 | static var defautOnTap: OnTap = { Swift.print("HeaderButton.defaultClickCallBack() - no call back attached") } 8 | static let width: CGFloat = 60 9 | static let height: CGFloat = 20 // Fixme: combine width and height to size 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/HeaderButton/HeaderButton+Interactive.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | 4 | extension HeaderButton { 5 | /** 6 | * Selector handler 7 | */ 8 | @objc func buttonTouched(sender: UIButton) { 9 | clickCallBack() // Calls whichever method that is attached to the call-back variable 10 | } 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/HeaderButton/HeaderButton+Setter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * Setter 5 | */ 6 | extension HeaderButton { 7 | /** 8 | * Sets the button to active mode 9 | */ 10 | func setActive(isActive: Bool) { 11 | if isActive { 12 | self.setTitleColor(HorView.style.header.button.selectedFontColor, for: .normal) 13 | self.titleLabel?.font = HorView.style.header.button.selectedFont 14 | } else { 15 | self.setTitleColor(HorView.style.header.button.unSelectedFontColor, for: .normal) 16 | self.titleLabel?.font = HorView.style.header.button.unSelectedFont 17 | } 18 | } 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/HeaderButton/HeaderButton+Type.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * Type 5 | */ 6 | extension HeaderButton { 7 | typealias OnTap = () -> Void 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/ButtonContainer/HeaderButton/HeaderButton.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * headerButton 5 | */ 6 | final class HeaderButton: CustomButton { 7 | var clickCallBack: OnTap = HeaderButton.defautOnTap 8 | override init(title: String) { 9 | super.init(title: title) 10 | self.addTarget(self, action: #selector(buttonTouched), for: .touchUpInside) 11 | } 12 | /** 13 | * Boilerplate 14 | */ 15 | required init?(coder aDecoder: NSCoder) { 16 | fatalError("init(coder:) has not been implemented") 17 | } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Header+Const.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | /** 5 | * Const 6 | */ 7 | extension Header { 8 | internal static let height: CGFloat = HeaderTitle.height + ButtonContainer.height + Slider.height 9 | } 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Header+Create.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | import With 5 | /** 6 | * Create 7 | */ 8 | extension Header { 9 | /** 10 | * TopFix (to cover up a void space) 11 | */ 12 | func createTopFix() -> UIView { 13 | with(.init()) { 14 | self.addSubview($0) 15 | $0.anchorAndSize(to: self, height: Slider.height, align: .topLeft, alignTo: .topLeft) 16 | $0.backgroundColor = HorView.style.header.backgroundColor 17 | } 18 | } 19 | /** 20 | * Creates header title 21 | */ 22 | func createHeaderTitle() -> HeaderTitle { 23 | with(.init()) { 24 | $0.setTitleText(text: ColumnCellType.primary.rawValue.capitalized) 25 | self.addSubview($0) 26 | $0.anchorAndSize(to: self.topFix, sizeTo: self, height: HeaderTitle.height, align: .topLeft, alignTo: .bottomLeft) 27 | } 28 | } 29 | /** 30 | * Creates buttons 31 | */ 32 | func createButtonContainer() -> ButtonContainer { 33 | with(.init(frame: .zero)) { 34 | addSubview($0) 35 | $0.anchorAndSize(to: headerTitle, sizeTo: self, height: ButtonContainer.height, align: .topLeft, alignTo: .bottomLeft) 36 | } 37 | } 38 | /** 39 | * Create slider 40 | */ 41 | func createSlider() -> Slider { 42 | let segmentCount: Int = ColumnCellType.allCases.count 43 | return with(.init(idx: 0, segmentCount:segmentCount, frame: .zero)) { 44 | self.addSubview($0) 45 | $0.anchorAndSize(to: self, height: Slider.height, align: .bottomLeft, alignTo: .bottomLeft) 46 | } 47 | } 48 | /** 49 | * Graphic fix (When you drag the list up and down this covers to match header color) 50 | * - Note: Basically you can drag down the header 500px before it shows 51 | * - Note: this doesn't need to be saved as a param 52 | */ 53 | func createBackgroundFix() -> UIView { 54 | with(.init(frame: .zero)) { 55 | $0.backgroundColor = HorView.style.header.backgroundColor 56 | self.addSubview($0) 57 | $0.anchorAndSize(to: self, height: 500, align: .bottomLeft, alignTo: .topLeft) 58 | } 59 | } 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Header+Setter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | /** 5 | * Accessor 6 | */ 7 | extension Header { 8 | /** 9 | * Set index for title label 10 | */ 11 | func setTitleIdx(idx: Int) { 12 | let title: String = ColumnCellType.allCases[idx].rawValue // ?? "undefined" 13 | headerTitle.text = title 14 | } 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Header.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Header 5 | * - Note: Essentially contains: (title, buttons, slider) 6 | */ 7 | open class Header: UIView { 8 | lazy var topFix: UIView = self.createTopFix() // to cover up a void space 9 | lazy var headerTitle: UILabel = self.createHeaderTitle() 10 | lazy var buttonContainer: ButtonContainer = self.createButtonContainer() 11 | lazy var slider: Slider = self.createSlider() 12 | override public init(frame: CGRect = .zero) { 13 | super.init(frame: frame) 14 | self.backgroundColor = HorView.style.header.backgroundColor 15 | _ = createBackgroundFix() 16 | _ = topFix 17 | _ = headerTitle 18 | _ = buttonContainer 19 | _ = slider 20 | } 21 | /** 22 | * Boilerplate 23 | */ 24 | @available(*, unavailable) 25 | public required init(coder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/HeaderTitle/HeaderTitle+Const.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import QuartzCore 3 | 4 | extension HeaderTitle { 5 | internal static let height: CGFloat = 60 6 | } 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/HeaderTitle/HeaderTitle+Setter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | extension HeaderTitle { 5 | func setTitleText(text: String) { 6 | self.text = text 7 | } 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/HeaderTitle/HeaderTitle.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | 4 | final class HeaderTitle: UILabel { 5 | override init(frame: CGRect) { 6 | super.init(frame: frame) 7 | self.font = HorView.style.header.title.font 8 | self.textAlignment = .center 9 | self.backgroundColor = .clear 10 | self.textColor = HorView.style.header.title.color 11 | } 12 | /** 13 | * Boilerplate 14 | */ 15 | required init?(coder aDecoder: NSCoder) { 16 | fatalError("init(coder:) has not been implemented") 17 | } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Slider/Slider+Animation.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Animation 5 | */ 6 | extension Slider { 7 | typealias OnComplete = () -> Void 8 | /** 9 | * - Parameters: 10 | * - to: the amount to offset in the X dir 11 | * - onComplete: called when the animation completes 12 | */ 13 | func animate(to: CGFloat, onComplete:@escaping OnComplete = {}) { 14 | Swift.print("Slider.animate: \(to)") 15 | UIView.animate({ // Animate 16 | self.setProgress(to: to) 17 | self.layoutIfNeeded() 18 | }, onComplete: onComplete) 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Slider/Slider+Const.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | 4 | extension Slider { 5 | static let height: CGFloat = 10 6 | } 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Slider/Slider+Create.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | import With 5 | /** 6 | * Create 7 | */ 8 | extension Slider { 9 | /** 10 | * Creates the slider bar 11 | */ 12 | func createSliderBar() -> SliderBar { 13 | with(.init()) { 14 | addSubview($0) 15 | let sliderWidthMultiplier: CGFloat = 1 / CGFloat(segmentCount) 16 | $0.applyAnchorAndSize(to: self, height: Slider.height, align: .topLeft, alignTo: .topLeft, multiplier: .init(width: sliderWidthMultiplier, height: 1)) 17 | } 18 | } 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Slider/Slider+Setter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | /** 5 | * Accesor 6 | */ 7 | extension Slider { 8 | /** 9 | * Sets index 10 | */ 11 | func setIdx(idx: Int) { 12 | let sliderBarWidth: CGFloat = self.frame.width / CGFloat(segmentCount) 13 | let x: CGFloat = sliderBarWidth * CGFloat(idx) 14 | animate(to: x) 15 | } 16 | /** 17 | * Set progress 18 | * - Parameter progress: 0 - 1 19 | */ 20 | func setProgress(progress: CGFloat) { 21 | let sliderBarWidth: CGFloat = { self.frame.width / CGFloat(segmentCount) }() 22 | let x: CGFloat = sliderBarWidth * progress 23 | setProgress(to: x) 24 | } 25 | /** 26 | * - Parameter to: the x-position to set to 27 | */ 28 | func setProgress(to: CGFloat) { 29 | self.sliderBar.update(offset: to, align: .left, alignTo: .left) 30 | } 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Slider/Slider.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | /** 5 | * - Abstract: The topBar slider that indicates where you are horizontally 6 | */ 7 | final class Slider: UIView { 8 | lazy var sliderBar: SliderBar = createSliderBar() 9 | let segmentCount: Int 10 | let idx: Int 11 | /** 12 | * - Fixme: ⚠️️ add doc 13 | */ 14 | init(idx: Int, segmentCount: Int, frame: CGRect) { 15 | self.segmentCount = segmentCount 16 | self.idx = idx 17 | super.init(frame: frame) 18 | backgroundColor = HorView.style.slider.backgroundColor 19 | _ = sliderBar 20 | } 21 | /** 22 | * Boilerplate 23 | */ 24 | @available(*, unavailable) 25 | required init?(coder aDecoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /Sources/FlowLayout/Header/Slider/SliderBar/SliderBar.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | 5 | final class SliderBar: UIView, ConstraintKind { 6 | // - Fixme: ⚠️️ use anchorAndSize var 7 | var size: SizeConstraint? 8 | var anchor: AnchorConstraint? 9 | override init(frame: CGRect = .zero) { 10 | super.init(frame: frame) 11 | backgroundColor = HorView.style.slider.foregroundColor 12 | } 13 | /** 14 | * Boilerplate 15 | */ 16 | required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Collection.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import ReusableCell 4 | /** 5 | * CollectionView related 6 | */ 7 | extension HorView { 8 | /** 9 | * Num of items in table 10 | */ 11 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 12 | items.count 13 | } 14 | /** 15 | * Respawns cells 16 | * - Fixme: ⚠️️ use simpler code for dequeuing cells, see cell frameworks 17 | */ 18 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 19 | let cell: HorCell = { 20 | switch indexPath.row { 21 | case ColumnCellType.primary.idx: 22 | let cell: PrimaryHorCell = collectionView.dequeueReusableCell(indexPath: indexPath) 23 | let imageURLStr: String = "https://rawgit.com/stylekit/img/master/" + "pic_1_thumb.png" 24 | _ = imageURLStr 25 | let urls: [String] = .init(repeating: "🎉", count: 11) // ["a","b","c","d","a","b","c","d","a","b","c"] 26 | cell.data = PrimaryCellData(thumbURLS: urls) // When you set this, the data is applied to the UI 27 | return cell 28 | case ColumnCellType.secondary.idx: 29 | guard let cell: SecondaryHorCell = collectionView.dequeueReusableCell(withReuseIdentifier: SecondaryHorCell.id, for: indexPath as IndexPath) as? SecondaryHorCell else { fatalError("err") } 30 | return cell 31 | case ColumnCellType.tertiary.idx: 32 | guard let cell: TertiaryHorCell = collectionView.dequeueReusableCell(withReuseIdentifier: TertiaryHorCell.id, for: indexPath as IndexPath) as? TertiaryHorCell else { fatalError("err") } 33 | return cell 34 | default: fatalError("err: \(indexPath.row)") 35 | } 36 | }() 37 | // We add onScroll callback to every cell 38 | cell.onScroll = onVerticalScroll // Attach scoll-call-back-closure 39 | // cell.onItemSelect = {indexPath in Swift.print("HorView.cell.onItemSelect: \(indexPath) in cellIdx:\(self.currentPageIndex)")}//callback for cell click 40 | return cell 41 | } 42 | } 43 | /** 44 | * This method is interesting, might be able to have different sized cells in the same collectionview 45 | */ 46 | // func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 47 | // return CGSize(width: self.frame.width, height: self.frame.height - 50) 48 | // } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Create.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import Spatial 4 | import With 5 | /** 6 | * HorView 7 | */ 8 | extension HorView { 9 | /** 10 | * Creates header 11 | */ 12 | @objc open func createHeader() -> Header { 13 | with(.init()) { // Header has title, buttons, slider 14 | addSubview($0) 15 | $0.anchorAndSize(to: self, height: Header.height) 16 | $0.buttonContainer.onButtonClick = onButtonClick 17 | } 18 | } 19 | /** 20 | * Creates horizontal collectionview 21 | * - Note: Overridable in subclass for customization 22 | * - Fixme: ⚠️️ rename to createColumnView 23 | */ 24 | @objc open func createCollectionView() -> UICollectionView { 25 | let flowLayout: UICollectionViewFlowLayout = createLayout() 26 | return with(.init(frame: self.frame, collectionViewLayout: flowLayout)) { 27 | $0.dataSource = self // We must set datasource because the framework is view based not VC based 28 | $0.delegate = self // We must set delegate because the framework is view based not VC based 29 | $0.backgroundColor = .clear 30 | $0.contentInset = .zero // Reset contentInset 31 | $0.scrollIndicatorInsets = .zero // Reset scrollIndicator insets 32 | $0.isPagingEnabled = true // Makes each cell snap to whole integers 33 | $0.showsHorizontalScrollIndicator = false // Hides scrollbar 34 | self.insertSubview($0, belowSubview: header) 35 | $0.anchorAndSize(to: self) 36 | } 37 | } 38 | } 39 | /** 40 | * Create 41 | */ 42 | extension HorView { 43 | /** 44 | * Returns layout 45 | */ 46 | private func createLayout() -> UICollectionViewFlowLayout { 47 | with(.init()) { 48 | $0.itemSize = UIScreen.main.bounds.size // The size of each collection item 49 | $0.scrollDirection = .horizontal // Enables horizontal scrolling direction 50 | $0.minimumLineSpacing = 0 // Removes the line-spacing between cells 51 | } 52 | } 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Event.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Event 5 | */ 6 | extension HorView { 7 | /** 8 | * On vertical scroll callback 9 | * 1. Make Header-Button-Container stick to top 10 | * 2. Make Header stick to downward movement 11 | * 3. Allow Table to slide under Header 12 | */ 13 | func onVerticalScroll(yOffset: CGFloat) { 14 | let y: CGFloat = (-1 * yOffset) - Header.height // offset y starts from 0 (easier to reason about) 15 | let headerY: CGFloat = { 16 | if y <= 0 && y >= -HeaderTitle.height { return y } // if y is negative, and y is within -HeaderTitle.height 17 | else if y < -HeaderTitle.height { return -HeaderTitle.height } // if y is beyond -Header.height, then only hide header-title 18 | else { return y } // if y is possetive, then set headerY directly 19 | }() 20 | self.header.frame.origin.y = headerY // - Fixme: ⚠️️ rather use Autolayout to update this 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Getter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import QuartzCore 3 | 4 | extension HorView { 5 | /** 6 | * Calculates the current page 7 | */ 8 | internal var currentPageIndex: Int { 9 | let x: CGFloat = collectionView.contentOffset.x 10 | let w: CGFloat = collectionView.bounds.size.width 11 | return .init(round(x / w)) 12 | } 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Register.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import ReusableCell 4 | /** 5 | * Core 6 | */ 7 | extension HorView { 8 | /** 9 | * Register cell types 10 | * - Fixme: ⚠️️ make the [].register call 11 | */ 12 | @objc func registerCellTypes() { 13 | collectionView.register([PrimaryHorCell.self, SecondaryHorCell.self, TertiaryHorCell.self].map { $0 as HorCell.Type }) // Register Cells with ease 14 | } 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Scroll.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Scrolling 5 | * - Note: Pagining in iOS is snapping to whole pages 6 | * - Reference: Horizontal CollectionView: https://github.com/maximbilan/UICollectionViewHorizontalPaging 7 | */ 8 | extension HorView { 9 | /** 10 | * - Fixme: ⚠️️ Might want to make a bool flag to avoid calling if the page was the same as the last 11 | */ 12 | public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 13 | header.setTitleIdx(idx: currentPageIndex) 14 | header.buttonContainer.setIdx(idx: currentPageIndex) 15 | } 16 | /** 17 | * Called when the user scrolls in the horizontal direction 18 | * - Important: ⚠️️ You want to reference the param instead of self.collectionView, as the collection view isn't ready when viewDidScroll is called. It's called on the init of the app. 19 | */ 20 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 21 | let x = scrollView.contentOffset.x 22 | let w = scrollView.bounds.size.width 23 | let currentProgress = x / w 24 | header.slider.setProgress(progress: currentProgress) // Moves the slider left and right 25 | } 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Setter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | extension HorView { 5 | /** 6 | * Set index 7 | * - Fixme: ⚠️️ add doc 8 | */ 9 | func setIdx(idx: Int) { 10 | header.setTitleIdx(idx: idx) 11 | setCollectionViewIndex(idx: idx) 12 | } 13 | /** 14 | * Scrolls to collectionview index 15 | */ 16 | func setCollectionViewIndex(idx: Int) { 17 | let indexPath = IndexPath(row: idx, section: 0) 18 | self.collectionView.scrollToItem(at: indexPath, at: .right, animated: true) 19 | collectionView.setNeedsLayout() // ⚠️️ might not be needed 20 | collectionView.layoutIfNeeded() // ⚠️️ might not be needed 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Style.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Type 5 | * - Fixme: ⚠️️ maybe move into own HorStyle scope? 6 | */ 7 | extension HorView { 8 | typealias Style = (header: HeaderStyle, backgroundColor: UIColor, slider: SliderStyle, horCollectionViewBackgroundColor: UIColor) 9 | typealias HeaderStyle = (backgroundColor: UIColor, title: HeaderTitleStyle, button: HeaderButtonStyle) 10 | typealias HeaderTitleStyle = (color: UIColor, font: UIFont) 11 | typealias HeaderButtonStyle = (selectedFontColor: UIColor, unSelectedFontColor: UIColor, selectedFont: UIFont, unSelectedFont: UIFont) 12 | typealias SliderStyle = (backgroundColor: UIColor, foregroundColor: UIColor) 13 | } 14 | /** 15 | * Constant 16 | */ 17 | extension HorView { 18 | static let style: Style = (headerStyle, .blue, sliderStyle, .lightGray) 19 | private static let headerStyle: HeaderStyle = (.blue, headerTitleStyle, headerButtonStyle) 20 | private static let headerTitleStyle: HeaderTitleStyle = (.white, .systemFont(ofSize: 22)) 21 | private static let headerButtonStyle: HeaderButtonStyle = (.white, .black, .boldSystemFont(ofSize: 16), .systemFont(ofSize: 16)) 22 | private static let sliderStyle: SliderStyle = (.blue, .white) 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView+Update.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Update 5 | */ 6 | extension HorView { 7 | /** 8 | * Gets all the transactions and updates the TableView items 9 | * - Abstract: We call this after we receive data from a webservice 10 | * - Fixme: ⚠️️ Add weak self here 11 | */ 12 | func updateCollectionView() { 13 | DispatchQueue.main.async { // Jump on the main thread for UI update 14 | self.items = [.primary, .secondary, .tertiary] // 3, 4, 5, 5, 7, 8, 9, 10, 11, 12 15 | self.collectionView.reloadData() // Updates collectionView 16 | } 17 | } 18 | /** 19 | * onButtonClick (When user clicks header buttons) 20 | * - Fixme: ⚠️️ Move to +Event file 21 | */ 22 | func onButtonClick(buttonTitle: String) { 23 | if let cellType = ColumnCellType(rawValue: buttonTitle.lowercased()) { 24 | guard let idx: Int = cellType.idx else { return } // 0, 1, 2 converts button to index 25 | self.setIdx(idx: idx) 26 | self.header.buttonContainer.setIdx(idx: idx) 27 | } 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/HorView.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * - Abstract: the Horisontal view holds the collectionView for the vertical cells 5 | * - Fixme: ⚠️️ In theory it could be an Idea to extend UICollectionView directly, instead of UIView 6 | */ 7 | open class HorView: UIView, UICollectionViewDataSource, UICollectionViewDelegate { 8 | lazy var header: Header = self.createHeader() // Adds the header 9 | // - Fixme: ⚠️️ rename to collumnsView? 10 | lazy var collectionView: UICollectionView = self.createCollectionView() // Adds the collectionView 11 | // - Fixme: ⚠️️ maybe rename to columns? 12 | var items: [ColumnCellType] // Aka columns 13 | override public init(frame: CGRect = .zero) { 14 | items = [.primary, .secondary, .tertiary] 15 | super.init(frame: frame) 16 | self.backgroundColor = HorView.style.backgroundColor 17 | _ = header 18 | _ = collectionView // Inits the collectionView, works with empty data, before we get data from remote 19 | registerCellTypes() // Register cells 20 | // updateCollectionView() // updates the collection view with new data etc 21 | } 22 | /** 23 | * Boilerplate 24 | */ 25 | @available(*, unavailable) 26 | public required init?(coder aDecoder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Sources/FlowLayout/HorView/utils/ColumnCellType.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * CellType 5 | * - Fixme: ⚠️️ rename to ColumnCellType 6 | */ 7 | enum ColumnCellType: String, CaseIterable { 8 | case primary, secondary, tertiary 9 | } 10 | /** 11 | * Getter 12 | */ 13 | extension ColumnCellType { 14 | var idx: Int? { ColumnCellType.allCases.firstIndex(of: self) } 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell+Collection.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * CollectionView related 5 | */ 6 | extension HorCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { 7 | /** 8 | * Num of items in section 9 | */ 10 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 11 | self.count 12 | } 13 | /** 14 | * Respawns cells @ indexPath 15 | */ 16 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 17 | dequeCell(cellForItemAt: indexPath) 18 | } 19 | /** 20 | * On item click 21 | */ 22 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 23 | onItemSelect(indexPath) 24 | } 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell+Const.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | import QuartzCore 4 | 5 | extension HorCell { 6 | open class var defaultReuseIdentifier: String { String(describing: self) } // fatalError("must be overrideen in subclass") 7 | static let margin: CGFloat = 32 8 | // Default callbacks 9 | static let defaultOnItemSelect: OnItemSelect = { indexPath in Swift.print("indexPath: \(indexPath)") } 10 | static let defaultOnScroll: OnScroll = { _ in Swift.print("HorCell.scroll - no callback attached") } 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell+Core.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Core 5 | */ 6 | extension HorCell { 7 | /** 8 | * Override in sub class 9 | * - Note: This method exists so that we can subclass HorCell and register different child cell types 10 | * - Fixme: ⚠️️ Replace this method with accociate type etc. See ref link in ViewController.swift 11 | */ 12 | @objc func registerCell(collectionView: UICollectionView) { 13 | collectionView.register(VerCell.self, forCellWithReuseIdentifier: VerCell.id) 14 | } 15 | /** 16 | * Override in sub class 17 | * - Note: This method exists so that we can subclass HorCell and register different child cell types 18 | * - Fixme: ⚠️️ Replace this method with accociate type etc. See ref link in ViewController.swift 19 | */ 20 | @objc func dequeCell(cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 21 | guard let cell: VerCell = collectionView.dequeueReusableCell(withReuseIdentifier: VerCell.id, for: indexPath as IndexPath) as? VerCell else { fatalError("err") } 22 | return cell 23 | } 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell+Create.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | import Spatial 5 | /** 6 | * Create 7 | */ 8 | extension HorCell { 9 | /** 10 | * This adds the custom look to the table (Overridable) 11 | * - Note: overrideable with subclass 12 | * - Note: flowLayout.scrollDirection is vertical by default 13 | */ 14 | @objc open func createLayout() -> UICollectionViewFlowLayout { 15 | with(.init()) { 16 | let margin = HorCell.margin 17 | $0.sectionInset = .init(top: margin, left: margin, bottom: margin, right: margin) 18 | $0.minimumInteritemSpacing = 0 19 | $0.minimumLineSpacing = margin // Vertical spacing 20 | let itemSize: CGSize = { 21 | let screenWidth: CGFloat = UIScreen.main.bounds.size.width 22 | let width: CGFloat = (screenWidth - (margin * 3)) / 2 23 | let height: CGFloat = width // width + (width * 0.33) 24 | return .init(width: width, height: height) 25 | }() 26 | $0.itemSize = itemSize 27 | } 28 | } 29 | /** 30 | * Creates collectionview 31 | */ 32 | func createCollectionView() -> UICollectionView { 33 | let layout: UICollectionViewFlowLayout = createLayout() 34 | return with(.init(frame: self.frame, collectionViewLayout: layout)) { 35 | $0.dataSource = self // We must set datasource because the framework is view based not VC based 36 | $0.delegate = self // We must set delegate because the framework is view based not VC based 37 | $0.contentInset = .init(top: Header.height, left: 0, bottom: 0, right: 0) // offsets the content, so that sticky header works etc 38 | $0.scrollIndicatorInsets = .init(top: Header.height, left: 0, bottom: 0, right: 0) 39 | self.registerCell(collectionView: $0) // Register cell kind 40 | $0.backgroundColor = .clear // The HorCell it self also has a bg so we set this to clear 41 | self.addSubview($0) 42 | // ⚠️️ We have to use a constraint or else the double UICollection setup starts to behave strangly ⚠️️ 43 | $0.anchorAndSize(to: self) // The view needs to use constraints or else AutoLayout wont work with dual UICollectionView 44 | } 45 | } 46 | } 47 | #endif 48 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell+Getter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * Constants 5 | */ 6 | extension HorCell { 7 | @objc class var id: String { "\(HorCell.self)" } // Stores the deque cell id, overrideable in subclasses 8 | @objc var count: Int { items.count } // override this in subclasses 9 | } 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell+Scroll.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Scroll 5 | */ 6 | extension HorCell { 7 | /** 8 | * Used to make the NavBar stick to the tableView 9 | * - Note: Calls a callback that notifies external UI elements to reposition etc 10 | * - Note: You can get the statusBarheight from UIApplication.shared.statusBarFrame.height if you also want to hide the statusbar 11 | */ 12 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 13 | onScroll(scrollView.contentOffset.y) 14 | } 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell+Type.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Helper 5 | */ 6 | extension HorCell { 7 | typealias OnScroll = (_ yOffset: CGFloat) -> Void // Needed to get the sticky header to work 8 | typealias OnItemSelect = (_ indexPath: IndexPath) -> Void 9 | } 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell+Update.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * Update 5 | */ 6 | extension HorCell { 7 | /** 8 | * Updates the TableView items 9 | */ 10 | func updateCollectionView() { 11 | // Swift.print("Update collectionView👌") 12 | DispatchQueue.main.async { // jump on the main thread for UI update 13 | //Swift.print("got json from remote 👌") 14 | self.items = [0, 1, 2, 3, 4, 5, 7] 15 | self.collectionView.reloadData() // Updates collectionView 16 | } 17 | } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/HorCell/HorCell.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | import Spatial 5 | import ReusableCell 6 | /** 7 | * - Abstract A horizontal Cell that has a vertical collectionView 8 | * - Note: This class is later subclassed as a generic class, and as such overriding things in extension doesn't work 9 | * - Fixme: ⚠️️ rename to ColumnCell? 10 | */ 11 | class HorCell: UICollectionViewCell, ReusableCellKind { 12 | lazy var collectionView: UICollectionView = self.createCollectionView() // Vertical collection view 13 | var items: [Int] // Vertical items 14 | var onScroll: OnScroll = defaultOnScroll 15 | var onItemSelect: OnItemSelect = defaultOnItemSelect 16 | var data: CellDataKind? // Stores cellData 17 | /** 18 | * Initiate 19 | */ 20 | override init(frame: CGRect) { 21 | items = [0, 1, 2, 3, 4, 5, 7, 8, 9] // Dummy model data 22 | super.init(frame: frame) 23 | _ = collectionView // Initiates the collectionView, works with empty data, before we get data from remote 24 | self.backgroundColor = HorView.style.horCollectionViewBackgroundColor 25 | // updateCollectionView() // Updates the collection view with data 26 | } 27 | /** 28 | * Boilerplate 29 | */ 30 | @available(*, unavailable) 31 | required init?(coder aDecoder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/VerCell/VerCell.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Browser cell for CollectionView 5 | * - Fixme: ⚠️️ rename to rowcell? 6 | */ 7 | class VerCell: UICollectionViewCell { 8 | class var id: String { "\(VerCell.self)" } // Stores the deque cell id, overrideable in subclasses 9 | override init(frame: CGRect) { 10 | super.init(frame: frame) 11 | self.backgroundColor = UIColorParser.random 12 | } 13 | /** 14 | * Boilerplate 15 | */ 16 | @available(*, unavailable) 17 | required init?(coder aDecoder: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/VerCell/custom/ImageCell/ImageCell+Create.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | import Spatial 5 | /** 6 | * Create 7 | */ 8 | extension ImageCell { 9 | /** 10 | * Creates ImgView 11 | */ 12 | func createImgView() -> UIImageView { 13 | with(.init()) { // Create the image view 14 | $0.contentMode = .scaleAspectFill // Fills the view 15 | $0.backgroundColor = .lightGray 16 | // adds a ciruclar mask to the image-view 17 | let cornerRadius = self.contentView.frame.height / 2 18 | $0.layer.cornerRadius = cornerRadius 19 | $0.clipsToBounds = true 20 | self.contentView.addSubview($0) 21 | // adds constraints 22 | $0.translatesAutoresizingMaskIntoConstraints = false 23 | $0.anchorAndSize(to: contentView ) 24 | } 25 | } 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/VerCell/custom/ImageCell/ImageCell.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Beta (Experimental) 5 | */ 6 | class ImageCell: UITableViewCell { 7 | lazy var imgView: UIImageView = self.createImgView() 8 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 9 | super.init(style: style, reuseIdentifier: reuseIdentifier) 10 | self.selectionStyle = .none // Disables the selection highlighting 11 | _ = imgView // Initiate UI's 12 | } 13 | @available(*, unavailable) 14 | required init?(coder aDecoder: NSCoder) { 15 | fatalError("init(coder:) has not been implemented") 16 | } 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/VerCell/custom/PrimaryVerCell/PrimaryVerCell+Core.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | /** 5 | * Core 6 | */ 7 | extension PrimaryVerCell { 8 | /** 9 | * If the view is redrawn then this is called 10 | */ 11 | override func layoutSubviews() { 12 | super.layoutSubviews() 13 | drawCircle() 14 | } 15 | /** 16 | * Adds a UIView that is round 17 | * - Fixme: ⚠️️ Use CGShapeUtil and draw a circle instead, faster and more performant 18 | */ 19 | func drawCircle() { 20 | let rect: CGRect = .init(origin: .zero, size: .init(width: self.frame.width, height: self.frame.width)) 21 | self.subviews.first?.removeFromSuperview() // Removes previouse graphics if it exists 22 | with(UIView(frame: rect)) { 23 | $0.layer.cornerRadius = self.frame.width / 2 24 | $0.layer.masksToBounds = true 25 | $0.backgroundColor = UIColorParser.random 26 | addSubview($0) 27 | } 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/VerCell/custom/PrimaryVerCell/PrimaryVerCell.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | /** 5 | * Primary vertical cell 6 | */ 7 | class PrimaryVerCell: VerCell { 8 | override class var id: String { "\(PrimaryHorCell.self)" } // Stores the deque cell id 9 | override init(frame: CGRect) { 10 | super.init(frame: frame) 11 | self.backgroundColor = .clear 12 | } 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/PrimaryCellData/CellDataKind.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * Base protocol for Horizontal cells 5 | */ 6 | protocol CellDataKind {} 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/PrimaryCellData/PrimaryCellData.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | 4 | struct PrimaryCellData: CellDataKind { 5 | let thumbURLS: [String] 6 | } 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/PrimaryHorCell/PrimaryHorCell+Core.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | /** 5 | * Core 6 | */ 7 | extension PrimaryHorCell { 8 | /** 9 | * Register cells for CollectionView 10 | */ 11 | override func registerCell(collectionView: UICollectionView) { 12 | collectionView.register(PrimaryVerCell.self, forCellWithReuseIdentifier: PrimaryVerCell.id) 13 | } 14 | /** 15 | * Deques cells for CollectionView 16 | * - Fixme: ⚠️️ Remove this method, it's superflouse, rather just override the caller 17 | */ 18 | override func dequeCell(cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 19 | guard let cell: PrimaryVerCell = collectionView.dequeueReusableCell(withReuseIdentifier: PrimaryVerCell.id, for: indexPath as IndexPath) as? PrimaryVerCell else { fatalError("err") } 20 | if let thumbURL: String = primaryCellData?.thumbURLS[indexPath.row] { 21 | _ = thumbURL 22 | // Swift.print("PrimaryHorCell.dequeCell() thumbURL: \(thumbURL)") 23 | //cell.thumbImage = UIImage(url:thumbURL) 24 | //cell.imgView.setImage(webPath: thumbURL) 25 | } 26 | return cell 27 | } 28 | /** 29 | * Alternate layout 30 | * - Fixme: ⚠️️ add more doc 31 | */ 32 | func createAlternateLayout() -> UICollectionViewFlowLayout { 33 | with(.init()) { 34 | let margin: CGFloat = SecondaryHorCell.margin 35 | $0.sectionInset = .init(top: margin, left: margin, bottom: margin, right: margin) 36 | $0.minimumInteritemSpacing = 0 37 | $0.minimumLineSpacing = margin // vertical spacing 38 | let itemSize: CGSize = { 39 | let screenWidth: CGFloat = UIScreen.main.bounds.size.width 40 | let width: CGFloat = screenWidth - (margin * 2) 41 | let height: CGFloat = width 42 | return .init(width: width, height: height) 43 | }() 44 | $0.itemSize = itemSize//CGSize(width: 70, height: 70) 45 | } 46 | } 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/PrimaryHorCell/PrimaryHorCell+Getter.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * Getter 5 | */ 6 | extension PrimaryHorCell { 7 | override class var id: String { "\(PrimaryHorCell.self)" } // Used for dequeing cells 8 | var primaryCellData: PrimaryCellData? { data as? PrimaryCellData } 9 | override var count: Int { primaryCellData?.thumbURLS.count ?? 0 } 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/PrimaryHorCell/PrimaryHorCell+Interaction.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import Foundation 3 | /** 4 | * Interaction 5 | */ 6 | extension PrimaryHorCell { 7 | /** 8 | * - Description: Example how we can change the layout of the collectionview on interaction 9 | */ 10 | func itemSelect(indexPath: IndexPath) { 11 | // Swift.print("PrimaryCell.itemSelect() - indexPath: \(indexPath)") 12 | collectionView.collectionViewLayout.invalidateLayout() 13 | collectionView.collectionViewLayout = createAlternateLayout() 14 | updateCollectionView() 15 | } 16 | } 17 | #endif 18 | 19 | // let itemSize:CGSize = { 20 | // let screenWidth:CGFloat = UIScreen.main.bounds.size.width 21 | // let width:CGFloat = screenWidth - (SecondaryCell.margin*2) 22 | // let height:CGFloat = width 23 | // return .init(width: width, height: height) 24 | // }() 25 | // collectionView.visibleCells.forEach{$0.frame = .init(origin:$0.frame.origin,size:itemSize)} 26 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/PrimaryHorCell/PrimaryHorCell.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | /** 5 | * - Fixme: ⚠️️ Rename to PrimaryHorCell 6 | */ 7 | final class PrimaryHorCell: HorCell { 8 | override init(frame: CGRect) { 9 | super.init(frame: frame) 10 | self.onItemSelect = itemSelect 11 | } 12 | /** 13 | * When you set the data, the diferent UI's will be updated 14 | */ 15 | override var data: CellDataKind? { 16 | didSet { 17 | guard let data = data else { fatalError("data not avaiable") } 18 | _ = data 19 | updateCollectionView()/* Updates the collection view with data */ 20 | } 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/SecondaryHorCell/SecondaryHorCell+Const.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Const 5 | */ 6 | extension SecondaryHorCell { 7 | @objc override class var id: String { "\(SecondaryHorCell.self)" } 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/SecondaryHorCell/SecondaryHorCell+Create.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | import With 4 | /** 5 | * Create 6 | */ 7 | extension SecondaryHorCell { 8 | /** 9 | * Adds portrait cell sized design 10 | * - Note: this can also be done after the fact via: self.collectionView.setCollectionViewLayout(customLayout, animated: false), but this isnt as optimized 11 | */ 12 | override func createLayout() -> UICollectionViewFlowLayout { 13 | with(.init()) { 14 | let margin: CGFloat = SecondaryHorCell.margin 15 | $0.sectionInset = .init(top: margin, left: margin, bottom: margin, right: margin) 16 | $0.minimumInteritemSpacing = 0 17 | $0.minimumLineSpacing = margin // vertical spacing 18 | $0.itemSize = with(.zero) { 19 | let screenWidth: CGFloat = UIScreen.main.bounds.size.width 20 | $0.width = (screenWidth - (margin * 3)) / 2 21 | $0.height = $0.width + ($0.width * 0.33) 22 | } 23 | } 24 | } 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/SecondaryHorCell/SecondaryHorCell.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | 4 | final class SecondaryHorCell: HorCell { 5 | override init(frame: CGRect) { 6 | super.init(frame: frame) 7 | } 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /Sources/FlowLayout/cell/horizontal/custom/TertiaryHorCell/TertiaryHorCell.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | 4 | final class TertiaryHorCell: HorCell { 5 | override init(frame: CGRect) { 6 | super.init(frame: frame) 7 | } 8 | } 9 | /** 10 | * Const 11 | */ 12 | extension TertiaryHorCell { 13 | @objc override class var id: String { "\(TertiaryHorCell.self)" } 14 | } 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /Sources/FlowLayout/common/CGShapeUtil.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * - Fixme: ⚠️️ remove this, use GeomKit via spm instead or? 5 | */ 6 | internal class CGShapeUtil { 7 | /** 8 | * Draw line 9 | * - NOTE: remember to: shapeLayer.addSublayer(lineLayer) 10 | * It's also possible to do this with UIBezierPath 11 | * let path:UIBezierPath = { 12 | * let aPath = UIBezierPath.init()//cgPath: linePath 13 | * aPath.move(to: p1) 14 | * aPath.addLine(to: p2) 15 | * aPath.close()//Keep using the method addLineToPoint until you get to the one where about to close the path 16 | * return aPath 17 | * }() 18 | */ 19 | static func drawLine(lineLayer: CAShapeLayer, line: (p1: CGPoint, p2: CGPoint), style: (stroke: UIColor, strokeWidth: CGFloat)) -> CAShapeLayer { 20 | let lineLayer: CAShapeLayer = .init() 21 | let path: CGMutablePath = .init() 22 | path.move(to: line.p1) 23 | path.addLine(to: line.p2) 24 | lineLayer.path = path/*Setup the CAShapeLayer with the path, colors, and line width*/ 25 | lineLayer.strokeColor = style.stroke.cgColor 26 | lineLayer.lineWidth = style.strokeWidth 27 | lineLayer.lineCap = .butt 28 | return lineLayer 29 | } 30 | /** 31 | * Draws circle 32 | * - PARAM: progress: 0-1 33 | * - Fixme: ⚠️️ write method for nsbez... that use bezierPath.appendBezierPathWithArcWithCenter as init 34 | */ 35 | static func drawCircle(with circleLayer: CAShapeLayer, circle:(center: CGPoint, radius: CGFloat), style: (fill: UIColor, stroke: UIColor, strokeWidth: CGFloat), progress: CGFloat) -> CAShapeLayer { 36 | let origin: CGPoint = .init(x: circle.center.x - circle.radius, y: circle.center.y - circle.radius) 37 | let size: CGSize = .init(width: circle.radius * 2, height: circle.radius * 2) 38 | let rect: CGRect = .init(origin:origin, size: size) 39 | // let circlePath:BezierPath = .init(ovalIn: rect) 40 | let cgPath: CGPath = .init(ellipseIn: rect, transform: nil) 41 | //UIBezierPath(arcCenter: CGPoint(x: circle.center.x, y: circle.center.y), radius:circle.radius, startAngle: CGFloat(Double.pi * -0.5), endAngle: CGFloat(Double.pi * 1.5), clockwise: true)/*The path should be the entire circle, for the strokeEnd and strokeStart to work*/ 42 | // - Fixme: ⚠️️ use with 43 | circleLayer.path = cgPath//circlePath.cgPath/*Setup the CAShapeLayer with the path, colors, and line width*/ 44 | circleLayer.fillColor = style.fill.cgColor 45 | circleLayer.strokeColor = style.stroke.cgColor 46 | circleLayer.lineWidth = style.strokeWidth 47 | circleLayer.lineCap = .round 48 | circleLayer.strokeEnd = progress/*Sets progress of the stroke between predefined start and predefined end*/ 49 | return circleLayer 50 | } 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /Sources/FlowLayout/common/UIColorParser.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | 4 | internal class UIColorParser { 5 | /** 6 | * Returns random color 7 | * ## Examples: self.backgroundColor = UIColorParser.random 8 | */ 9 | static var random: UIColor { 10 | let r: CGFloat = .random(in: (0..<255.0)) / 255.0 11 | let g: CGFloat = .random(in: (0..<255.0)) / 255.0 12 | let b: CGFloat = .random(in: (0..<255.0)) / 255.0 13 | let uiColor: UIColor = .init(red: r, green: g, blue: b, alpha: 1) 14 | return uiColor 15 | } 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /Sources/FlowLayout/common/UIView+Extension.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * Controller related 5 | */ 6 | extension UIView { 7 | /** 8 | * Easily get Controller 9 | * - Fixme: ⚠️️ add more doc 10 | */ 11 | internal func controller() -> UIViewController? { 12 | if let nextViewControllerResponder = next as? UIViewController { 13 | return nextViewControllerResponder 14 | } else if let nextViewResponder = next as? UIView { 15 | return nextViewResponder.controller() 16 | } else { 17 | return nil 18 | } 19 | } 20 | /** 21 | * Easily get navController from 22 | * - Fixme: ⚠️️ add more doc 23 | */ 24 | internal func navigationController() -> UINavigationController? { 25 | guard let controller = controller() else { return nil } 26 | return controller.navigationController 27 | } 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /Sources/FlowLayout/common/image/UIImage+Extension.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * - Fixme: ⚠️️ Add a way to also get response, maybe look into result? 5 | * - Fixme: ⚠️️ remove this, use ImageKit via spm instead 6 | */ 7 | extension UIImage { 8 | internal typealias DownloadComplete = (UIImage?, IMGError?) -> Void 9 | internal enum IMGError: Error { 10 | case invalideWebPath 11 | case imageDataIsCorrupted 12 | case errorGettingDataFromURL 13 | } 14 | /** 15 | * Get UIImage from webPath 16 | */ 17 | internal static func image(webPath: String, onComplete:@escaping DownloadComplete) { 18 | guard let url = URL(string: webPath) else { onComplete(nil, .invalideWebPath); return } 19 | Utils.downloadImage(url: url, downloadComplete: onComplete) 20 | } 21 | } 22 | /** 23 | * Helpers 24 | */ 25 | private class Utils { 26 | /** 27 | * Assign and convert data to Image 28 | */ 29 | static func downloadImage(url: URL, downloadComplete:@escaping UIImage.DownloadComplete) { 30 | getDataFromUrl(url: url) { data, response, error in 31 | guard let data = data, error == nil else { downloadComplete(nil, .errorGettingDataFromURL); return } 32 | Swift.print(response?.suggestedFilename ?? url.lastPathComponent) 33 | guard let image = UIImage(data: data) else { downloadComplete(nil, .imageDataIsCorrupted); return } 34 | downloadComplete(image, nil) 35 | } 36 | } 37 | typealias URLQuery = (Data?, URLResponse?, Error?) -> Void 38 | /** 39 | * Get data from URL 40 | */ 41 | private static func getDataFromUrl(url: URL, completion: @escaping URLQuery) { 42 | URLSession.shared.dataTask(with: url) { data, response, error in 43 | completion(data, response, error) 44 | }.resume() 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/FlowLayout/common/image/UIImageView+Extension.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | /** 4 | * - Fixme: ⚠️️ remove this, use ImageKit via spm instead 5 | */ 6 | extension UIImageView { 7 | typealias OnError = (UIImage.IMGError?) -> Void 8 | static var defaultErrorHandler: OnError = { err in 9 | Swift.print("img error: \(String(describing: err))") 10 | } 11 | /** 12 | * Simplifies loading an image from a weburl to uiimageview 13 | */ 14 | internal convenience init(webPath: String, useBGThread: Bool = true, onError:@escaping OnError = defaultErrorHandler) { 15 | self.init() 16 | setImage(webPath: webPath, useBGThread: useBGThread) 17 | } 18 | /** 19 | * Create UIIMageView from filePath 20 | */ 21 | internal convenience init(filePath: String) { // should throw? 22 | let img = UIImage(contentsOfFile: filePath) 23 | self.init(image: img) 24 | } 25 | /** 26 | * Sets image async 27 | */ 28 | internal func setImage(webPath: String, useBGThread: Bool = true, onError:@escaping OnError = defaultErrorHandler) { 29 | let thread: DispatchQueue = (useBGThread ? .global(qos: .background) : .main) // Create bg or main thread 30 | thread.async { 31 | UIImage.image(webPath: webPath) { (image: UIImage?, error: UIImage.IMGError?) in 32 | if let img = image { 33 | DispatchQueue.main.async { // UI stuff must happen on the main thread 34 | self.image = img // set the image 35 | } 36 | } else { 37 | onError(error) // call the error handler 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /Tests/FlowLayoutTests/FlowLayoutTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import FlowLayout 3 | 4 | final class FlowLayoutTests: XCTestCase { 5 | func testExample() { 6 | _ = {}() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/CustomView.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import QuartzCore 3 | /** 4 | * - Note: So this class represents the view you can replicate and add your own logic too 5 | */ 6 | class CustomView: HorView { 7 | override init(frame: CGRect = .zero) { 8 | super.init(frame: frame) 9 | } 10 | } 11 | --------------------------------------------------------------------------------