├── .gitignore ├── .slather.yml ├── .travis.yml ├── CHANGELOG.md ├── Classes ├── GRXJSONCleaner.h ├── GRXJSONCleaner.m ├── GRXLayout+GRXLayoutInflater.h ├── GRXLayout+GRXLayoutInflater.m ├── GRXLayout.h ├── GRXLayout.m ├── GRXLayoutInflater.h ├── GRXLayoutInflater.m ├── GRXLayoutParams.h ├── GRXLayoutParams.m ├── GRXLinearLayout+GRXLayoutInflater.h ├── GRXLinearLayout+GRXLayoutInflater.m ├── GRXLinearLayout.h ├── GRXLinearLayout.m ├── GRXLinearLayoutParams.h ├── GRXLinearLayoutParams.m ├── GRXMeasureSpec.h ├── GRXRelativeLayout+GRXLayoutInflater.h ├── GRXRelativeLayout+GRXLayoutInflater.m ├── GRXRelativeLayout.h ├── GRXRelativeLayout.m ├── GRXRelativeLayoutParams.h ├── GRXRelativeLayoutParams.m ├── GRXTextView.h ├── GRXTextView.m ├── GranadaLayout.h ├── Private │ ├── GRXDependencyGraph.h │ ├── GRXDependencyGraph.m │ ├── GRXLayout+Protected.h │ ├── GRXLayout+Protected.m │ └── GRXLayoutParams_Protected.h ├── UIKitCategories │ ├── UIColor+GRXHexParse.h │ ├── UIColor+GRXHexParse.m │ ├── UIDevice+Util.h │ ├── UIDevice+Util.m │ ├── UIImageView+GRXLayout.h │ ├── UIImageView+GRXLayout.m │ ├── UISwitch+GRXLayout.h │ ├── UISwitch+GRXLayout.m │ ├── UIView+Frame.h │ ├── UIView+Frame.m │ ├── UIView+GRXLayout.h │ └── UIView+GRXLayout.m ├── UIView+GRXLayoutInflater.h └── UIView+GRXLayoutInflater.m ├── Example ├── Default-568h@2x.png ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── Example.xccheckout │ │ └── xcuserdata │ │ │ └── jose.alcala.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Example.xcscheme │ └── xcuserdata │ │ └── jose.alcala.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── Example │ ├── Assets │ │ ├── green@2x.png │ │ ├── lab@2x.png │ │ └── power@2x.png │ ├── GRXAppDelegate.h │ ├── GRXAppDelegate.m │ ├── GRXBaseInflationTestController.h │ ├── GRXBaseInflationTestController.m │ ├── GRXCollectionViewTestController.h │ ├── GRXCollectionViewTestController.m │ ├── GRXComplexBodyView.h │ ├── GRXComplexBodyView.m │ ├── GRXComplexCellCollectionViewTestController.h │ ├── GRXComplexCellCollectionViewTestController.m │ ├── GRXComplexCollectionViewCell.h │ ├── GRXComplexCollectionViewCell.m │ ├── GRXComplexData.h │ ├── GRXComplexData.m │ ├── GRXComplexHeaderView.h │ ├── GRXComplexHeaderView.m │ ├── GRXLabelTestViewController.h │ ├── GRXLabelTestViewController.m │ ├── GRXLayoutInflaterTestController1.h │ ├── GRXLayoutInflaterTestController1.m │ ├── GRXLinearLayoutInflaterAnimationController.h │ ├── GRXLinearLayoutInflaterAnimationController.m │ ├── GRXLinearLayoutVerticalTestController.h │ ├── GRXLinearLayoutVerticalTestController.m │ ├── GRXLoadMoreCell.h │ ├── GRXLoadMoreCell.m │ ├── GRXRelativeLayoutInflaterAnimationController.h │ ├── GRXRelativeLayoutInflaterAnimationController.m │ ├── GRXRelativeLayoutTestController.h │ ├── GRXRelativeLayoutTestController.m │ ├── GRXSelectionViewController.h │ ├── GRXSelectionViewController.m │ ├── GRXTestAppDelegate.h │ ├── GRXTestAppDelegate.m │ ├── GRXTestViewController.h │ ├── GRXTextGenerator.h │ ├── GRXTextGenerator.m │ ├── GRXTextViewTestViewController.h │ ├── GRXTextViewTestViewController.m │ ├── GRXTopLayoutTestViewController.h │ ├── GRXTopLayoutTestViewController.m │ ├── GRXWeightedLinearLayoutTestController.h │ ├── GRXWeightedLinearLayoutTestController.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── Default-568h@2x-1.png │ │ │ └── Default-568h@2x.png │ ├── Info.plist │ ├── Layouts │ │ ├── cell_test.grx │ │ ├── complex_cell.grx │ │ ├── complex_cell_body.grx │ │ ├── complex_cell_header.grx │ │ ├── linear_test_1.grx │ │ ├── relative_test_1.grx │ │ └── relative_test_2.grx │ └── main.m ├── Podfile ├── Podfile.lock └── Tests │ ├── Fixtures │ ├── broken.grx │ ├── empty.grx │ ├── inflate_contained_1.grx │ ├── inflate_contained_2.grx │ ├── inflate_container.grx │ ├── invalid_root.grx │ ├── lab.png │ ├── linear_horizontal.grx │ ├── linear_horizontal_in_vertical.grx │ ├── linear_horizontal_weight.grx │ ├── linear_horizontal_weight_label.grx │ ├── linear_in_relative.grx │ ├── linear_root.grx │ ├── linear_vertical.grx │ ├── linear_vertical_weight.grx │ ├── no_layout_root.grx │ ├── relative_in_relative.grx │ ├── relative_other_views.grx │ ├── relative_parent.grx │ ├── relative_root.grx │ ├── relative_to_gone.grx │ ├── relative_wrapped.grx │ ├── relative_wrapped_around_center.grx │ └── relative_wrapped_push.grx │ ├── GRXCombinationSnapshotTests.m │ ├── GRXLayoutInflaterTests.m │ ├── GRXLayoutParamsUnitTests.m │ ├── GRXLinearLayoutUnitTests.m │ ├── GRXLinearSnapshotTests.m │ ├── GRXRelativeLayoutUnitTests.m │ ├── GRXRelativeSnapshotTests.m │ ├── GRXTestHelper.h │ ├── GRXTestHelper.m │ ├── GRXUIViewCategoriesTests.m │ ├── GRXUIViewFrameUnitTests.m │ ├── Info.plist │ ├── ReferenceImages │ ├── GRXCombinationSnapshotTests │ │ └── testRecursiveInflation@2x.png │ ├── GRXLinearSnapshotTests │ │ ├── testHorizontal@2x.png │ │ ├── testHorizontalInVertical@2x.png │ │ ├── testHorizontalWeighted@2x.png │ │ ├── testHorizontalWeightedWithLabel@2x.png │ │ ├── testVertical@2x.png │ │ └── testVerticalWeighted@2x.png │ └── GRXRelativeLayoutSnapshotTests │ │ ├── testAlignParent@2x.png │ │ ├── testAlignRelative@2x.png │ │ ├── testAlignWrapped@2x.png │ │ ├── testAlignWrappedAroundCenter@2x.png │ │ ├── testAlignWrappedPush@2x.png │ │ ├── testRelativeInRelative@2x.png │ │ └── testRelativeToGone@2x.png │ └── UIColor+GRXHexParseTests.m ├── Gemfile ├── Gemfile.lock ├── GranadaLayout.podspec ├── GranadaLayout.xcworkspace └── contents.xcworkspacedata ├── LICENSE ├── README.md └── Rakefile /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.gcda 22 | *.gcno 23 | *.xcscmblueprint 24 | 25 | Pods/ 26 | 27 | .idea/workspace.xml 28 | .idea/tasks.xml 29 | 30 | .idea/dataSources.ids 31 | .idea/dataSources.xml 32 | .idea/sqlDataSources.xml 33 | .idea/dynamic.xml 34 | *.ipr 35 | *.iws 36 | *.iml 37 | 38 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: coveralls 2 | xcodeproj: Example/Example.xcodeproj 3 | source_directory: Classes 4 | build_directory: build 5 | ignore: 6 | - Example/* 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | before_install: bundle; rake bootstrap 3 | script: rake spec 4 | after_success: bundle exec slather coverage --verbose Example/Example.xcodeproj 5 | osx_image: xcode7.2 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.4.0 Improved layout inflater, bugs fixed 2 | - Fix layout error on weighted linear layouts 3 | - Layout inflater now supports comments in layout files (thanks to https://github.com/blach/NSJSONSerialization-Comments) 4 | 5 | # 0.3.1 Common header file 6 | - Add ``GranadaLayout.h`` to ease import of files 7 | - Hide private headers 8 | 9 | # 0.3.0: Various improvements 10 | - Property ``grx_debugIdentifier`` renamed to ``grx_identifier`` 11 | - Added methods ``-grx_subviewForIdentifier:`` and ``grx_findViewWithIdentifier:`` 12 | - Remove method ``-viewForIdentifier`` from the ``GRXLayoutInflater``, now the previous methods have to be used 13 | - Improved stability of snapshot tests 14 | - Update dependencies 15 | - Improve compatibility with Swift 16 | 17 | # 0.2.4: Fix layout when adding subviews, improve performance 18 | 19 | # 0.2.3: Fix recursive layout inflation 20 | - Some values were being overriden when loading an inflated layout from within another one 21 | - Update snapshot tests according to the fixed inflation 22 | 23 | # 0.2.2 and 0.2.1: Fix recursive inflation 24 | - Using now ``bundleName`` and ``bundleIdentifier`` to refer to other bundles (like the ones coming from Pods) 25 | 26 | # 0.2.0: Improved layout inflater 27 | - Fixed size calculation for root layouts 28 | - Layout inflater can include external files 29 | ```json 30 | { 31 | "id" : "included1", 32 | "inflate" : "inflate_contained_1.grx", 33 | 34 | "width" : "100", 35 | "height" : "100", 36 | 37 | "debug_bgColor" : "green", 38 | "marginBottom" : "10" 39 | }, 40 | 41 | { 42 | "id" : "included2", 43 | "inflate" : { 44 | "filename" : "inflate_contained_2.grx", 45 | "bundleIdentifier" : "org.gskbyte.GranadaLayout.Tests" 46 | }, 47 | 48 | "width" : "200", 49 | "height" : "200", 50 | 51 | "gravity" : "end", 52 | "debug_bgColor" : "blue" 53 | } 54 | ``` 55 | - Layout inflater can inflate existing views 56 | 57 | ```objc 58 | - (instancetype)initWithData:(NSData *)data 59 | rootView:(UIView *)rootView; 60 | - (instancetype)initWithBundleFile:(NSString *)filename 61 | rootView:(UIView *)rootView; 62 | - (instancetype)initWithFile:(NSString *)filename 63 | fromBundle:(NSBundle *)bundle 64 | rootView:(UIView *)rootView; 65 | ``` 66 | - Improved examples 67 | - Measurement block in ``UIView+GRXLayout`` allow overriding default ``-grx_measureWithWidthSpec:heightSpec:`` 68 | - Added more tests 69 | 70 | # 0.1.1: minor bugfixes 71 | 72 | # 0.1.0: Initial release 73 | - Added categories to ``UIView`` to implement measuring methods needed by the layout system 74 | - Added ``GRXRelativeLayout``, that allows positioning views relative to each other and also relative to the superview. 75 | - Added ``GRXLinearLayout``, that allows stacking views either vertically or horizontally, also defining weights (for proportional sizes). 76 | - Added initial version of the ``GRXLinearLayoutInflater``, that allows loading layout definition from JSON files. 77 | - Added lots of tests and some examples 78 | -------------------------------------------------------------------------------- /Classes/GRXJSONCleaner.h: -------------------------------------------------------------------------------- 1 | // kudos to https://github.com/gskbyte/NSJSONSerialization-Comments 2 | 3 | #import 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @interface GRXJSONCleaner : NSObject 8 | 9 | + (NSData *)cleanJSONDataWithData:(NSData *)data; 10 | 11 | @end 12 | 13 | NS_ASSUME_NONNULL_END 14 | -------------------------------------------------------------------------------- /Classes/GRXJSONCleaner.m: -------------------------------------------------------------------------------- 1 | #import "GRXJSONCleaner.h" 2 | 3 | @implementation GRXJSONCleaner 4 | 5 | static const int EncLen_UTF8[256] = { 6 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 13 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1 14 | }; 15 | 16 | static inline void copyUTF8CharacterAndAdvancePointers(UTF8Char **source, UTF8Char **target) { 17 | UTF8Char character = **source; 18 | if (__builtin_expect(character < 128, 1)) { 19 | // one byte UTF-8 character 20 | **target = **source; 21 | *source += 1; 22 | *target += 1; 23 | } else { 24 | int len = EncLen_UTF8[character]; 25 | memcpy(*target, *source, len); 26 | *source += len; 27 | *target += len; 28 | } 29 | } 30 | 31 | static inline void skipUTF8Character(UTF8Char **source) { 32 | *source += EncLen_UTF8[**source]; 33 | } 34 | 35 | + (NSData *)cleanJSONDataWithData:(NSData *)data { 36 | UTF8Char *originalString = (UTF8Char *)[data bytes]; 37 | NSUInteger length = [data length]; 38 | 39 | UTF8Char *modifiedString = malloc(sizeof(UTF8Char) * length); 40 | 41 | UTF8Char *originalStringCurrent = originalString; 42 | UTF8Char *originalStringEnd = originalString + length; 43 | UTF8Char *modifiedStringCurrent = modifiedString; 44 | 45 | while (originalStringCurrent < originalStringEnd) { 46 | UTF8Char currentChar = *originalStringCurrent; 47 | 48 | if (currentChar == '\t' || currentChar == ' ' || currentChar == '\r' 49 | || currentChar == '\n') { 50 | // skip whitespace 51 | 52 | // Ignore whitespace tokens. According to ES 5.1 section 15.12.1.1, 53 | // whitespace tokens include tabs, carriage returns, line feeds, and 54 | // space characters. 55 | originalStringCurrent++; 56 | } else if (currentChar == '"') { 57 | // we found a string! -> handle it 58 | *modifiedStringCurrent++ = currentChar; 59 | originalStringCurrent++; 60 | 61 | UTF8Char lastChar = 0; 62 | 63 | while (originalStringCurrent < originalStringEnd) { 64 | currentChar = *originalStringCurrent; 65 | 66 | if (currentChar == '"') { 67 | *modifiedStringCurrent++ = currentChar; 68 | originalStringCurrent++; 69 | 70 | if (lastChar == '\\') { 71 | // was escaped character -> not at string end 72 | } else { 73 | // arrived at end of string 74 | break; 75 | } 76 | } else if (currentChar == '\n' || currentChar == '\r') { 77 | // line breaks should not happen in JSON strings! 78 | *modifiedStringCurrent++ = currentChar; 79 | originalStringCurrent++; 80 | break; 81 | } else { 82 | // still in string -> copy character 83 | copyUTF8CharacterAndAdvancePointers(&originalStringCurrent, 84 | &modifiedStringCurrent); 85 | } 86 | lastChar = currentChar; 87 | } 88 | } else if (currentChar == '/' && originalStringCurrent + 1 < originalStringEnd) { 89 | // maybe we have a single-line or multi-line comment 90 | UTF8Char nextChar = *(originalStringCurrent + 1); 91 | 92 | if (nextChar == '/') { 93 | // single line comment 94 | originalStringCurrent += 2; 95 | 96 | while (originalStringCurrent < originalStringEnd) { 97 | char currentChar = *originalStringCurrent; 98 | 99 | if (currentChar == '\r' || currentChar == '\n') { 100 | // at end of line -> comment end 101 | break; 102 | } else { 103 | // skip 104 | skipUTF8Character(&originalStringCurrent); 105 | } 106 | } 107 | } else if (nextChar == '*') { 108 | // multi line comment 109 | originalStringCurrent += 2; 110 | 111 | while (originalStringCurrent < originalStringEnd) { 112 | char currentChar = *originalStringCurrent; 113 | 114 | if (currentChar == '*') { 115 | originalStringCurrent++; 116 | 117 | if (originalStringCurrent < originalStringEnd) { 118 | currentChar = *originalStringCurrent; 119 | if (currentChar == '/') { 120 | // comment end! 121 | originalStringCurrent++; 122 | break; 123 | } 124 | } 125 | } else { 126 | // skip 127 | skipUTF8Character(&originalStringCurrent); 128 | } 129 | } 130 | } else { 131 | // nope, no comment, just copy the character 132 | *modifiedStringCurrent++ = currentChar; 133 | originalStringCurrent++; 134 | } 135 | } else { 136 | // copy character as is 137 | copyUTF8CharacterAndAdvancePointers(&originalStringCurrent, &modifiedStringCurrent); 138 | } 139 | } 140 | 141 | NSUInteger modifiedStringLength = modifiedStringCurrent - modifiedString; 142 | 143 | if (modifiedStringLength != length) { 144 | modifiedString = realloc(modifiedString, sizeof(UTF8Char) * modifiedStringLength); 145 | return [NSData dataWithBytesNoCopy:modifiedString 146 | length:modifiedStringLength 147 | freeWhenDone:YES]; 148 | } else { 149 | free(modifiedString); 150 | return data; 151 | } 152 | } 153 | 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /Classes/GRXLayout+GRXLayoutInflater.h: -------------------------------------------------------------------------------- 1 | #import "GRXLayout.h" 2 | #import "UIView+GRXLayoutInflater.h" 3 | #import "GRXLayoutInflater.h" 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @interface GRXLayout (GRXLayoutInflater) 8 | 9 | - (void)configureSubviewLayoutParams:(GRXLayoutParams *)params 10 | fromDictionary:(NSDictionary *)dictionary 11 | inInflater:(GRXLayoutInflater *)inflater; 12 | + (void)configureUnparentedLayoutParams:(GRXLayoutParams *)params 13 | fromDictionary:(NSDictionary *)dictionary; 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Classes/GRXLayout+GRXLayoutInflater.m: -------------------------------------------------------------------------------- 1 | #import "GRXLayout+GRXLayoutInflater.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | static inline CGFloat GRXLayoutSizeFromString(NSString *sizeStr) { 6 | if (sizeStr == nil || [sizeStr isEqualToString:@"wrap_content"]) { 7 | return GRXWrapContent; 8 | } else if ([sizeStr isEqualToString:@"match_parent"]) { 9 | return GRXMatchParent; 10 | } else { 11 | return sizeStr.floatValue; 12 | } 13 | } 14 | 15 | @implementation GRXLayout (GRXLayoutInflater) 16 | 17 | - (void)grx_configureFromDictionary:(NSDictionary *)dictionary { 18 | [super grx_configureFromDictionary:dictionary]; 19 | 20 | BOOL paddingDefined = NO; 21 | UIEdgeInsets padding = UIEdgeInsetsZero; 22 | CGFloat p = [dictionary[@"padding"] floatValue]; 23 | if (p != 0) { 24 | padding = UIEdgeInsetsMake(p, p, p, p); 25 | paddingDefined = YES; 26 | } 27 | 28 | p = [dictionary[@"paddingLeft"] floatValue]; 29 | if (p != 0) { 30 | padding.left = p; 31 | paddingDefined = YES; 32 | } 33 | p = [dictionary[@"paddingRight"] floatValue]; 34 | if (p != 0) { 35 | padding.right = p; 36 | paddingDefined = YES; 37 | } 38 | p = [dictionary[@"paddingTop"] floatValue]; 39 | if (p != 0) { 40 | padding.top = p; 41 | paddingDefined = YES; 42 | } 43 | p = [dictionary[@"paddingBottom"] floatValue]; 44 | if (p != 0) { 45 | padding.bottom = p; 46 | paddingDefined = YES; 47 | } 48 | 49 | if (paddingDefined) { 50 | self.padding = padding; 51 | } 52 | } 53 | 54 | - (void)configureSubviewLayoutParams:(GRXLayoutParams *)params 55 | fromDictionary:(NSDictionary *)dictionary 56 | inInflater:(GRXLayoutInflater *)inflater { 57 | [GRXLayout configureUnparentedLayoutParams:params 58 | fromDictionary:dictionary]; 59 | } 60 | 61 | + (void)configureUnparentedLayoutParams:(GRXLayoutParams *)params 62 | fromDictionary:(NSDictionary *)dictionary { 63 | if (dictionary[@"width"] != nil) { 64 | params.width = GRXLayoutSizeFromString(dictionary[@"width"]); 65 | } 66 | if (dictionary[@"height"] != nil) { 67 | params.height = GRXLayoutSizeFromString(dictionary[@"height"]); 68 | } 69 | 70 | BOOL definesMargins = NO; 71 | UIEdgeInsets margins = UIEdgeInsetsZero; 72 | 73 | CGFloat m = [dictionary[@"margin"] floatValue]; 74 | if (m != 0) { 75 | margins = UIEdgeInsetsMake(m, m, m, m); 76 | definesMargins = YES; 77 | } 78 | 79 | // all margins can be overriden 80 | m = [dictionary[@"marginLeft"] floatValue]; 81 | if (m != 0) { 82 | margins.left = m; 83 | definesMargins = YES; 84 | } 85 | 86 | m = [dictionary[@"marginRight"] floatValue]; 87 | if (m != 0) { 88 | margins.right = m; 89 | definesMargins = YES; 90 | } 91 | 92 | m = [dictionary[@"marginTop"] floatValue]; 93 | if (m != 0) { 94 | margins.top = m; 95 | definesMargins = YES; 96 | } 97 | 98 | m = [dictionary[@"marginBottom"] floatValue]; 99 | if (m != 0) { 100 | margins.bottom = m; 101 | definesMargins = YES; 102 | } 103 | 104 | if (definesMargins) { 105 | params.margins = margins; 106 | } 107 | } 108 | 109 | @end 110 | 111 | NS_ASSUME_NONNULL_END 112 | -------------------------------------------------------------------------------- /Classes/GRXLayout.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GRXLayoutParams.h" 3 | #import "UIView+GRXLayout.h" 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @interface GRXLayout : UIView 8 | 9 | @property (nonatomic) UIEdgeInsets padding; 10 | 11 | // This property specifies if this layout must take into account the parent's size 12 | // when it is going to be measured ONLY IF the parent is not an instance of GRXLayout 13 | // By default, views will grow to the bottom but not horizontally 14 | @property (nonatomic) BOOL limitToNonLayoutParentWidth; // YES by default 15 | @property (nonatomic) BOOL limitToNonLayoutParentHeight; // YES by default 16 | 17 | + (Class)layoutParamsClass; 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /Classes/GRXLayout.m: -------------------------------------------------------------------------------- 1 | #import "GRXLayout.h" 2 | #import "GRXLayoutParams.h" 3 | #import 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @implementation GRXLayout 8 | 9 | #pragma mark - static class methods 10 | 11 | + (Class)layoutParamsClass { 12 | return GRXLayoutParams.class; 13 | } 14 | 15 | #pragma mark - setup methods 16 | 17 | - (instancetype)initWithFrame:(CGRect)frame { 18 | self = [super initWithFrame:frame]; 19 | if (self) { 20 | _limitToNonLayoutParentWidth = YES; 21 | _limitToNonLayoutParentHeight = YES; 22 | } 23 | return self; 24 | } 25 | 26 | - (void)addSubview:(UIView *)view { 27 | if (view.grx_layoutParams != nil) { 28 | NSAssert([view.grx_layoutParams isKindOfClass:self.class.layoutParamsClass], 29 | @"Layout class %@ needs layoutParams to be instances of %@", self.class, self.class.layoutParamsClass); 30 | } else { 31 | view.grx_layoutParams = [[self.class.layoutParamsClass alloc] init]; 32 | } 33 | [super addSubview:view]; 34 | [view grx_setNeedsLayoutInParent]; 35 | } 36 | 37 | - (void)setNeedsLayout { 38 | [super setNeedsLayout]; 39 | [self grx_invalidateMeasuredSize]; 40 | } 41 | 42 | // This is how the whole layout system works 43 | // By default, layout views don't do anything when -layoutSubviews is called 44 | // Except if they are inside a non-GRXLayout view, then they take the initiative and measure their subviews 45 | // And set their frame size. Position is left equal, must be changed manually outside. 46 | 47 | - (void)layoutSubviews { 48 | if (NO == [self.superview isKindOfClass:GRXLayout.class]) { 49 | GRXMeasureSpec wspec, hspec; 50 | GRXLayoutParams *ownParams = self.grx_layoutParams; 51 | 52 | // could be CGFLOAT_MAX, but wrong layouts may cause problems 53 | // (for example, a relativeLayout with a view alignParentBottom and alignParentRight 54 | CGSize maxSize = CGSizeMake(100000, 100000); 55 | CGSize screenSize = UIScreen.mainScreen.bounds.size; 56 | 57 | // Take parent size to see how big I can be, if I'm root, take the whole display 58 | // If a dimension is set to match parent, limit to the display size 59 | if (self.limitToNonLayoutParentWidth && self.superview.width > 0) { 60 | maxSize.width = self.superview.width; 61 | } else if (ownParams.width == GRXMatchParent) { 62 | maxSize.width = screenSize.width; 63 | } 64 | 65 | if (self.limitToNonLayoutParentHeight && self.superview.height > 0) { 66 | maxSize.height = self.superview.height; 67 | } else if (ownParams.height == GRXMatchParent) { 68 | maxSize.height = screenSize.height; 69 | } 70 | 71 | 72 | if (ownParams.width == 0 || ownParams.width == GRXWrapContent) { 73 | wspec = GRXMeasureSpecMake(maxSize.width, GRXMeasureSpecAtMost); 74 | } else if (ownParams.width == GRXMatchParent) { 75 | wspec = GRXMeasureSpecMake(maxSize.width, GRXMeasureSpecExactly); 76 | } else { // exact size 77 | wspec = GRXMeasureSpecMake(ownParams.width, GRXMeasureSpecExactly); 78 | } 79 | 80 | if (ownParams.height == 0 || ownParams.height == GRXWrapContent) { 81 | hspec = GRXMeasureSpecMake(maxSize.height, GRXMeasureSpecAtMost); 82 | } else if (ownParams.height == GRXMatchParent) { 83 | hspec = GRXMeasureSpecMake(maxSize.height, GRXMeasureSpecExactly); 84 | } else { // exact size 85 | hspec = GRXMeasureSpecMake(ownParams.height, GRXMeasureSpecExactly); 86 | } 87 | 88 | self.size = [self grx_measuredSizeForWidthSpec:wspec 89 | heightSpec:hspec]; 90 | } 91 | 92 | [super layoutSubviews]; 93 | } 94 | 95 | @end 96 | 97 | NS_ASSUME_NONNULL_END 98 | -------------------------------------------------------------------------------- /Classes/GRXLayoutInflater.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface GRXLayoutInflater : NSObject 6 | 7 | @property (nullable, nonatomic, readonly) NSError *parseError; 8 | @property (nullable, nonatomic, readonly) UIView *rootView; 9 | 10 | + (BOOL)areDebugOptionsEnabled; 11 | + (void)setDebugOptionsEnabled:(BOOL)active; 12 | 13 | - (instancetype)initWithData:(NSData *)data 14 | rootView:(nullable __kindof UIView *)rootView; 15 | - (instancetype)initWithMainBundleFileNamed:(NSString *)filename 16 | rootView:(nullable __kindof UIView *)rootView; 17 | - (instancetype)initWithFile:(NSString *)filename 18 | fromBundle:(NSBundle *)bundle 19 | rootView:(nullable __kindof UIView *)rootView; 20 | 21 | // same result as calling previous initializers with rootView=nil 22 | - (instancetype)initWithData:(NSData *)data; 23 | - (instancetype)initWithMainBundleFileNamed:(NSString *)filename; 24 | - (instancetype)initWithFile:(NSString *)filename 25 | fromBundle:(NSBundle *)bundle; 26 | 27 | - (nullable __kindof UIView *)viewWithIdentifier:(NSString *)identifier; 28 | 29 | @end 30 | 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /Classes/GRXLayoutParams.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | static const CGFloat GRXMatchParent = -1; 6 | static const CGFloat GRXWrapContent = -2; 7 | 8 | #define kGRXLayoutParamsDefaultSize CGSizeMake(GRXWrapContent, GRXWrapContent) 9 | 10 | @interface GRXLayoutParams : NSObject 11 | 12 | @property (nonatomic, weak, readonly) UIView *view; 13 | 14 | @property (nonatomic) CGSize size; 15 | @property (nonatomic) CGFloat width; 16 | @property (nonatomic) CGFloat height; 17 | 18 | @property (nonatomic) UIEdgeInsets margins; 19 | @property (nonatomic, readonly) BOOL hasMargins; 20 | 21 | - (instancetype)init; 22 | - (instancetype)initWithSize:(CGSize)size; 23 | - (instancetype)initWithLayoutParams:(GRXLayoutParams *)layoutParams; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /Classes/GRXLayoutParams.m: -------------------------------------------------------------------------------- 1 | #import "GRXLayoutParams.h" 2 | #import "GRXLayoutParams_Protected.h" 3 | #import "UIView+GRXLayout.h" 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @implementation GRXLayoutParams 8 | 9 | - (instancetype)init { 10 | return [self initWithSize:kGRXLayoutParamsDefaultSize]; 11 | } 12 | 13 | - (instancetype)initWithSize:(CGSize)size { 14 | self = [super init]; 15 | if (self) { 16 | _size = size; 17 | } 18 | return self; 19 | } 20 | 21 | - (instancetype)initWithLayoutParams:(GRXLayoutParams *)layoutParams { 22 | self = [self initWithSize:layoutParams.size]; 23 | if (self) { 24 | _margins = layoutParams.margins; 25 | } 26 | return self; 27 | } 28 | 29 | #pragma mark - Instance methods 30 | 31 | - (void)setSize:(CGSize)size { 32 | _size = size; 33 | [_view grx_setNeedsLayoutInParent]; 34 | } 35 | 36 | - (CGFloat)width { 37 | return _size.width; 38 | } 39 | 40 | - (void)setWidth:(CGFloat)width { 41 | _size.width = width; 42 | } 43 | 44 | - (CGFloat)height { 45 | return _size.height; 46 | } 47 | 48 | - (void)setHeight:(CGFloat)height { 49 | _size.height = height; 50 | } 51 | 52 | - (void)setMargins:(UIEdgeInsets)margins { 53 | _margins = margins; 54 | [_view grx_setNeedsLayoutInParent]; 55 | } 56 | 57 | - (BOOL)hasMargins { 58 | return _margins.top == _margins.left == _margins.bottom == _margins.right == 0; 59 | } 60 | 61 | #pragma mark - Copy method 62 | 63 | - (id)copyWithZone:(nullable NSZone *)zone { 64 | return [[self.class alloc] initWithLayoutParams:self]; 65 | } 66 | 67 | #pragma mark - Protected methods 68 | 69 | - (void)setView:(UIView *)view { 70 | _view = view; 71 | } 72 | 73 | - (NSString *)debugDescription { 74 | return [NSString stringWithFormat:@"" 75 | "%@[%.0f,%.0f]", 76 | NSStringFromClass(self.class), self.width, self.height 77 | ]; 78 | } 79 | 80 | @end 81 | 82 | NS_ASSUME_NONNULL_END 83 | -------------------------------------------------------------------------------- /Classes/GRXLinearLayout+GRXLayoutInflater.h: -------------------------------------------------------------------------------- 1 | #import "GRXLinearLayout.h" 2 | #import "GRXLayout+GRXLayoutInflater.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @interface GRXLinearLayout (GRXLayoutInflater) 7 | 8 | @end 9 | 10 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /Classes/GRXLinearLayout+GRXLayoutInflater.m: -------------------------------------------------------------------------------- 1 | #import "GRXLinearLayout+GRXLayoutInflater.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @implementation GRXLinearLayout (GRXLayoutInflater) 6 | 7 | - (void)grx_configureFromDictionary:(NSDictionary *)dictionary { 8 | [super grx_configureFromDictionary:dictionary]; 9 | 10 | NSString *directionStr = dictionary[@"direction"]; 11 | if ([directionStr isEqualToString:@"horizontal"]) { 12 | self.direction = GRXLinearLayoutDirectionHorizontal; 13 | } else if ([directionStr isEqualToString:@"vertical"]) { 14 | self.direction = GRXLinearLayoutDirectionVertical; 15 | } 16 | 17 | NSString *weightSumStr = dictionary[@"weightSum"]; 18 | if (weightSumStr) { 19 | self.weightSum = weightSumStr.floatValue; 20 | } 21 | } 22 | 23 | static NSDictionary *Gravities; 24 | 25 | + (void)initializeGravities { 26 | Gravities = @{ 27 | @"begin": @(GRXLinearLayoutGravityBegin), 28 | @"center": @(GRXLinearLayoutGravityCenter), 29 | @"end": @(GRXLinearLayoutGravityEnd), 30 | }; 31 | } 32 | 33 | - (void)configureSubviewLayoutParams:(GRXLayoutParams *)params 34 | fromDictionary:(NSDictionary *)dict 35 | inInflater:(GRXLayoutInflater *)inflater { 36 | [super configureSubviewLayoutParams:params 37 | fromDictionary:dict 38 | inInflater:inflater]; 39 | 40 | static dispatch_once_t onceToken; 41 | dispatch_once(&onceToken, ^{ 42 | [self.class initializeGravities]; 43 | }); 44 | 45 | GRXLinearLayoutParams *lp = (GRXLinearLayoutParams *)params; 46 | 47 | for (NSString *key in Gravities.allKeys) { 48 | NSString *value = dict[@"gravity"]; 49 | if ([value isEqualToString:key]) { 50 | lp.gravity = [Gravities[key] integerValue]; 51 | break; 52 | } 53 | } 54 | 55 | NSString *weightValue = dict[@"weight"]; 56 | if (weightValue != nil) { 57 | lp.weight = [weightValue floatValue]; 58 | } 59 | } 60 | 61 | @end 62 | 63 | NS_ASSUME_NONNULL_END 64 | -------------------------------------------------------------------------------- /Classes/GRXLinearLayout.h: -------------------------------------------------------------------------------- 1 | #import "GRXLayout.h" 2 | #import "GRXLinearLayoutParams.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | typedef NS_ENUM (NSUInteger, GRXLinearLayoutDirection) { 7 | GRXLinearLayoutDirectionHorizontal = 0, 8 | GRXLinearLayoutDirectionVertical 9 | }; 10 | 11 | const static GRXLinearLayoutDirection kGRXLinearLayoutDefaultDirection = GRXLinearLayoutDirectionVertical; 12 | const static CGFloat kGRXLinearLayoutDefaultWeightSum = 0; 13 | 14 | @interface GRXLinearLayout : GRXLayout 15 | 16 | @property (nonatomic) GRXLinearLayoutDirection direction; 17 | @property (nonatomic) CGFloat weightSum; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /Classes/GRXLinearLayoutParams.h: -------------------------------------------------------------------------------- 1 | #import "GRXLayoutParams.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | typedef NS_ENUM (NSUInteger, GRXLinearLayoutGravity) { 6 | GRXLinearLayoutGravityBegin = 0, 7 | GRXLinearLayoutGravityCenter, 8 | GRXLinearLayoutGravityEnd 9 | }; 10 | 11 | static const NSInteger kGRXLinearLayoutParamsDefaultGravity = 0; 12 | static const CGFloat kGRXLinearLayoutParamsDefaultWeight = 0; 13 | 14 | @interface GRXLinearLayoutParams : GRXLayoutParams 15 | 16 | @property (nonatomic) NSInteger gravity; 17 | @property (nonatomic) CGFloat weight; 18 | 19 | @end 20 | 21 | @interface UIView (GRXLinearLayoutParams) 22 | 23 | @property (nullable, nonatomic, readonly) GRXLinearLayoutParams *grx_linearLayoutParams; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /Classes/GRXLinearLayoutParams.m: -------------------------------------------------------------------------------- 1 | #import "GRXLinearLayoutParams.h" 2 | #import "UIView+GRXLayout.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @implementation GRXLinearLayoutParams 7 | 8 | - (instancetype)initWithSize:(CGSize)size { 9 | self = [super initWithSize:size]; 10 | if (self) { 11 | [self setupDefaultGravityAndWeight]; 12 | } 13 | return self; 14 | } 15 | 16 | - (instancetype)initWithLayoutParams:(GRXLayoutParams *)layoutParams { 17 | self = [super initWithLayoutParams:layoutParams]; 18 | if (self) { 19 | [self setupDefaultGravityAndWeight]; 20 | 21 | if ([layoutParams isKindOfClass:GRXLinearLayoutParams.class]) { 22 | GRXLinearLayoutParams *linParams = (GRXLinearLayoutParams *)layoutParams; 23 | _gravity = linParams.gravity; 24 | _weight = linParams.weight; 25 | } 26 | } 27 | return self; 28 | } 29 | 30 | - (void)setupDefaultGravityAndWeight { 31 | _gravity = kGRXLinearLayoutParamsDefaultGravity; 32 | _weight = kGRXLinearLayoutParamsDefaultWeight; 33 | } 34 | 35 | - (void)setGravity:(NSInteger)gravity { 36 | _gravity = gravity; 37 | [self.view grx_setNeedsLayoutInParent]; 38 | } 39 | 40 | - (void)setWeight:(CGFloat)weight { 41 | _weight = weight; 42 | [self.view grx_setNeedsLayoutInParent]; 43 | } 44 | 45 | @end 46 | 47 | 48 | @implementation UIView (GRXLinearLayoutParams) 49 | 50 | - (nullable GRXLinearLayoutParams *)grx_linearLayoutParams { 51 | GRXLayoutParams *params = self.grx_layoutParams; 52 | if ([params isKindOfClass:GRXLinearLayoutParams.class]) { 53 | return (GRXLinearLayoutParams *)params; 54 | } else { 55 | NSAssert(NO, @"Not GRXLinearLayoutParams for view %@", self); 56 | return nil; 57 | } 58 | } 59 | 60 | @end 61 | 62 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /Classes/GRXMeasureSpec.h: -------------------------------------------------------------------------------- 1 | typedef NS_ENUM (NSUInteger, GRXMeasureSpecMode) { 2 | GRXMeasureSpecUnspecified = 0, 3 | GRXMeasureSpecExactly, 4 | GRXMeasureSpecAtMost 5 | }; 6 | 7 | typedef struct GRXMeasureSpec { 8 | CGFloat value; 9 | GRXMeasureSpecMode mode; 10 | } GRXMeasureSpec; 11 | 12 | typedef struct GRXFullMeasureSpec { 13 | GRXMeasureSpec width; 14 | GRXMeasureSpec height; 15 | } GRXFullMeasureSpec; 16 | 17 | #pragma mark - Initializers 18 | 19 | CG_INLINE GRXFullMeasureSpec 20 | GRXFullMeasureSpecMake(GRXMeasureSpec wspec, GRXMeasureSpec hspec) { 21 | GRXFullMeasureSpec spec; 22 | spec.width = wspec; 23 | spec.height = hspec; 24 | return spec; 25 | } 26 | 27 | CG_INLINE GRXMeasureSpec 28 | GRXMeasureSpecMake(CGFloat value, GRXMeasureSpecMode mode) { 29 | GRXMeasureSpec spec; 30 | spec.value = value; 31 | spec.mode = mode; 32 | return spec; 33 | } 34 | 35 | #pragma mark - Comparison 36 | 37 | CG_INLINE BOOL 38 | GRXMeasureSpecIsZero(GRXMeasureSpec spec) { 39 | return spec.value == 0 && spec.mode == 0; 40 | } 41 | 42 | CG_INLINE BOOL 43 | GRXMeasureSpecsEqual(GRXMeasureSpec spec1, GRXMeasureSpec spec2) { 44 | return spec1.value == spec2.value && 45 | spec1.mode == spec2.mode; 46 | } 47 | 48 | CG_INLINE BOOL 49 | GRXFullMeasureSpecsEqual(GRXFullMeasureSpec spec1, GRXFullMeasureSpec spec2) { 50 | return GRXMeasureSpecsEqual(spec1.width, spec2.width) && 51 | GRXMeasureSpecsEqual(spec1.height, spec2.height); 52 | } 53 | 54 | #pragma mark - Measurement 55 | 56 | CG_INLINE CGFloat 57 | GRXMeasureSpecResolveSizeValue(CGFloat sizeValue, GRXMeasureSpec measureSpec) { 58 | switch (measureSpec.mode) { 59 | default: 60 | case GRXMeasureSpecUnspecified: 61 | return sizeValue; 62 | case GRXMeasureSpecExactly: 63 | return measureSpec.value; 64 | case GRXMeasureSpecAtMost: 65 | return MIN(sizeValue, measureSpec.value); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Classes/GRXRelativeLayout+GRXLayoutInflater.h: -------------------------------------------------------------------------------- 1 | #import "GRXRelativeLayout.h" 2 | #import "GRXLayout+GRXLayoutInflater.h" 3 | 4 | @interface GRXRelativeLayout (GRXLayoutInflater) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Classes/GRXRelativeLayout+GRXLayoutInflater.m: -------------------------------------------------------------------------------- 1 | #import "GRXRelativeLayout+GRXLayoutInflater.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @implementation GRXRelativeLayout (GRXLayoutInflater) 6 | 7 | static NSDictionary *RelativeRules = nil; 8 | static NSDictionary *ParentRules = nil; 9 | 10 | + (void)initializeRuleDictionaries { 11 | RelativeRules = @{@"toLeftOf": @(GRXRelativeLayoutRuleLeftOf), 12 | @"toRightOf": @(GRXRelativeLayoutRuleRightOf), 13 | @"above": @(GRXRelativeLayoutRuleAbove), 14 | @"below": @(GRXRelativeLayoutRuleBelow), 15 | 16 | @"alignLeft": @(GRXRelativeLayoutRuleAlignLeft), 17 | @"alignRight": @(GRXRelativeLayoutRuleAlignRight), 18 | @"alignTop": @(GRXRelativeLayoutRuleAlignTop), 19 | @"alignBottom": @(GRXRelativeLayoutRuleAlignBottom), }; 20 | 21 | ParentRules = @{ 22 | @"alignParentLeft": @(GRXRelativeLayoutParentRuleAlignLeft), 23 | @"alignParentRight": @(GRXRelativeLayoutParentRuleAlignRight), 24 | @"alignParentTop": @(GRXRelativeLayoutParentRuleAlignTop), 25 | @"alignParentBottom": @(GRXRelativeLayoutParentRuleAlignBottom), 26 | 27 | @"centerInParent": @(GRXRelativeLayoutParentRuleCenter), 28 | @"centerHorizontal": @(GRXRelativeLayoutParentRuleCenterHorizontal), 29 | @"centerVertical": @(GRXRelativeLayoutParentRuleCenterVertical), 30 | }; 31 | } 32 | 33 | - (void)configureSubviewLayoutParams:(GRXLayoutParams *)params 34 | fromDictionary:(NSDictionary *)dict 35 | inInflater:(GRXLayoutInflater *)inflater { 36 | [super configureSubviewLayoutParams:params 37 | fromDictionary:dict 38 | inInflater:inflater]; 39 | 40 | static dispatch_once_t onceToken; 41 | dispatch_once(&onceToken, ^{ 42 | [self.class initializeRuleDictionaries]; 43 | }); 44 | 45 | GRXRelativeLayoutParams *p = (GRXRelativeLayoutParams *)params; 46 | 47 | for (NSString *key in ParentRules.allKeys) { 48 | NSString *value = dict[key]; 49 | if (value == nil) { 50 | continue; 51 | } 52 | 53 | if (value.boolValue) { 54 | GRXRelativeLayoutParentRule rule = [ParentRules[key] unsignedIntegerValue]; 55 | [p setParentRule:rule]; 56 | } 57 | } 58 | 59 | for (NSString *key in RelativeRules.allKeys) { 60 | NSString *viewId = dict[key]; 61 | if (viewId == nil) { 62 | continue; 63 | } 64 | UIView *view = [self grx_subviewForIdentifier:viewId]; 65 | if (view == nil) { 66 | NSLog(@"Warning: view not found for id %@ in GRXRelativeLayout", viewId); 67 | continue; 68 | } 69 | GRXRelativeLayoutRule rule = [RelativeRules[key] integerValue]; 70 | [p setRule:rule forView:view]; 71 | } 72 | } 73 | 74 | @end 75 | 76 | NS_ASSUME_NONNULL_END 77 | -------------------------------------------------------------------------------- /Classes/GRXRelativeLayout.h: -------------------------------------------------------------------------------- 1 | #import "GRXLayout.h" 2 | #import "GRXRelativeLayoutParams.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @interface GRXRelativeLayout : GRXLayout 7 | 8 | @property (nonatomic, getter = isHierarchyDirty) BOOL dirtyHierarchy; 9 | 10 | @end 11 | 12 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /Classes/GRXRelativeLayoutParams.h: -------------------------------------------------------------------------------- 1 | #import "GRXLayoutParams.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | typedef NS_ENUM (NSUInteger, GRXRelativeLayoutRule) { 6 | GRXRelativeLayoutRuleLeftOf = 0, 7 | GRXRelativeLayoutRuleRightOf, 8 | GRXRelativeLayoutRuleAbove, 9 | GRXRelativeLayoutRuleBelow, 10 | 11 | GRXRelativeLayoutRuleAlignLeft, 12 | GRXRelativeLayoutRuleAlignTop, 13 | GRXRelativeLayoutRuleAlignRight, 14 | GRXRelativeLayoutRuleAlignBottom, 15 | 16 | GRXRelativeLayoutRuleCount, 17 | }; 18 | 19 | typedef NS_ENUM (NSUInteger, GRXRelativeLayoutParentRule) { 20 | GRXRelativeLayoutParentRuleAlignLeft = 0, 21 | GRXRelativeLayoutParentRuleAlignRight, 22 | GRXRelativeLayoutParentRuleAlignTop, 23 | GRXRelativeLayoutParentRuleAlignBottom, 24 | 25 | GRXRelativeLayoutParentRuleCenter, 26 | GRXRelativeLayoutParentRuleCenterHorizontal, 27 | GRXRelativeLayoutParentRuleCenterVertical, 28 | 29 | GRXRelativeLayoutParentRuleCount, 30 | }; 31 | 32 | 33 | @interface GRXRelativeLayoutParams : GRXLayoutParams 34 | 35 | // TODO use custom class / structure? 36 | + (NSArray *)verticalRules; 37 | + (NSArray *)horizontalRules; 38 | 39 | @property (nonatomic) CGFloat top, left, bottom, right; 40 | @property (nonatomic, readonly) CGRect rect; 41 | 42 | - (BOOL)hasRule:(GRXRelativeLayoutRule)rule; 43 | - (nullable __kindof UIView *)viewForRule:(GRXRelativeLayoutRule)rule; 44 | - (void)setRule:(GRXRelativeLayoutRule)rule 45 | forView:(nullable __kindof UIView *)view; 46 | 47 | - (BOOL)hasParentRule:(GRXRelativeLayoutParentRule)parentRule; 48 | - (void)setParentRule:(GRXRelativeLayoutParentRule)parentRule; 49 | - (void)setParentRule:(GRXRelativeLayoutParentRule)parentRule 50 | active:(BOOL)active; 51 | 52 | @end 53 | 54 | @interface UIView (GRXRelativeLayoutParams) 55 | 56 | @property (nullable, nonatomic, readonly) GRXRelativeLayoutParams *grx_relativeLayoutParams; 57 | 58 | @end 59 | 60 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /Classes/GRXRelativeLayoutParams.m: -------------------------------------------------------------------------------- 1 | #import "GRXRelativeLayoutParams.h" 2 | #import "UIView+GRXLayout.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @interface GRXRelativeLayoutParams () 7 | @property(nonatomic) NSMapTable *mutableRules; 8 | @property(nonatomic) NSMutableSet *mutableParentRules; 9 | @end 10 | 11 | @implementation GRXRelativeLayoutParams 12 | 13 | + (NSArray *)verticalRules { 14 | static NSArray *GRXRelativeVerticalRules; 15 | static dispatch_once_t onceToken; 16 | dispatch_once(&onceToken, ^{ 17 | GRXRelativeVerticalRules = @[ 18 | @(GRXRelativeLayoutRuleAbove), 19 | @(GRXRelativeLayoutRuleBelow), 20 | @(GRXRelativeLayoutRuleAlignTop), 21 | @(GRXRelativeLayoutRuleAlignBottom), 22 | ]; 23 | }); 24 | return GRXRelativeVerticalRules; 25 | } 26 | 27 | + (NSArray *)horizontalRules { 28 | static NSArray *GRXRelativeHorizontalRules; 29 | static dispatch_once_t onceToken; 30 | dispatch_once(&onceToken, ^{ 31 | GRXRelativeHorizontalRules =@[ 32 | @(GRXRelativeLayoutRuleLeftOf), 33 | @(GRXRelativeLayoutRuleRightOf), 34 | @(GRXRelativeLayoutRuleAlignLeft), 35 | @(GRXRelativeLayoutRuleAlignRight), 36 | ]; 37 | }); 38 | return GRXRelativeHorizontalRules; 39 | } 40 | 41 | #pragma mark - initialization methods 42 | 43 | - (instancetype)initWithSize:(CGSize)size { 44 | self = [super initWithSize:size]; 45 | if (self) { 46 | [self setupRules]; 47 | } 48 | return self; 49 | } 50 | 51 | - (instancetype)initWithLayoutParams:(GRXLayoutParams *)layoutParams { 52 | self = [super initWithLayoutParams:layoutParams]; 53 | if (self) { 54 | [self setupRules]; 55 | if ([layoutParams isKindOfClass:GRXRelativeLayoutParams.class]) { 56 | GRXRelativeLayoutParams *relParams = (GRXRelativeLayoutParams *)layoutParams; 57 | _mutableRules = relParams.mutableRules; 58 | _mutableParentRules = relParams.mutableParentRules; 59 | _top = relParams.top; 60 | _left = relParams.left; 61 | _bottom = relParams.bottom; 62 | _right = relParams.right; 63 | } 64 | } 65 | return self; 66 | } 67 | 68 | - (void)setupRules { 69 | _mutableRules = [NSMapTable strongToWeakObjectsMapTable]; 70 | _mutableParentRules = [NSMutableSet setWithCapacity:GRXRelativeLayoutParentRuleCount]; 71 | } 72 | 73 | #pragma mark - instance methods 74 | 75 | - (BOOL)hasRule:(GRXRelativeLayoutRule)rule { 76 | return [self.mutableRules objectForKey:@(rule)] != nil; 77 | } 78 | 79 | - (nullable __kindof UIView *)viewForRule:(GRXRelativeLayoutRule)rule { 80 | return [self.mutableRules objectForKey:@(rule)]; 81 | } 82 | 83 | - (void)setRule:(GRXRelativeLayoutRule)rule 84 | forView:(nullable __kindof UIView *)view { 85 | [self.mutableRules setObject:view forKey:@(rule)]; 86 | } 87 | 88 | - (BOOL)hasParentRule:(GRXRelativeLayoutParentRule)parentRule { 89 | return [self.mutableParentRules containsObject:@(parentRule)]; 90 | } 91 | 92 | - (void)setParentRule:(GRXRelativeLayoutParentRule)parentRule { 93 | [self setParentRule:parentRule active:YES]; 94 | } 95 | 96 | - (void)setParentRule:(GRXRelativeLayoutParentRule)parentRule 97 | active:(BOOL)active { 98 | if (active) { 99 | [self.mutableParentRules addObject:@(parentRule)]; 100 | } else { 101 | [self.mutableParentRules removeObject:@(parentRule)]; 102 | } 103 | } 104 | 105 | - (NSString *)debugDescription { 106 | NSString *description = [super debugDescription]; 107 | description = [description stringByAppendingFormat:@"[%.0f, %.0f, %.0f, %.0f]", _left, _top, _right, _bottom]; 108 | return description; 109 | } 110 | 111 | #pragma mark - protected methods 112 | 113 | - (CGRect)rect { 114 | CGRect rect; 115 | rect.origin.x = _left; 116 | rect.origin.y = _top; 117 | rect.size.width = _right - _left; 118 | rect.size.height = _bottom - _top; 119 | return rect; 120 | } 121 | 122 | @end 123 | 124 | @implementation UIView (GRXRelativeLayoutParams) 125 | 126 | - (nullable GRXRelativeLayoutParams *)grx_relativeLayoutParams { 127 | GRXLayoutParams *params = self.grx_layoutParams; 128 | if ([params isKindOfClass:GRXRelativeLayoutParams.class]) { 129 | return (GRXRelativeLayoutParams *)params; 130 | } else { 131 | NSAssert(NO, @"Not GRXLinearLayoutParams for view %@", self); 132 | return nil; 133 | } 134 | } 135 | 136 | @end 137 | 138 | NS_ASSUME_NONNULL_END 139 | -------------------------------------------------------------------------------- /Classes/GRXTextView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /** 4 | * A simple subclass of UITextView that makes it ready to be layouted, without scroll and insets 5 | */ 6 | 7 | @interface GRXTextView : UITextView 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Classes/GRXTextView.m: -------------------------------------------------------------------------------- 1 | #import "GRXTextView.h" 2 | #import "UIView+GRXLayout.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @implementation GRXTextView 7 | 8 | - (instancetype)initWithFrame:(CGRect)frame { 9 | self = [super initWithFrame:frame]; 10 | if (self) { 11 | [self commonInit]; 12 | } 13 | return self; 14 | } 15 | 16 | - (instancetype)initWithFrame:(CGRect)frame textContainer:(nullable NSTextContainer *)textContainer { 17 | self = [super initWithFrame:frame textContainer:textContainer]; 18 | if (self) { 19 | [self commonInit]; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)commonInit { 25 | self.scrollEnabled = NO; 26 | self.editable = NO; 27 | self.contentInset = UIEdgeInsetsMake(0, 0, 0, 0); 28 | self.textContainerInset = UIEdgeInsetsMake(-3, -5, 0, 0); 29 | self.backgroundColor = [UIColor clearColor]; 30 | self.clipsToBounds = NO; 31 | } 32 | 33 | - (CGSize)grx_measureForWidthSpec:(GRXMeasureSpec)widthSpec 34 | heightSpec:(GRXMeasureSpec)heightSpec { 35 | CGSize measurementSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); 36 | if (widthSpec.mode != GRXMeasureSpecUnspecified) { 37 | measurementSize.width = widthSpec.value; 38 | } 39 | if (heightSpec.mode != GRXMeasureSpecUnspecified) { 40 | measurementSize.height = heightSpec.value; 41 | } 42 | 43 | CGSize measuredSize = [self sizeThatFits:measurementSize]; 44 | if (widthSpec.mode == GRXMeasureSpecExactly) { 45 | measuredSize.width = widthSpec.value; 46 | } 47 | if (heightSpec.mode == GRXMeasureSpecExactly) { 48 | measuredSize.height = heightSpec.value; 49 | } 50 | 51 | return measuredSize; 52 | } 53 | 54 | 55 | - (void)setText:(nullable NSString *)text { 56 | if (NO == [self.text isEqualToString:text]) { 57 | [super setText:text]; 58 | [self grx_setNeedsLayoutInParent]; 59 | } 60 | } 61 | 62 | - (void)setAttributedText:(nullable NSAttributedString *)attributedText { 63 | if (NO == [self.attributedText isEqualToAttributedString:attributedText]) { 64 | [super setAttributedText:attributedText]; 65 | [self grx_setNeedsLayoutInParent]; 66 | } 67 | } 68 | 69 | - (void)setFont:(nullable UIFont *)font { 70 | if (NO == [self.font isEqual:font]) { 71 | [super setFont:font]; 72 | [self grx_setNeedsLayoutInParent]; 73 | } 74 | } 75 | 76 | @end 77 | 78 | NS_ASSUME_NONNULL_END 79 | -------------------------------------------------------------------------------- /Classes/GranadaLayout.h: -------------------------------------------------------------------------------- 1 | #import "UIView+GRXLayout.h" 2 | #import "GRXLinearLayout.h" 3 | #import "GRXRelativeLayout.h" 4 | #import "GRXLayoutInflater.h" -------------------------------------------------------------------------------- /Classes/Private/GRXDependencyGraph.h: -------------------------------------------------------------------------------- 1 | #import "GRXRelativeLayoutParams.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface GRXDependencyNode : NSObject 6 | 7 | @property (nonatomic) UIView *view; 8 | @property (nonatomic) NSMutableArray *dependents; 9 | @property (nonatomic) NSMutableDictionary *dependencies; 10 | #ifdef DEBUG 11 | @property (nonatomic) NSNumber *viewId; 12 | @property (nonatomic) NSString *debugIdentifier; 13 | #endif 14 | 15 | + (GRXDependencyNode *)nodeForView:(UIView *)view; 16 | - (void)recycle; 17 | 18 | @end 19 | 20 | @interface GRXDependencyGraph : NSObject 21 | 22 | @property (nullable, nonatomic) NSMutableDictionary *nodes; // {viewIdentifier, node} 23 | 24 | - (void)add:(UIView *)view; 25 | - (NSArray<__kindof UIView *> *)sortedViewsWithRules:(NSArray *)rulesArray; 26 | - (void)clear; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /Classes/Private/GRXDependencyGraph.m: -------------------------------------------------------------------------------- 1 | #import "GRXDependencyGraph.h" 2 | #import "UIView+GRXLayout.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @interface GRXDependencyGraph () 7 | @property (nonatomic) NSMutableArray *roots; 8 | @end 9 | 10 | @implementation GRXDependencyGraph 11 | 12 | - (instancetype)init { 13 | self = [super init]; 14 | if (self) { 15 | _nodes = [NSMutableDictionary dictionary]; 16 | _roots = [NSMutableArray array]; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)clear { 22 | for (GRXDependencyNode *node in _nodes) { 23 | [node recycle]; 24 | } 25 | [_nodes removeAllObjects]; 26 | [_roots removeAllObjects]; 27 | } 28 | 29 | - (void)add:(UIView *)view { 30 | GRXDependencyNode *node = [GRXDependencyNode nodeForView:view]; 31 | _nodes[view.grx_layoutId] = node; 32 | } 33 | 34 | - (NSArray<__kindof UIView *> *)sortedViewsWithRules:(NSArray *)rulesArray { 35 | NSMutableArray<__kindof UIView *> *sortedViews = [NSMutableArray new]; 36 | 37 | [self findRootsWithRulesFilter:rulesArray]; 38 | 39 | NSUInteger index = 0; 40 | while (_roots.count > 0) { 41 | GRXDependencyNode *node = [_roots firstObject]; 42 | [_roots removeObjectAtIndex:0]; 43 | 44 | UIView *view = node.view; 45 | NSNumber *identifier = view.grx_layoutId; 46 | 47 | ++index; 48 | [sortedViews addObject:view]; 49 | for (GRXDependencyNode *dependent in node.dependents) { 50 | [dependent.dependencies removeObjectForKey:identifier]; 51 | if (dependent.dependencies.count == 0) { 52 | [_roots addObject:dependent]; 53 | } 54 | } 55 | } 56 | 57 | NSAssert(index >= sortedViews.count, @"There are circular dependencies in this relative layout"); 58 | return sortedViews; 59 | } 60 | 61 | - (void)findRootsWithRulesFilter:(NSArray *)rulesFilter { 62 | for (GRXDependencyNode *node in _nodes.allValues) { 63 | [node.dependencies removeAllObjects]; 64 | [node.dependents removeAllObjects]; 65 | } 66 | 67 | for (GRXDependencyNode *node in _nodes.allValues) { 68 | UIView *view = node.view; 69 | GRXRelativeLayoutParams *params = view.grx_relativeLayoutParams; 70 | for (NSNumber *rule in rulesFilter) { 71 | UIView *referred = [params viewForRule:rule.unsignedIntegerValue]; 72 | if (referred != nil) { 73 | GRXDependencyNode *dependency = _nodes[referred.grx_layoutId]; 74 | NSAssert(dependency != nil, @"A view must not refer to a nonexisting view on RelativeLayout"); 75 | NSAssert(dependency != node, @"A view must not refer to itself on RelativeLayout"); 76 | 77 | // set as dependent on the obtained 78 | [dependency.dependents addObject:node]; 79 | // add a dependency to the current node 80 | node.dependencies[referred.grx_layoutId] = dependency; 81 | } 82 | } 83 | } 84 | 85 | [_roots removeAllObjects]; 86 | for (GRXDependencyNode *node in _nodes.allValues) { 87 | if (node.dependencies.count == 0) { 88 | [_roots addObject:node]; 89 | } 90 | } 91 | } 92 | 93 | @end 94 | 95 | @implementation GRXDependencyNode 96 | 97 | static NSMutableArray *GRXDependencyNodePool; 98 | static const NSUInteger GRXDependencyNodePoolCapacity = 64; 99 | 100 | + (GRXDependencyNode *)nodeForView:(UIView *)view { 101 | static dispatch_once_t onceToken; 102 | dispatch_once(&onceToken, ^{ 103 | GRXDependencyNodePool = [[NSMutableArray alloc] initWithCapacity:GRXDependencyNodePoolCapacity]; 104 | }); 105 | 106 | GRXDependencyNode *node = [GRXDependencyNodePool lastObject]; 107 | [GRXDependencyNodePool removeLastObject]; 108 | if (node == nil) { 109 | node = [GRXDependencyNode new]; 110 | } 111 | node.view = view; 112 | #ifdef DEBUG 113 | node.viewId = view.grx_layoutId; 114 | node.debugIdentifier = view.grx_identifier ? view.grx_identifier : @"UNNAMED"; 115 | #endif 116 | return node; 117 | } 118 | 119 | - (instancetype)init { 120 | self = [super init]; 121 | if (self) { 122 | _dependents = [NSMutableArray new]; 123 | _dependencies = [NSMutableDictionary new]; 124 | } 125 | return self; 126 | } 127 | 128 | - (void)recycle { 129 | _view = nil; 130 | [_dependents removeAllObjects]; 131 | [_dependencies removeAllObjects]; 132 | if (GRXDependencyNodePool.count < GRXDependencyNodePoolCapacity) { 133 | [GRXDependencyNodePool addObject:self]; 134 | } 135 | } 136 | 137 | @end 138 | 139 | NS_ASSUME_NONNULL_END 140 | -------------------------------------------------------------------------------- /Classes/Private/GRXLayout+Protected.h: -------------------------------------------------------------------------------- 1 | #import "GRXLayout.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface GRXLayout (Protected) 6 | 7 | - (GRXMeasureSpec)subviewSpecWithParentSpec:(GRXMeasureSpec)spec 8 | padding:(CGFloat)padding 9 | subviewDimension:(CGFloat)subviewDimension; 10 | - (GRXMeasureSpec)subviewSpecWithStart:(CGFloat)subviewStart 11 | end:(CGFloat)subviewEnd 12 | subviewSize:(CGFloat)subviewSize 13 | startMargin:(CGFloat)startMargin 14 | endMargin:(CGFloat)endMargin 15 | startPadding:(CGFloat)startPadding 16 | endPadding:(CGFloat)endPadding 17 | ownSize:(CGFloat)ownSize; 18 | 19 | - (CGSize)measureSubviewWithMargins:(UIView *)subview 20 | parentWidthSpec:(GRXMeasureSpec)parentWidthSpec 21 | widthUsed:(CGFloat)widthUsed 22 | parentHeightSpec:(GRXMeasureSpec)parentHeightSpec 23 | heightUsed:(CGFloat)heightUsed; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /Classes/Private/GRXLayoutParams_Protected.h: -------------------------------------------------------------------------------- 1 | #import "GRXLayoutParams.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface GRXLayoutParams () 6 | 7 | - (void)setView:(UIView *)view; 8 | 9 | @end 10 | 11 | NS_ASSUME_NONNULL_END 12 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIColor+GRXHexParse.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface UIColor (GRXHexParse) 6 | 7 | + (UIColor *)grx_colorFromRGBHex:(NSInteger)RGBValue; 8 | 9 | // Supports following formats: RRGGBB, AARRGGBB 10 | + (UIColor *)grx_colorFromRGBHexString:(NSString *)RGBString; 11 | 12 | @end 13 | 14 | NS_ASSUME_NONNULL_END 15 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIColor+GRXHexParse.m: -------------------------------------------------------------------------------- 1 | #import "UIColor+GRXHexParse.h" 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @implementation UIColor (GRXHexParse) 6 | 7 | + (UIColor *)grx_colorFromRGBHex:(NSInteger)RGBValue { 8 | return [UIColor colorWithRed:((float)((RGBValue & 0xFF0000) >> 16)) / 255.0 9 | green:((float)((RGBValue & 0xFF00) >> 8)) / 255.0 10 | blue:((float)(RGBValue & 0xFF)) / 255.0 11 | alpha:1.0]; 12 | } 13 | 14 | // kudos to http://stackoverflow.com/a/7180905/305582 15 | + (UIColor *)grx_colorFromRGBHexString:(NSString *)RGBString { 16 | NSString *colorString = [[RGBString stringByReplacingOccurrencesOfString:@"#" withString:@""] uppercaseString]; 17 | CGFloat alpha, red, blue, green; 18 | switch ([colorString length]) { 19 | case 6:// #RRGGBB 20 | alpha = 1.0f; 21 | red = [self colorComponentFrom:colorString range:NSMakeRange(0, 2)]; 22 | green = [self colorComponentFrom:colorString range:NSMakeRange(2, 2)]; 23 | blue = [self colorComponentFrom:colorString range:NSMakeRange(4, 2)]; 24 | break; 25 | case 8:// #AARRGGBB 26 | alpha = [self colorComponentFrom:colorString range:NSMakeRange(0, 2)]; 27 | red = [self colorComponentFrom:colorString range:NSMakeRange(2, 2)]; 28 | green = [self colorComponentFrom:colorString range:NSMakeRange(4, 2)]; 29 | blue = [self colorComponentFrom:colorString range:NSMakeRange(6, 2)]; 30 | break; 31 | default: 32 | [NSException raise:@"Invalid color value" 33 | format:@"Color value %@ is invalid. It should be a hex value of the form #RRGGBB or #AARRGGBB", RGBString]; 34 | break; 35 | } 36 | return [UIColor colorWithRed:red green:green blue:blue alpha:alpha]; 37 | } 38 | 39 | + (CGFloat)colorComponentFrom:(NSString *)string 40 | range:(NSRange)range { 41 | NSString *substring = [string substringWithRange:range]; 42 | unsigned hexComponent; 43 | [[NSScanner scannerWithString:substring] scanHexInt:&hexComponent]; 44 | return hexComponent / 255.0; 45 | } 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIDevice+Util.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface UIDevice (Helper) 5 | 6 | + (CGFloat)grx_screenScale; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIDevice+Util.m: -------------------------------------------------------------------------------- 1 | #import "UIDevice+Util.h" 2 | #include 3 | 4 | @implementation UIDevice (Util) 5 | 6 | + (CGFloat)grx_screenScale { 7 | static CGFloat scale; 8 | static dispatch_once_t onceToken; 9 | dispatch_once(&onceToken, ^{ 10 | scale = UIScreen.mainScreen.scale; 11 | }); 12 | return scale; 13 | } 14 | 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIImageView+GRXLayout.h: -------------------------------------------------------------------------------- 1 | #import "UIView+GRXLayout.h" 2 | 3 | @interface UIImageView (GRXLayout) 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIImageView+GRXLayout.m: -------------------------------------------------------------------------------- 1 | #import "UIImageView+GRXLayout.h" 2 | #import "UIDevice+Util.h" 3 | 4 | @implementation UIImageView (GRXLayout) 5 | 6 | // we override this method because -sizeThatFits doesn't consider screen and image scales 7 | - (CGSize)grx_measureForWidthSpec:(GRXMeasureSpec)widthSpec 8 | heightSpec:(GRXMeasureSpec)heightSpec { 9 | CGFloat scaleMult = self.image.scale / UIDevice.grx_screenScale; 10 | const CGSize imageSize = CGSizeMake(self.image.size.width * scaleMult, 11 | self.image.size.height * scaleMult); 12 | CGSize measuredSize = imageSize; 13 | 14 | if (widthSpec.mode == GRXMeasureSpecExactly) { 15 | measuredSize.width = widthSpec.value; 16 | if (heightSpec.mode != GRXMeasureSpecExactly) { 17 | measuredSize.height *= (measuredSize.width / imageSize.width); 18 | } 19 | } 20 | if (heightSpec.mode == GRXMeasureSpecExactly) { 21 | measuredSize.height = heightSpec.value; 22 | if (widthSpec.mode != GRXMeasureSpecExactly) { 23 | measuredSize.width *= (measuredSize.height / imageSize.height); 24 | } 25 | } 26 | 27 | CGSize minSize = self.grx_minSize; 28 | measuredSize.width = MAX(minSize.width, measuredSize.width); 29 | measuredSize.height = MAX(minSize.height, measuredSize.height); 30 | 31 | return measuredSize; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UISwitch+GRXLayout.h: -------------------------------------------------------------------------------- 1 | #import "UIView+GRXLayout.h" 2 | 3 | @interface UISwitch (GRXLayout) 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UISwitch+GRXLayout.m: -------------------------------------------------------------------------------- 1 | #import "UISwitch+GRXLayout.h" 2 | #import "UIDevice+Util.h" 3 | 4 | @implementation UISwitch (GRXLayout) 5 | 6 | - (CGSize)grx_minSize { 7 | return [self sizeThatFits:CGSizeZero]; 8 | } 9 | 10 | // UISwitches always have the same size, and -sizeThatFits: returns the currentSize 11 | 12 | - (CGSize)grx_measureForWidthSpec:(GRXMeasureSpec)widthSpec 13 | heightSpec:(GRXMeasureSpec)heightSpec { 14 | CGSize size = [self grx_minSize]; 15 | return size; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIView+Frame.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface UIView (FrameU) 5 | 6 | #pragma mark - Shorthand properties for frame 7 | 8 | @property (nonatomic) CGPoint origin; 9 | @property (nonatomic) CGSize size; 10 | 11 | @property (nonatomic) CGFloat top; 12 | @property (nonatomic) CGFloat left; 13 | @property (nonatomic) CGFloat bottom; 14 | @property (nonatomic) CGFloat right; 15 | 16 | @property (nonatomic) CGFloat width; 17 | @property (nonatomic) CGFloat height; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIView+Frame.m: -------------------------------------------------------------------------------- 1 | #import "UIView+Frame.h" 2 | 3 | @implementation UIView (FrameU) 4 | 5 | #pragma mark - Shorthand properties for frame 6 | 7 | - (CGPoint)origin { 8 | return self.frame.origin; 9 | } 10 | 11 | - (void)setOrigin:(CGPoint)origin { 12 | CGRect frame = self.frame; 13 | frame.origin = origin; 14 | self.frame = frame; 15 | } 16 | 17 | - (CGSize)size { 18 | return self.frame.size; 19 | } 20 | 21 | - (void)setSize:(CGSize)size { 22 | CGRect frame = self.frame; 23 | frame.size = size; 24 | self.frame = frame; 25 | } 26 | 27 | - (CGFloat)top { 28 | return self.frame.origin.y; 29 | } 30 | 31 | - (void)setTop:(CGFloat)y { 32 | CGRect frame = self.frame; 33 | frame.origin.y = y; 34 | self.frame = frame; 35 | } 36 | 37 | - (CGFloat)left { 38 | return self.frame.origin.x; 39 | } 40 | 41 | - (void)setLeft:(CGFloat)x { 42 | CGRect frame = self.frame; 43 | frame.origin.x = x; 44 | self.frame = frame; 45 | } 46 | 47 | - (CGFloat)bottom { 48 | return self.frame.origin.y + self.frame.size.height; 49 | } 50 | 51 | - (void)setBottom:(CGFloat)bottom { 52 | CGRect frame = self.frame; 53 | frame.origin.y = bottom - frame.size.height; 54 | self.frame = frame; 55 | } 56 | 57 | - (CGFloat)right { 58 | return self.frame.origin.x + self.frame.size.width; 59 | } 60 | 61 | - (void)setRight:(CGFloat)right { 62 | CGRect frame = self.frame; 63 | frame.origin.x = right - frame.size.width; 64 | self.frame = frame; 65 | } 66 | 67 | - (CGFloat)width { 68 | return self.frame.size.width; 69 | } 70 | 71 | - (void)setWidth:(CGFloat)width { 72 | CGRect frame = self.frame; 73 | frame.size.width = width; 74 | self.frame = frame; 75 | } 76 | 77 | - (CGFloat)height { 78 | return self.frame.size.height; 79 | } 80 | 81 | - (void)setHeight:(CGFloat)height { 82 | CGRect frame = self.frame; 83 | frame.size.height = height; 84 | self.frame = frame; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /Classes/UIKitCategories/UIView+GRXLayout.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "UIView+Frame.h" 3 | #import "GRXLayoutParams.h" 4 | #import "GRXMeasureSpec.h" 5 | 6 | NS_ASSUME_NONNULL_BEGIN 7 | 8 | @class GRXLayout; 9 | 10 | typedef CGSize (^GRXMeasurementBlock)(GRXMeasureSpec widthSpec, GRXMeasureSpec heightSpec); 11 | 12 | typedef NS_ENUM (NSUInteger, GRXVisibility) { 13 | GRXVisibilityVisible = 0, // the view is visible and will be layouted 14 | GRXVisibilityHidden = 1, // the view is not visible but keeps its size and will be layouted 15 | GRXVisibilityGone = 2 // the view is not visible, its size is zero and won't be layouted 16 | }; 17 | 18 | @interface UIView (GRXLayout) 19 | 20 | // unique id for every view 21 | // does never return nil, the number is always > 0 22 | @property (nonatomic, readonly) NSNumber *grx_layoutId; 23 | 24 | @property (nonatomic, setter = grx_setMinSize :) CGSize grx_minSize; 25 | @property (nullable, nonatomic, setter = grx_setLayoutParams :) GRXLayoutParams *grx_layoutParams; 26 | @property (nonatomic, setter = grx_setVisibility :) GRXVisibility grx_visibility; 27 | 28 | @property (nonatomic, readonly) CGSize grx_measuredSize; 29 | 30 | // set an implementation of a measurement method in order to override the default one 31 | // without needing to subclass 32 | @property (nullable, nonatomic, copy, setter = grx_setMeasurementBlock :) CGSize (^grx_measurementBlock)(GRXMeasureSpec widthSpec, GRXMeasureSpec heightSpec); 33 | 34 | @property (nullable, nonatomic, setter = grx_setIdentifier :) NSString *grx_identifier; 35 | 36 | - (instancetype)initWithLayoutParams:(GRXLayoutParams *)layoutParams; 37 | - (instancetype)initWithDefaultParamsInLayout:(GRXLayout *)layout; 38 | 39 | // returns a subview with the given identifier, nil if none 40 | - (nullable __kindof UIView *)grx_subviewForIdentifier:(NSString *)identifier; 41 | 42 | // finds a subview through the whole hierarchy with the given identifier 43 | // returns the first one found, nil if no results 44 | - (nullable __kindof UIView *)grx_findViewWithIdentifier:(NSString *)identifier; 45 | 46 | // this method must NOT be overriden and is called by layouts 47 | // implements a caching mechanism so measureForWidthSpec:heightSpec: is not called for same specs 48 | - (CGSize)grx_measuredSizeForWidthSpec:(GRXMeasureSpec)widthSpec 49 | heightSpec:(GRXMeasureSpec)heightSpec; 50 | - (void)grx_invalidateMeasuredSize; 51 | 52 | // measurement is done within this method 53 | - (CGSize)grx_measureForWidthSpec:(GRXMeasureSpec)widthSpec 54 | heightSpec:(GRXMeasureSpec)heightSpec; 55 | 56 | // this method must be called when the size of this view may change, and requests the top 57 | // layout to relayout its subviews 58 | - (void)grx_setNeedsLayoutInParent; 59 | 60 | - (NSString *)grx_debugDescription; 61 | 62 | @end 63 | 64 | NS_ASSUME_NONNULL_END 65 | -------------------------------------------------------------------------------- /Classes/UIView+GRXLayoutInflater.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "UIView+GRXLayout.h" 3 | #import "GRXLayoutInflater.h" 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @interface UIView (GRXLayoutInflater) 8 | 9 | // Override this method to parse extended options from the layout file 10 | - (void)grx_configureFromDictionary:(NSDictionary *)dictionary; 11 | 12 | // This method is called by the layout inflater on the root view defined in a .grx file when it 13 | // has finished parsing the file. 14 | // Override this method to set loaded views into properties, the base implementation does nothing by default 15 | 16 | // TODO ? could use reflection to initialize properties automatically 17 | - (void)grx_didLoadFromInflater:(GRXLayoutInflater *)inflater; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /Classes/UIView+GRXLayoutInflater.m: -------------------------------------------------------------------------------- 1 | #import "UIView+GRXLayoutInflater.h" 2 | #import "UIColor+GRXHexParse.h" 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @implementation UIView (GRXLayoutInflater) 7 | 8 | - (void)grx_configureFromDictionary:(NSDictionary *)dictionary { 9 | CGSize minSize; 10 | minSize.width = [dictionary[@"minWidth"] floatValue]; 11 | minSize.height = [dictionary[@"minHeight"] floatValue]; 12 | if (minSize.width > 0 && minSize.height > 0) { 13 | self.grx_minSize = minSize; 14 | } 15 | 16 | NSString *visibilityString = dictionary[@"visibility"]; 17 | if ( [visibilityString isEqualToString:@"hidden"] ) { 18 | self.grx_visibility = GRXVisibilityHidden; 19 | } else if ( [visibilityString isEqualToString:@"gone"] ) { 20 | self.grx_visibility = GRXVisibilityGone; 21 | } else if ( [visibilityString isEqualToString:@"visible"] ) { 22 | self.grx_visibility = GRXVisibilityVisible; 23 | } 24 | 25 | NSString *nuiClass = dictionary[@"nuiClass"]; 26 | #pragma clang diagnostic push 27 | #pragma clang diagnostic ignored "-Wundeclared-selector" 28 | if (nuiClass != nil && [self respondsToSelector:@selector(setNuiClass:)]) { 29 | [self performSelector:@selector(setNuiClass:) 30 | withObject:nuiClass]; 31 | } 32 | #pragma clang diagnostic pop 33 | 34 | if (NO == [GRXLayoutInflater areDebugOptionsEnabled]) { 35 | return; 36 | } 37 | 38 | NSString *bgColorStr = dictionary[@"debug_bgColor"]; 39 | if (bgColorStr.length) { 40 | NSString *colorSelectorName = [bgColorStr stringByAppendingString:@"Color"]; 41 | SEL selector = NSSelectorFromString(colorSelectorName); 42 | if ([[UIColor class] respondsToSelector:selector]) { 43 | self.backgroundColor = [UIColor performSelector:selector]; 44 | } else { 45 | self.backgroundColor = [UIColor grx_colorFromRGBHexString:bgColorStr]; 46 | } 47 | } 48 | } 49 | 50 | - (void)grx_didLoadFromInflater:(GRXLayoutInflater *)inflater { 51 | } 52 | 53 | @end 54 | 55 | NS_ASSUME_NONNULL_END 56 | -------------------------------------------------------------------------------- /Example/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Default-568h@2x.png -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/Example.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | E5114E63-1954-47CA-8EBC-4A5713AC92D8 9 | IDESourceControlProjectName 10 | Example 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 648FBFFB271FBB93B0F7A2157913BBE32A15D168 14 | https://github.com/gskbyte/GranadaLayout.git 15 | 16 | IDESourceControlProjectPath 17 | Example/Example.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 648FBFFB271FBB93B0F7A2157913BBE32A15D168 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/gskbyte/GranadaLayout.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 648FBFFB271FBB93B0F7A2157913BBE32A15D168 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 648FBFFB271FBB93B0F7A2157913BBE32A15D168 36 | IDESourceControlWCCName 37 | GranadaLayout 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcuserdata/jose.alcala.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Example.xcodeproj/project.xcworkspace/xcuserdata/jose.alcala.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 62 | 68 | 69 | 70 | 71 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 93 | 95 | 101 | 102 | 103 | 104 | 105 | 106 | 112 | 114 | 120 | 121 | 122 | 123 | 125 | 126 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/jose.alcala.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C09F21EB19DC590D0069C0A1 16 | 17 | primary 18 | 19 | 20 | C09F220419DC590D0069C0A1 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/Example/Assets/green@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Example/Assets/green@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets/lab@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Example/Assets/lab@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets/power@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Example/Assets/power@2x.png -------------------------------------------------------------------------------- /Example/Example/GRXAppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface GRXAppDelegate : UIResponder 4 | 5 | @property (strong, nonatomic) UIWindow *window; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Example/Example/GRXAppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "GRXAppDelegate.h" 2 | #import "GRXSelectionViewController.h" 3 | 4 | @implementation GRXAppDelegate 5 | 6 | - (BOOL) application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | if (NSClassFromString(@"XCTestCase")) { 9 | return YES; 10 | } 11 | 12 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 13 | 14 | self.window.backgroundColor = [UIColor whiteColor]; 15 | [self.window makeKeyAndVisible]; 16 | 17 | GRXSelectionViewController *root = [[GRXSelectionViewController alloc] init]; 18 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:root]; 19 | self.window.rootViewController = nav; 20 | 21 | return YES; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Example/Example/GRXBaseInflationTestController.h: -------------------------------------------------------------------------------- 1 | #import "GRXTopLayoutTestViewController.h" 2 | #import 3 | 4 | @interface GRXBaseInflationTestController : GRXTopLayoutTestViewController 5 | 6 | @property (nonatomic) GRXLayoutInflater *layoutInflater; 7 | 8 | - (NSString *)layoutFileNameInBundle; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Example/Example/GRXBaseInflationTestController.m: -------------------------------------------------------------------------------- 1 | #import "GRXBaseInflationTestController.h" 2 | 3 | @interface GRXBaseInflationTestController () 4 | 5 | @end 6 | 7 | @implementation GRXBaseInflationTestController 8 | 9 | - (NSString *)layoutFileNameInBundle { 10 | return @""; 11 | } 12 | 13 | - (void)viewDidLoad { 14 | NSBundle *bundle = [NSBundle bundleForClass:self.class]; 15 | NSString *path = [bundle pathForResource:[self layoutFileNameInBundle] 16 | ofType:nil]; 17 | NSData *data = [NSData dataWithContentsOfFile:path]; 18 | self.layoutInflater = [[GRXLayoutInflater alloc] initWithData:data]; 19 | [super viewDidLoad]; 20 | } 21 | 22 | - (GRXLayout *)initializeTopLayout { 23 | return (GRXLayout *)self.layoutInflater.rootView; 24 | } 25 | 26 | - (void)createViews { 27 | } 28 | 29 | 30 | - (void)viewDidAppear:(BOOL)animated { 31 | [super viewDidAppear:animated]; 32 | NSLog(@"%@", self.topLayout.grx_debugDescription); 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Example/Example/GRXCollectionViewTestController.h: -------------------------------------------------------------------------------- 1 | #import "GRXTopLayoutTestViewController.h" 2 | 3 | @interface GRXCollectionViewTestController : UICollectionViewController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexBodyView.h: -------------------------------------------------------------------------------- 1 | #import "GRXRelativeLayout.h" 2 | #import "GRXComplexData.h" 3 | 4 | @interface GRXComplexBodyView : GRXRelativeLayout 5 | 6 | @property (nonatomic) GRXComplexDataBody *body; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexBodyView.m: -------------------------------------------------------------------------------- 1 | #import "GRXComplexBodyView.h" 2 | #import 3 | 4 | @interface GRXComplexBodyView () 5 | 6 | @property (nonatomic) UIImageView *image; 7 | @property (nonatomic) UILabel *title, *subtitle; 8 | @property (nonatomic) UITextView *url; 9 | 10 | @end 11 | 12 | @implementation GRXComplexBodyView 13 | 14 | - (instancetype)initWithFrame:(CGRect)frame { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | [self loadViews]; 18 | } 19 | return self; 20 | } 21 | 22 | - (void)loadViews { 23 | GRXLayoutInflater *inflater = [[GRXLayoutInflater alloc] initWithFile:@"complex_cell_body.grx" 24 | fromBundle:[NSBundle bundleForClass:self.class] 25 | rootView:self]; 26 | 27 | self.image = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 28 | self.image.backgroundColor = [UIColor blueColor]; 29 | self.image.contentMode = UIViewContentModeScaleAspectFit; 30 | 31 | self.title = (UILabel *) [inflater.rootView grx_findViewWithIdentifier:@"title"]; 32 | self.title.font = [UIFont boldSystemFontOfSize:16]; 33 | self.title.numberOfLines = 2; 34 | 35 | self.subtitle = (UILabel *) [inflater.rootView grx_findViewWithIdentifier:@"subtitle"]; 36 | self.subtitle.numberOfLines = 3; 37 | self.subtitle.font = [UIFont systemFontOfSize:12]; 38 | self.subtitle.textColor = [UIColor grayColor]; 39 | 40 | self.url = (UITextView *)[inflater.rootView grx_findViewWithIdentifier:@"url"]; 41 | self.url.font = [UIFont systemFontOfSize:14]; 42 | self.url.textAlignment = NSTextAlignmentCenter; 43 | 44 | 45 | self.layer.borderColor = [UIColor grayColor].CGColor; 46 | self.layer.borderWidth = 2; 47 | self.layer.cornerRadius = 4; 48 | } 49 | 50 | - (void)setBody:(GRXComplexDataBody *)body { 51 | self.image.image = body.image; 52 | self.image.grx_visibility = body.image != nil ? GRXVisibilityVisible : GRXVisibilityGone; 53 | 54 | self.title.text = body.title; 55 | self.title.grx_visibility = body.title.length > 0 ? GRXVisibilityVisible : GRXVisibilityGone; 56 | 57 | self.subtitle.text = body.subtitle; 58 | self.subtitle.grx_visibility = body.subtitle.length > 0 ? GRXVisibilityVisible : GRXVisibilityGone; 59 | 60 | if (body.url != nil) { 61 | self.url.attributedText = [[NSAttributedString alloc] initWithString:body.url.absoluteString 62 | attributes:@{NSLinkAttributeName: body.url}]; 63 | } 64 | self.url.grx_visibility = body.url != nil ? GRXVisibilityVisible : GRXVisibilityGone; 65 | } 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexCellCollectionViewTestController.h: -------------------------------------------------------------------------------- 1 | #import "GRXTestViewController.h" 2 | 3 | @interface GRXComplexCellCollectionViewTestController : UICollectionViewController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GRXComplexData.h" 3 | 4 | @interface GRXComplexCollectionViewCell : UICollectionViewCell 5 | 6 | @property (nonatomic) GRXComplexData *data; 7 | 8 | + (CGSize)sizeForData:(GRXComplexData *)data; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | #import "GRXComplexCollectionViewCell.h" 2 | #import "GRXComplexHeaderView.h" 3 | #import "GRXComplexBodyView.h" 4 | #import 5 | #import 6 | 7 | @interface GRXComplexCollectionViewCell () 8 | 9 | @property (nonatomic) GRXLinearLayout *root; 10 | @property (nonatomic) GRXComplexHeaderView *headerView; 11 | @property (nonatomic) GRXLinearLayout *bodyContainer; 12 | 13 | @end 14 | 15 | @implementation GRXComplexCollectionViewCell 16 | 17 | + (NSMutableArray *)bodyPool { 18 | static NSMutableArray *bodyPool = nil; 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | bodyPool = [NSMutableArray array]; 22 | }); 23 | return bodyPool; 24 | } 25 | 26 | + (void)recycleBodies:(NSArray *)bodyViews { 27 | for (GRXComplexBodyView *bodyView in bodyViews) { 28 | [bodyView removeFromSuperview]; 29 | [self.class.bodyPool addObject:bodyView]; 30 | } 31 | } 32 | 33 | + (GRXComplexBodyView *)dequeueBody { 34 | GRXComplexBodyView *bodyView = nil; 35 | if (self.class.bodyPool.count > 0) { 36 | bodyView = [self.class.bodyPool lastObject]; 37 | [self.class.bodyPool removeLastObject]; 38 | } else { 39 | bodyView = [[GRXComplexBodyView alloc] initWithFrame:CGRectZero]; 40 | } 41 | 42 | return bodyView; 43 | } 44 | 45 | - (instancetype)initWithFrame:(CGRect)frame { 46 | self = [super initWithFrame:frame]; 47 | if (self) { 48 | GRXLayoutInflater *inflater = [[GRXLayoutInflater alloc] initWithMainBundleFileNamed:@"complex_cell.grx"]; 49 | self.root = (GRXLinearLayout*) inflater.rootView; 50 | [self.contentView addSubview:self.root]; 51 | self.headerView = (GRXComplexHeaderView *) [inflater.rootView grx_findViewWithIdentifier:@"header"]; 52 | self.bodyContainer = (GRXLinearLayout *) [inflater.rootView grx_findViewWithIdentifier:@"bodyContainer"]; 53 | 54 | self.backgroundColor = [UIColor whiteColor]; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)setData:(GRXComplexData *)data { 60 | _data = data; 61 | self.headerView.header = data.header; 62 | [self.headerView grx_setNeedsLayoutInParent]; 63 | 64 | [self.class recycleBodies:self.bodyContainer.subviews]; 65 | for (GRXComplexDataBody *body in data.bodies) { 66 | GRXComplexBodyView *bodyView = [self.class dequeueBody]; 67 | bodyView.body = body; 68 | bodyView.grx_layoutParams = [[GRXLinearLayoutParams alloc] initWithSize:CGSizeMake(GRXMatchParent, GRXWrapContent)]; 69 | [self.bodyContainer addSubview:bodyView]; 70 | bodyView.grx_layoutParams.margins = UIEdgeInsetsMake(4, 0, 0, 0); 71 | } 72 | self.bodyContainer.grx_visibility = data.bodies.count > 0 ? GRXVisibilityVisible : GRXVisibilityGone; 73 | } 74 | 75 | + (CGSize)sizeForData:(GRXComplexData *)data { 76 | static GRXComplexCollectionViewCell *staticComplexCell = nil; 77 | static dispatch_once_t onceToken; 78 | dispatch_once(&onceToken, ^{ 79 | staticComplexCell = [[GRXComplexCollectionViewCell alloc] initWithFrame:CGRectZero]; 80 | }); 81 | staticComplexCell.data = data; 82 | [staticComplexCell.root layoutIfNeeded]; 83 | CGSize size = staticComplexCell.root.grx_measuredSize; 84 | return size; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexData.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface GRXComplexDataHeader : NSObject 4 | 5 | @property (nonatomic) UIImage *image; 6 | @property (nonatomic) NSString *title; 7 | @property (nonatomic) NSString *message; 8 | @property (nonatomic) NSDate *date; 9 | 10 | @property (nonatomic, readonly) NSString *formattedDate; 11 | 12 | @end 13 | 14 | @interface GRXComplexDataBody : NSObject 15 | 16 | @property (nonatomic) UIImage *image; 17 | @property (nonatomic) NSString *title; 18 | @property (nonatomic) NSString *subtitle; 19 | @property (nonatomic) NSURL *url; 20 | 21 | @end 22 | 23 | @interface GRXComplexData : NSObject 24 | 25 | @property (nonatomic) GRXComplexDataHeader *header; 26 | @property (nonatomic) NSArray *bodies; 27 | 28 | + (NSArray *)generateDataWithCount:(NSUInteger)count; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexData.m: -------------------------------------------------------------------------------- 1 | #import "GRXComplexData.h" 2 | #import "GRXTextGenerator.h" 3 | 4 | @implementation GRXComplexDataHeader 5 | 6 | @synthesize formattedDate = _formattedDate; 7 | 8 | - (NSString *)formattedDate { 9 | if (_formattedDate == nil) { 10 | static NSDateFormatter *formatter; 11 | static dispatch_once_t onceToken; 12 | dispatch_once(&onceToken, ^{ 13 | formatter = [[NSDateFormatter alloc] init]; 14 | formatter.dateFormat = @"dd.MM.yyyy hh:mm:ss"; 15 | }); 16 | _formattedDate = [formatter stringFromDate:_date]; 17 | } 18 | return _formattedDate; 19 | } 20 | 21 | @end 22 | 23 | @implementation GRXComplexDataBody 24 | 25 | 26 | @end 27 | 28 | @implementation GRXComplexData 29 | 30 | + (NSArray *)generateDataWithCount:(NSUInteger)count { 31 | NSMutableArray *datas = [NSMutableArray arrayWithCapacity:count]; 32 | for (NSUInteger i = 0; i < count; ++i) { 33 | GRXComplexData *data = [[GRXComplexData alloc] init]; 34 | 35 | GRXComplexDataHeader *header = [[GRXComplexDataHeader alloc] init]; 36 | header.title = [GRXTextGenerator stringWithMinimumWords:5 maxWords:16]; 37 | NSTimeInterval dateInterval = [NSDate date].timeIntervalSince1970 - arc4random_uniform(10000); 38 | header.date = [NSDate dateWithTimeIntervalSince1970:dateInterval]; 39 | header.image = [self.class randomImageWithProbability:0.9]; 40 | header.message = [GRXTextGenerator stringWithMaxLength:150 emptyProbability:0.7]; 41 | data.header = header; 42 | 43 | NSUInteger numBodies = ABS(arc4random_uniform(4)); 44 | data.bodies = [NSMutableArray arrayWithCapacity:numBodies]; 45 | for (NSUInteger i = 0; i < numBodies; ++i) { 46 | GRXComplexDataBody *body = [[GRXComplexDataBody alloc] init]; 47 | body.title = [GRXTextGenerator stringWithMinimumWords:5 maxWords:16]; 48 | body.subtitle = [GRXTextGenerator stringWithMaxLength:32 emptyProbability:0.3]; 49 | body.image = [self.class randomImageWithProbability:0.6]; 50 | body.url = [self.class randomURL]; 51 | [(NSMutableArray *)data.bodies addObject : body]; 52 | } 53 | [datas addObject:data]; 54 | } 55 | return datas; 56 | } 57 | 58 | + (UIImage *)randomImageWithProbability:(CGFloat)prob { 59 | BOOL hasImage = ((CGFloat)arc4random_uniform(1000) / 1000) < prob; 60 | if (NO == hasImage) { 61 | return nil; 62 | } 63 | 64 | NSString *name = nil; 65 | NSUInteger number = arc4random_uniform(3); 66 | switch (number) { 67 | case 0: 68 | name = @"lab.png"; 69 | break; 70 | case 1: 71 | name = @"green.png"; 72 | break; 73 | case 2: 74 | name = @"power.png"; 75 | break; 76 | } 77 | 78 | if (name != nil) { 79 | return [UIImage imageNamed:name]; 80 | } else { 81 | return nil; 82 | } 83 | } 84 | 85 | + (NSURL *)randomURL { 86 | NSUInteger number = arc4random_uniform(3); 87 | switch (number) { 88 | case 0: 89 | return [NSURL URLWithString:@"http://www.twitter.com/gskbyte"]; 90 | case 1: 91 | return [NSURL URLWithString:@"http://github.com/gskbyte/GranadaLayout"]; 92 | default: 93 | return nil; 94 | } 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexHeaderView.h: -------------------------------------------------------------------------------- 1 | #import "GRXRelativeLayout.h" 2 | #import "GRXComplexData.h" 3 | 4 | @interface GRXComplexHeaderView : GRXRelativeLayout 5 | 6 | @property (nonatomic) GRXComplexDataHeader *header; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /Example/Example/GRXComplexHeaderView.m: -------------------------------------------------------------------------------- 1 | #import "GRXComplexHeaderView.h" 2 | #import 3 | 4 | @interface GRXComplexHeaderView () 5 | 6 | @property (nonatomic) UIImageView *image; 7 | @property (nonatomic) UITextView *title, *message; 8 | @property (nonatomic) UILabel *subtitle; 9 | 10 | @end 11 | 12 | @implementation GRXComplexHeaderView 13 | 14 | - (void)grx_didLoadFromInflater:(GRXLayoutInflater *)inflater { 15 | self.image = (UIImageView*) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 16 | self.image.layer.borderColor = [UIColor darkGrayColor].CGColor; 17 | self.image.layer.borderWidth = 1; 18 | self.image.contentMode = UIViewContentModeScaleAspectFit; 19 | 20 | self.title = (UITextView *) [inflater.rootView grx_findViewWithIdentifier:@"title"]; 21 | self.title.font = [UIFont boldSystemFontOfSize:16]; 22 | 23 | self.subtitle = (UILabel *) [inflater.rootView grx_findViewWithIdentifier:@"subtitle"]; 24 | self.subtitle.numberOfLines = 1000; 25 | self.subtitle.font = [UIFont systemFontOfSize:12]; 26 | self.subtitle.textColor = [UIColor grayColor]; 27 | 28 | self.message = (UITextView *) [inflater.rootView grx_findViewWithIdentifier:@"message"]; 29 | self.message.font = [UIFont systemFontOfSize:14]; 30 | } 31 | 32 | - (void)setHeader:(GRXComplexDataHeader *)header { 33 | self.image.image = header.image; 34 | self.image.grx_visibility = header.image != nil ? GRXVisibilityVisible : GRXVisibilityGone; 35 | 36 | self.title.text = header.title; // we always will have a title 37 | 38 | self.subtitle.text = header.formattedDate; // we will always have a subtitle 39 | 40 | self.message.text = header.message; 41 | self.message.grx_visibility = header.message.length > 0 ? GRXVisibilityVisible : GRXVisibilityGone; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Example/Example/GRXLabelTestViewController.h: -------------------------------------------------------------------------------- 1 | #import "GRXTopLayoutTestViewController.h" 2 | 3 | @interface GRXLabelTestViewController : GRXTopLayoutTestViewController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXLabelTestViewController.m: -------------------------------------------------------------------------------- 1 | #import "GRXLabelTestViewController.h" 2 | #import "GRXLinearLayout.h" 3 | 4 | @implementation GRXLabelTestViewController 5 | 6 | + (NSString *)selectionTitle { 7 | return @"UILabel test"; 8 | } 9 | 10 | + (NSString *)selectionDetail { 11 | return @"Tests height calculation for UILabel"; 12 | } 13 | 14 | - (void)viewDidLoad { 15 | [super viewDidLoad]; 16 | [self configureRandomizeButton]; 17 | [self randomizeText]; 18 | } 19 | 20 | - (GRXLinearLayout *)topLinearLayout { 21 | return (GRXLinearLayout *)self.topLayout; 22 | } 23 | 24 | - (void)configureRandomizeButton { 25 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Random" 26 | style:UIBarButtonItemStyleDone 27 | target:self 28 | action:@selector(randomizeText)]; 29 | } 30 | 31 | static NSString *ipsum = @"" 32 | "Lorem fistrum no puedor mamaar quietooor a gramenawer. Condemor quietooor por la gloria de mi madre diodenoo diodeno no puedor a wan pecador pupita ese que llega. Ahorarr no te digo trigo por no llamarte Rodrigor por la gloria de mi madre a gramenawer benemeritaar no te digo trigo por no llamarte Rodrigor al ataquerl ahorarr quietooor.\n" 33 | "Al ataquerl amatomaa me cago en tus muelas a gramenawer a wan quietooor hasta luego Lucas te voy a borrar el cerito tiene musho peligro. Amatomaa quietooor llevame al sircoo al ataquerl caballo blanco caballo negroorl."; 34 | 35 | - (void)randomizeText { 36 | for (UILabel *label in self.topLayout.subviews) { 37 | NSUInteger length = arc4random_uniform((uint32_t)ipsum.length); 38 | 39 | NSString *displayedText = [ipsum substringWithRange:NSMakeRange(0, length)]; 40 | NSMutableString *text = [NSMutableString stringWithString:@""]; 41 | NSUInteger numLines = arc4random_uniform(15); 42 | label.numberOfLines = numLines; 43 | [text appendFormat:@" numLines(%lu) >", (unsigned long)numLines]; 44 | CGFloat fontSize = 11 + arc4random_uniform(70) / 10; 45 | label.font = [UIFont systemFontOfSize:fontSize]; 46 | [text appendFormat:@" fontSize(%.0f) >", fontSize]; 47 | 48 | [text appendFormat:@"%@", displayedText]; 49 | label.text = text; 50 | 51 | [label grx_setNeedsLayoutInParent]; 52 | } 53 | } 54 | 55 | - (GRXLayout *)initializeTopLayout { 56 | GRXLinearLayout *top = [[GRXLinearLayout alloc] initWithFrame:CGRectZero]; 57 | top.direction = GRXLinearLayoutDirectionVertical; 58 | return top; 59 | } 60 | 61 | - (void)createViews { 62 | [super createViews]; 63 | 64 | UILabel *label0 = [[UILabel alloc] initWithDefaultParamsInLayout:self.topLayout]; 65 | label0.numberOfLines = 1; 66 | label0.grx_linearLayoutParams.margins = UIEdgeInsetsMake(4, 4, 4, 4); 67 | label0.grx_linearLayoutParams.width = GRXMatchParent; 68 | label0.backgroundColor = [UIColor greenColor]; 69 | 70 | UILabel *label1 = [[UILabel alloc] initWithDefaultParamsInLayout:self.topLayout]; 71 | label1.numberOfLines = 4; 72 | label1.grx_linearLayoutParams.margins = UIEdgeInsetsMake(4, 4, 4, 4); 73 | label1.grx_linearLayoutParams.width = GRXMatchParent; 74 | label1.backgroundColor = [UIColor redColor]; 75 | 76 | 77 | UILabel *label2 = [[UILabel alloc] initWithDefaultParamsInLayout:self.topLayout]; 78 | label2.grx_linearLayoutParams.margins = UIEdgeInsetsMake(4, 4, 4, 4); 79 | label2.grx_linearLayoutParams.width = GRXMatchParent; 80 | label2.backgroundColor = [UIColor yellowColor]; 81 | } 82 | @end 83 | -------------------------------------------------------------------------------- /Example/Example/GRXLayoutInflaterTestController1.h: -------------------------------------------------------------------------------- 1 | #import "GRXBaseInflationTestController.h" 2 | 3 | @interface GRXLayoutInflaterTestController1 : GRXBaseInflationTestController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXLayoutInflaterTestController1.m: -------------------------------------------------------------------------------- 1 | #import "GRXLayoutInflaterTestController1.h" 2 | 3 | @interface GRXLayoutInflaterTestController1 () 4 | 5 | @end 6 | 7 | @implementation GRXLayoutInflaterTestController1 8 | 9 | + (NSString *)selectionTitle { 10 | return @"Inflation test 1"; 11 | } 12 | 13 | + (NSString *)selectionDetail { 14 | return @"Inflates a layout from a file"; 15 | } 16 | 17 | - (NSString *)layoutFileNameInBundle { 18 | return @"relative_test_1.grx"; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Example/Example/GRXLinearLayoutInflaterAnimationController.h: -------------------------------------------------------------------------------- 1 | #import "GRXBaseInflationTestController.h" 2 | 3 | @interface GRXLinearLayoutInflaterAnimationController : GRXBaseInflationTestController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXLinearLayoutInflaterAnimationController.m: -------------------------------------------------------------------------------- 1 | #import "GRXLinearLayoutInflaterAnimationController.h" 2 | #import "GRXLinearLayout.h" 3 | 4 | @implementation GRXLinearLayoutInflaterAnimationController 5 | 6 | + (NSString *)selectionTitle { 7 | return @"Linear inflation"; 8 | } 9 | 10 | + (NSString *)selectionDetail { 11 | return @"Features an animation on direction change"; 12 | } 13 | 14 | - (NSString *)layoutFileNameInBundle { 15 | return @"linear_test_1.grx"; 16 | } 17 | 18 | - (void)createViews { 19 | UIImageView *imageView = (UIImageView *) [self.layoutInflater.rootView grx_findViewWithIdentifier:@"image"]; 20 | imageView.image = [UIImage imageNamed:@"power.png"]; 21 | } 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | 26 | UIBarButtonItem *directionButton = [[UIBarButtonItem alloc] initWithTitle:@"change" 27 | style:UIBarButtonItemStyleDone 28 | target:self 29 | action:@selector(switchDirection)]; 30 | self.navigationItem.rightBarButtonItem = directionButton; 31 | } 32 | 33 | - (void)switchDirection { 34 | GRXLinearLayout *linear = (GRXLinearLayout *)self.topLayout; 35 | if (linear.direction == GRXLinearLayoutDirectionVertical) { 36 | linear.direction = GRXLinearLayoutDirectionHorizontal; 37 | } else { 38 | linear.direction = GRXLinearLayoutDirectionVertical; 39 | } 40 | 41 | [UIView animateWithDuration:1 animations:^{ 42 | [linear layoutIfNeeded]; 43 | }]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Example/Example/GRXLinearLayoutVerticalTestController.h: -------------------------------------------------------------------------------- 1 | #import "GRXBaseInflationTestController.h" 2 | 3 | @interface GRXLinearLayoutVerticalTestController : GRXBaseInflationTestController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXLinearLayoutVerticalTestController.m: -------------------------------------------------------------------------------- 1 | #import "GRXLinearLayoutVerticalTestController.h" 2 | #import "GRXLinearLayout.h" 3 | 4 | @implementation GRXLinearLayoutVerticalTestController 5 | 6 | + (NSString *)selectionTitle { 7 | return @"LinearLayout vertical test"; 8 | } 9 | 10 | + (NSString *)selectionDetail { 11 | return @"Tests a vertical layout without weights"; 12 | } 13 | 14 | - (NSString *)layoutFileNameInBundle { 15 | return @"linear_test_1.grx"; 16 | } 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | [self configureDirectionButton]; 21 | } 22 | 23 | - (GRXLinearLayout *)topLinearLayout { 24 | return (GRXLinearLayout *)self.topLayout; 25 | } 26 | 27 | - (void)configureDirectionButton { 28 | NSString *title; 29 | if (self.topLinearLayout.direction == GRXLinearLayoutDirectionVertical) { 30 | title = @"Set horizontal"; 31 | } else { 32 | title = @"Set vertical"; 33 | } 34 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:title 35 | style:UIBarButtonItemStyleDone 36 | target:self 37 | action:@selector(changeLayoutDirection)]; 38 | } 39 | 40 | - (void)changeLayoutDirection { 41 | if (self.topLinearLayout.direction == GRXLinearLayoutDirectionVertical) { 42 | self.topLinearLayout.direction = GRXLinearLayoutDirectionHorizontal; 43 | } else { 44 | self.topLinearLayout.direction = GRXLinearLayoutDirectionVertical; 45 | } 46 | [self.topLinearLayout layoutIfNeeded]; 47 | [self configureDirectionButton]; 48 | } 49 | 50 | - (void)createViews { 51 | UIImageView *iv = (UIImageView *) [self.layoutInflater.rootView grx_findViewWithIdentifier:@"image"]; 52 | iv.image = [UIImage imageNamed:@"lab.png"]; 53 | } 54 | 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Example/Example/GRXLoadMoreCell.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface GRXLoadMoreCell : UICollectionViewCell 4 | 5 | + (CGSize)cellSizeForCollectionView:(UICollectionView *)collectionView; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Example/Example/GRXLoadMoreCell.m: -------------------------------------------------------------------------------- 1 | #import "GRXLoadMoreCell.h" 2 | 3 | @interface GRXLoadMoreCell () 4 | 5 | @property (nonatomic) UIActivityIndicatorView *refresh; 6 | 7 | @end 8 | 9 | @implementation GRXLoadMoreCell 10 | 11 | - (instancetype)initWithFrame:(CGRect)frame { 12 | self = [super initWithFrame:frame]; 13 | if (self) { 14 | self.refresh = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]; 15 | [self.contentView addSubview:self.refresh]; 16 | [self.refresh startAnimating]; 17 | } 18 | return self; 19 | } 20 | 21 | - (void)layoutSubviews { 22 | [super layoutSubviews]; 23 | CGRect ownRect = self.contentView.frame; 24 | CGRect refreshRect = self.refresh.frame; 25 | 26 | refreshRect.origin.x = (ownRect.size.width - refreshRect.size.width) / 2; 27 | refreshRect.origin.y = (ownRect.size.height - refreshRect.size.height) / 2; 28 | 29 | self.refresh.frame = refreshRect; 30 | } 31 | 32 | + (CGSize)cellSizeForCollectionView:(UICollectionView *)collectionView { 33 | return CGSizeMake(collectionView.frame.size.width * 0.9, 80); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Example/Example/GRXRelativeLayoutInflaterAnimationController.h: -------------------------------------------------------------------------------- 1 | #import "GRXBaseInflationTestController.h" 2 | 3 | @interface GRXRelativeLayoutInflaterAnimationController : GRXBaseInflationTestController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXRelativeLayoutInflaterAnimationController.m: -------------------------------------------------------------------------------- 1 | #import "GRXRelativeLayoutInflaterAnimationController.h" 2 | #import 3 | #import 4 | 5 | #import "GRXTextGenerator.h" 6 | 7 | @interface GRXRelativeLayoutInflaterAnimationController () 8 | 9 | @property (nonatomic) UIImageView *imageView; 10 | @property (nonatomic) GRXTextView *titleView; 11 | @property (nonatomic) UILabel *subtitleView; 12 | 13 | @end 14 | 15 | @implementation GRXRelativeLayoutInflaterAnimationController 16 | 17 | + (NSString *)selectionTitle { 18 | return @"Relative inflation"; 19 | } 20 | 21 | + (NSString *)selectionDetail { 22 | return @"Features an animation on attribute change"; 23 | } 24 | 25 | - (NSString *)layoutFileNameInBundle { 26 | return @"relative_test_2.grx"; 27 | } 28 | 29 | - (void)createViews { 30 | self.imageView = (UIImageView *) [self.layoutInflater.rootView grx_findViewWithIdentifier:@"image"]; 31 | self.imageView.image = [UIImage imageNamed:@"lab.png"]; 32 | 33 | self.titleView = (GRXTextView *) [self.layoutInflater.rootView grx_findViewWithIdentifier:@"title"]; 34 | self.titleView.font = [UIFont systemFontOfSize:14]; 35 | 36 | self.subtitleView = (UILabel *) [self.layoutInflater.rootView grx_findViewWithIdentifier:@"subtitle"]; 37 | self.subtitleView.numberOfLines = 10; 38 | self.subtitleView.font = [UIFont systemFontOfSize:12]; 39 | [self generateText]; 40 | } 41 | 42 | - (void)viewDidLoad { 43 | [super viewDidLoad]; 44 | 45 | UIBarButtonItem *directionButton = [[UIBarButtonItem alloc] initWithTitle:@"move image" 46 | style:UIBarButtonItemStyleDone 47 | target:self 48 | action:@selector(moveImage)]; 49 | self.navigationItem.rightBarButtonItem = directionButton; 50 | } 51 | 52 | - (void)moveImage { 53 | GRXRelativeLayout *relative = (GRXRelativeLayout *)self.topLayout; 54 | GRXRelativeLayoutParams *imageParams = self.imageView.grx_relativeLayoutParams; 55 | UIEdgeInsets imageMargins = imageParams.margins; 56 | GRXRelativeLayoutParams *titleParams = self.titleView.grx_relativeLayoutParams; 57 | 58 | if ([imageParams hasParentRule:GRXRelativeLayoutParentRuleAlignLeft]) { 59 | imageMargins.right = 0; 60 | imageMargins.left = 8; 61 | 62 | [imageParams setParentRule:GRXRelativeLayoutParentRuleAlignLeft active:NO]; 63 | [imageParams setParentRule:GRXRelativeLayoutParentRuleAlignRight active:YES]; 64 | [imageParams setParentRule:GRXRelativeLayoutParentRuleCenterVertical active:YES]; 65 | [imageParams setParentRule:GRXRelativeLayoutParentRuleAlignTop active:NO]; 66 | 67 | [titleParams setRule:GRXRelativeLayoutRuleRightOf forView:nil]; 68 | [titleParams setRule:GRXRelativeLayoutRuleLeftOf forView:self.imageView]; 69 | } else { 70 | imageMargins.right = 8; 71 | imageMargins.left = 0; 72 | 73 | [imageParams setParentRule:GRXRelativeLayoutParentRuleAlignLeft active:YES]; 74 | [imageParams setParentRule:GRXRelativeLayoutParentRuleAlignRight active:NO]; 75 | [imageParams setParentRule:GRXRelativeLayoutParentRuleCenterVertical active:NO]; 76 | [imageParams setParentRule:GRXRelativeLayoutParentRuleAlignTop active:YES]; 77 | 78 | [titleParams setRule:GRXRelativeLayoutRuleRightOf forView:self.imageView]; 79 | [titleParams setRule:GRXRelativeLayoutRuleLeftOf forView:nil]; 80 | } 81 | imageParams.margins = imageMargins; 82 | [self generateText]; 83 | 84 | [UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 85 | [relative layoutIfNeeded]; 86 | } completion:^(BOOL finished) { 87 | }]; 88 | } 89 | 90 | - (void)generateText { 91 | self.titleView.text = [GRXTextGenerator stringWithMinimumWords:8 maxWords:15]; 92 | self.subtitleView.text = [GRXTextGenerator stringWithMinimumWords:20 maxWords:40]; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /Example/Example/GRXRelativeLayoutTestController.h: -------------------------------------------------------------------------------- 1 | #import "GRXTopLayoutTestViewController.h" 2 | 3 | @interface GRXRelativeLayoutTestController : GRXTopLayoutTestViewController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXRelativeLayoutTestController.m: -------------------------------------------------------------------------------- 1 | #import "GRXRelativeLayoutTestController.h" 2 | #import "GRXRelativeLayout.h" 3 | 4 | @implementation GRXRelativeLayoutTestController 5 | 6 | + (NSString *)selectionTitle { 7 | return @"RelativeLayout test 1"; 8 | } 9 | 10 | + (NSString *)selectionDetail { 11 | return @""; 12 | } 13 | 14 | - (GRXLayout *)initializeTopLayout { 15 | GRXLayout *top = [[GRXRelativeLayout alloc] initWithFrame:CGRectZero]; 16 | return top; 17 | } 18 | 19 | - (GRXRelativeLayout *)topRelativeLayout { 20 | return (GRXRelativeLayout *)self.topLayout; 21 | } 22 | 23 | - (void)createViews { 24 | UIView *view1 = [[UIView alloc] initWithFrame:CGRectZero]; 25 | view1.backgroundColor = [UIColor blueColor]; 26 | view1.grx_identifier = @"view1"; 27 | GRXRelativeLayoutParams *params1 = [[GRXRelativeLayoutParams alloc] initWithSize:CGSizeMake(50, 50)]; 28 | [params1 setParentRule:GRXRelativeLayoutParentRuleAlignTop]; 29 | [params1 setParentRule:GRXRelativeLayoutParentRuleAlignRight]; 30 | view1.grx_layoutParams = params1; 31 | [self.topLayout addSubview:view1]; 32 | 33 | UIView *view2 = [[UIView alloc] initWithFrame:CGRectZero]; 34 | view2.backgroundColor = [UIColor greenColor]; 35 | view2.grx_identifier = @"view2"; 36 | GRXRelativeLayoutParams *params2 = [[GRXRelativeLayoutParams alloc] initWithSize:CGSizeMake(70, 50)]; 37 | [params2 setRule:GRXRelativeLayoutRuleBelow forView:view1]; 38 | [params2 setRule:GRXRelativeLayoutRuleLeftOf forView:view1]; 39 | view2.grx_layoutParams = params2; 40 | [self.topLayout addSubview:view2 ]; 41 | 42 | UIView *view3 = [[UIView alloc] initWithFrame:CGRectZero]; 43 | view3.backgroundColor = [UIColor redColor]; 44 | view3.grx_identifier = @"view3"; 45 | GRXRelativeLayoutParams *params3 = [[GRXRelativeLayoutParams alloc] initWithSize:CGSizeMake(50, 50)]; 46 | [params3 setParentRule:GRXRelativeLayoutParentRuleCenterHorizontal]; 47 | [params3 setRule:GRXRelativeLayoutRuleBelow forView:view2]; 48 | view3.grx_layoutParams = params3; 49 | [self.topLayout addSubview:view3]; 50 | } 51 | 52 | 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Example/Example/GRXSelectionViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | // set to a controller class name to push it immediately 4 | // using name avoids import 5 | static NSString *FastStartViewControllerClassName = @""; 6 | 7 | @interface GRXSelectionViewController : UITableViewController 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /Example/Example/GRXSelectionViewController.m: -------------------------------------------------------------------------------- 1 | #import "GRXSelectionViewController.h" 2 | #import "GRXTestViewController.h" 3 | 4 | #import 5 | 6 | @interface GRXSelectionViewController () 7 | 8 | @property (nonatomic) NSArray *controllerClasses; 9 | 10 | @end 11 | 12 | @implementation GRXSelectionViewController 13 | 14 | 15 | + (NSMutableArray *)classesImplementingProtocol:(Protocol *)protocol { 16 | int numClasses = objc_getClassList(NULL, 0); 17 | Class *classes = NULL; 18 | 19 | classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); 20 | numClasses = objc_getClassList(classes, numClasses); 21 | 22 | NSMutableArray *result = [NSMutableArray array]; 23 | for (NSInteger i = 0; i < numClasses; ++i) { 24 | Class clazz = classes[i]; 25 | 26 | if ([NSStringFromClass(clazz) rangeOfString:@"GRX"].location != NSNotFound) { 27 | NSLog(@""); 28 | } 29 | 30 | Class testProtocol = clazz; 31 | do { 32 | if ( class_conformsToProtocol(testProtocol, protocol) ) { 33 | break; 34 | } 35 | testProtocol = class_getSuperclass(testProtocol); 36 | } while (testProtocol != nil); 37 | 38 | if ( testProtocol != nil ) { 39 | [result addObject:clazz]; 40 | } 41 | } 42 | 43 | free(classes); 44 | 45 | return result; 46 | } 47 | 48 | + (NSMutableArray *)subclassesOfClass:(Class)parentClass { 49 | int numClasses = objc_getClassList(NULL, 0); 50 | Class *classes = NULL; 51 | 52 | classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); 53 | numClasses = objc_getClassList(classes, numClasses); 54 | 55 | NSMutableArray *result = [NSMutableArray array]; 56 | for (NSInteger i = 0; i < numClasses; ++i) { 57 | Class clazz = classes[i]; 58 | 59 | do { 60 | clazz = class_getSuperclass(clazz); 61 | } while (clazz && clazz != parentClass); 62 | 63 | if (clazz != nil) { 64 | [result addObject:clazz]; 65 | } 66 | } 67 | 68 | free(classes); 69 | 70 | return result; 71 | } 72 | 73 | - (void)viewDidLoad { 74 | [super viewDidLoad]; 75 | 76 | self.navigationItem.title = @"Test list"; 77 | self.view.backgroundColor = [UIColor whiteColor]; 78 | 79 | NSMutableArray *allTestControllers = [self.class classesImplementingProtocol:@protocol(GRXTestViewControllerProtocol)]; 80 | for (NSInteger i = allTestControllers.count - 1; i >= 0; --i) { 81 | Class c = allTestControllers[i]; 82 | NSString *title = [c performSelector:@selector(selectionTitle)]; 83 | if (title == nil) { 84 | [allTestControllers removeObjectAtIndex:i]; 85 | } 86 | } 87 | [allTestControllers sortUsingComparator:^NSComparisonResult (Class c1, Class c2) { 88 | return [[c1 selectionTitle] compare:[c2 selectionTitle]]; 89 | }]; 90 | 91 | self.controllerClasses = allTestControllers; 92 | 93 | if (FastStartViewControllerClassName.length > 0) { 94 | Class clazz = NSClassFromString(FastStartViewControllerClassName); 95 | if (clazz != nil) { 96 | [self.navigationController pushViewController:[[clazz alloc] init] 97 | animated:NO]; 98 | } 99 | } 100 | } 101 | 102 | 103 | #pragma mark - Table view data source 104 | 105 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 106 | return self.controllerClasses.count; 107 | } 108 | 109 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 110 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 111 | if (cell == nil) { 112 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle 113 | reuseIdentifier:@"cell"]; 114 | cell.textLabel.font = [UIFont systemFontOfSize:14]; 115 | cell.textLabel.numberOfLines = 2; 116 | cell.detailTextLabel.font = [UIFont systemFontOfSize:12]; 117 | cell.detailTextLabel.textColor = [UIColor grayColor]; 118 | } 119 | 120 | Class clazz = self.controllerClasses[indexPath.row]; 121 | cell.textLabel.text = [clazz selectionTitle]; 122 | cell.detailTextLabel.text = [clazz selectionDetail]; 123 | 124 | return cell; 125 | } 126 | 127 | - (void) tableView:(UITableView *)tableView 128 | didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 129 | Class clazz = self.controllerClasses[indexPath.row]; 130 | [self.navigationController pushViewController:[[clazz alloc] init] 131 | animated:YES]; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /Example/Example/GRXTestAppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface GRXTestAppDelegate : UIResponder 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXTestAppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "GRXTestAppDelegate.h" 2 | 3 | @implementation GRXTestAppDelegate 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXTestViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol GRXTestViewControllerProtocol 4 | 5 | + (NSString *)selectionTitle; // return nil to not appear in the list 6 | 7 | @optional 8 | + (NSString *)selectionDetail; 9 | 10 | @end -------------------------------------------------------------------------------- /Example/Example/GRXTextGenerator.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface GRXTextGenerator : NSObject 4 | 5 | + (NSString *)stringWithMinimumLength:(NSUInteger)minLength 6 | maxLength:(NSUInteger)maxLength; 7 | + (NSString *)stringWithMinimumWords:(NSUInteger)minWords 8 | maxWords:(NSUInteger)maxWords; 9 | + (NSString *)stringWithMaxLength:(NSUInteger)minLength 10 | emptyProbability:(CGFloat)emptyProbability; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Example/Example/GRXTextGenerator.m: -------------------------------------------------------------------------------- 1 | #import "GRXTextGenerator.h" 2 | 3 | static NSString *GRXRandomTextBase = @"Lorem fistrum a wan apetecan no puedor. Sexuarl la caidita está la cosa muy malar te voy a borrar el cerito mamaar. Se calle ustée qué dise usteer ahorarr fistro ese que llega llevame al sircoo tiene musho peligro fistro pecador benemeritaar. Hasta luego Lucas ese que llega apetecan te voy a borrar el cerito te va a hasé pupitaa está la cosa muy malar jarl caballo blanco caballo negroorl ese hombree a wan. Benemeritaar no puedor jarl llevame al sircoo diodenoo te va a hasé pupitaa te voy a borrar el cerito ese hombree. Pupita sexuarl qué dise usteer al ataquerl la caidita está la cosa muy malar. Qué dise usteer ahorarr va usté muy cargadoo diodenoo mamaar la caidita va usté muy cargadoo va usté muy cargadoo papaar papaar. Ese pedazo de sexuarl no puedor no puedor. Diodeno tiene musho peligro te va a hasé pupitaa benemeritaar va usté muy cargadoo diodenoo amatomaa apetecan está la cosa muy malar te va a hasé pupitaa. Pecador se calle ustée te voy a borrar el cerito diodeno ahorarr diodeno jarl diodenoo te voy a borrar el cerito a gramenawer ese que llega. Está la cosa muy malar torpedo condemor torpedo a wan va usté muy cargadoo va usté muy cargadoo al ataquerl ese hombree ese hombree fistro. Me cago en tus muelas no te digo trigo por no llamarte Rodrigor llevame al sircoo diodenoo torpedo."; 4 | 5 | @implementation GRXTextGenerator 6 | 7 | + (NSArray *)words { 8 | static NSArray *GRXRandomTextGeneratorWords; 9 | static dispatch_once_t onceToken; 10 | dispatch_once(&onceToken, ^{ 11 | GRXRandomTextGeneratorWords = [GRXRandomTextBase componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 12 | }); 13 | return GRXRandomTextGeneratorWords; 14 | } 15 | 16 | + (NSString *)stringWithMinimumLength:(NSUInteger)minLength 17 | maxLength:(NSUInteger)maxLength { 18 | NSUInteger length = minLength + arc4random_uniform((u_int32_t)(maxLength - minLength)); 19 | NSUInteger begin = arc4random_uniform((u_int32_t)(GRXRandomTextBase.length - length)); 20 | return [GRXRandomTextBase substringWithRange:NSMakeRange(begin, length)]; 21 | } 22 | 23 | + (NSString *)stringWithMinimumWords:(NSUInteger)minWords 24 | maxWords:(NSUInteger)maxWords { 25 | if (minWords > self.class.words.count) { 26 | minWords = self.class.words.count - 1; 27 | } 28 | 29 | if (maxWords > self.class.words.count) { 30 | maxWords = self.class.words.count - 1; 31 | } 32 | 33 | NSUInteger words = minWords + arc4random_uniform((u_int32_t)(maxWords - minWords)); 34 | NSUInteger begin = arc4random_uniform((u_int32_t)([self.class words].count - words)); 35 | 36 | return [[self.class.words subarrayWithRange:NSMakeRange(begin, words)] componentsJoinedByString:@" "]; 37 | } 38 | 39 | + (NSString *)stringWithMaxLength:(NSUInteger)maxLength 40 | emptyProbability:(CGFloat)emptyProbability { 41 | NSUInteger random = arc4random_uniform(1000); 42 | if (random < emptyProbability * 1000) { 43 | return @""; 44 | } 45 | return [self.class stringWithMinimumLength:0 maxLength:maxLength]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Example/Example/GRXTextViewTestViewController.h: -------------------------------------------------------------------------------- 1 | #import "GRXTopLayoutTestViewController.h" 2 | 3 | @interface GRXTextViewTestViewController : GRXTopLayoutTestViewController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXTextViewTestViewController.m: -------------------------------------------------------------------------------- 1 | #import "GRXTextViewTestViewController.h" 2 | #import "GRXLinearLayout.h" 3 | 4 | @interface GRXTextViewTestViewController () 5 | 6 | @end 7 | 8 | @implementation GRXTextViewTestViewController 9 | 10 | + (NSString *)selectionTitle { 11 | return @"UITextView height test"; 12 | } 13 | 14 | + (NSString *)selectionDetail { 15 | return @"Tests height calculation for UITextView"; 16 | } 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | [self configureRandomizeButton]; 21 | [self randomizeText]; 22 | } 23 | 24 | - (GRXLinearLayout *)topLinearLayout { 25 | return (GRXLinearLayout *)self.topLayout; 26 | } 27 | 28 | - (void)configureRandomizeButton { 29 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Random" 30 | style:UIBarButtonItemStyleDone 31 | target:self 32 | action:@selector(randomizeText)]; 33 | } 34 | 35 | static NSString *ipsum = @"" 36 | "Lorem fistrum no puedor mamaar quietooor a gramenawer. Condemor quietooor por la gloria de mi madre diodenoo diodeno no puedor a wan pecador pupita ese que llega. Ahorarr no te digo trigo por no llamarte Rodrigor por la gloria de mi madre a gramenawer benemeritaar no te digo trigo por no llamarte Rodrigor al ataquerl ahorarr quietooor.\n" 37 | "Al ataquerl amatomaa me cago en tus muelas a gramenawer a wan quietooor hasta luego Lucas te voy a borrar el cerito tiene musho peligro. Amatomaa quietooor llevame al sircoo al ataquerl caballo blanco caballo negroorl."; 38 | 39 | // just set random attributes in random positions to the generated text 40 | - (void)randomizeText { 41 | for (UITextView *tv in self.topLayout.subviews) { 42 | tv.editable = NO; 43 | 44 | NSUInteger length = arc4random_uniform((uint32_t)ipsum.length); 45 | 46 | NSString *text = [ipsum substringWithRange:NSMakeRange(0, length)]; 47 | 48 | BOOL attributed = arc4random_uniform(3); 49 | if (attributed) { 50 | NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:text]; 51 | NSUInteger numAttr = arc4random_uniform(10); 52 | for (NSUInteger i = 0; i < numAttr; ++i) { 53 | NSUInteger origin = arc4random_uniform((uint32_t)text.length / 2); 54 | NSUInteger length = 1 + arc4random_uniform((uint32_t)text.length / 3); 55 | NSRange range = NSMakeRange(origin, length); 56 | 57 | if (arc4random_uniform(3)) { 58 | CGFloat boldSize = 11 + arc4random_uniform(70) / 10; 59 | [attr addAttribute:NSFontAttributeName 60 | value:[UIFont boldSystemFontOfSize:boldSize] 61 | range:range]; 62 | } 63 | 64 | if (arc4random_uniform(3)) { 65 | [attr addAttribute:NSLinkAttributeName 66 | value:@"https://github.com/gskbyte" 67 | range:range]; 68 | } 69 | 70 | if (arc4random_uniform(3)) { 71 | [attr addAttribute:NSForegroundColorAttributeName 72 | value:UIColor.grayColor 73 | range:range]; 74 | } 75 | } 76 | tv.attributedText = attr; 77 | } else { 78 | tv.text = text; 79 | } 80 | [tv grx_setNeedsLayoutInParent]; 81 | } 82 | } 83 | 84 | - (GRXLayout *)initializeTopLayout { 85 | GRXLinearLayout *top = [[GRXLinearLayout alloc] initWithFrame:CGRectZero]; 86 | top.direction = GRXLinearLayoutDirectionVertical; 87 | return top; 88 | } 89 | 90 | - (void)createViews { 91 | [super createViews]; 92 | 93 | UITextView *t0 = [[UITextView alloc] initWithDefaultParamsInLayout:self.topLayout]; 94 | t0.grx_linearLayoutParams.margins = UIEdgeInsetsMake(4, 4, 4, 4); 95 | t0.grx_linearLayoutParams.width = GRXMatchParent; 96 | t0.backgroundColor = [UIColor greenColor]; 97 | 98 | UITextView *t1 = [[UITextView alloc] initWithDefaultParamsInLayout:self.topLayout]; 99 | t1.grx_linearLayoutParams.margins = UIEdgeInsetsMake(4, 4, 4, 4); 100 | t1.grx_linearLayoutParams.width = GRXMatchParent; 101 | t1.backgroundColor = [UIColor redColor]; 102 | 103 | 104 | UITextView *t2 = [[UITextView alloc] initWithDefaultParamsInLayout:self.topLayout]; 105 | t2.grx_linearLayoutParams.margins = UIEdgeInsetsMake(4, 4, 4, 4); 106 | t2.grx_linearLayoutParams.width = GRXMatchParent; 107 | t2.backgroundColor = [UIColor yellowColor]; 108 | } 109 | @end 110 | -------------------------------------------------------------------------------- /Example/Example/GRXTopLayoutTestViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GRXLayout.h" 3 | #import "GRXTestViewController.h" 4 | 5 | @interface GRXTopLayoutTestViewController : UIViewController 6 | 7 | @property (nonatomic, readonly) GRXLayout *topLayout; 8 | 9 | - (GRXLayout *)initializeTopLayout; 10 | - (void)createViews; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Example/Example/GRXTopLayoutTestViewController.m: -------------------------------------------------------------------------------- 1 | #import "GRXTopLayoutTestViewController.h" 2 | #import "GRXLayout.h" 3 | 4 | @implementation GRXTopLayoutTestViewController 5 | 6 | + (NSString *)selectionTitle { 7 | return nil; 8 | } 9 | 10 | - (void)viewDidLoad { 11 | [super viewDidLoad]; 12 | 13 | self.automaticallyAdjustsScrollViewInsets = NO; 14 | self.navigationItem.title = [self.class selectionTitle]; 15 | self.view.backgroundColor = [UIColor lightGrayColor]; 16 | 17 | _topLayout = [self initializeTopLayout]; 18 | if (_topLayout != nil) { 19 | _topLayout.clipsToBounds = YES; 20 | [self.view addSubview:_topLayout]; 21 | } 22 | [self createViews]; 23 | } 24 | 25 | - (GRXLayout *)initializeTopLayout { 26 | NSAssert(NO, @"implement -initializeTopLayout"); 27 | return nil; 28 | } 29 | 30 | - (void)createViews { 31 | } 32 | 33 | - (void)viewWillLayoutSubviews { 34 | [super viewWillLayoutSubviews]; 35 | self.topLayout.top = 64; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Example/Example/GRXWeightedLinearLayoutTestController.h: -------------------------------------------------------------------------------- 1 | #import "GRXLinearLayoutVerticalTestController.h" 2 | 3 | @interface GRXWeightedLinearLayoutTestController : GRXLinearLayoutVerticalTestController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Example/GRXWeightedLinearLayoutTestController.m: -------------------------------------------------------------------------------- 1 | #import "GRXWeightedLinearLayoutTestController.h" 2 | #import "GRXLinearLayout.h" 3 | 4 | @implementation GRXWeightedLinearLayoutTestController 5 | 6 | + (NSString *)selectionTitle { 7 | return @"weighted linear test 1"; 8 | } 9 | 10 | + (NSString *)selectionDetail { 11 | return @"Includes UIImageView and UISwitch"; 12 | } 13 | 14 | - (GRXLayout *)initializeTopLayout { 15 | GRXLinearLayout *top = [[GRXLinearLayout alloc] initWithFrame:CGRectZero]; 16 | top.direction = GRXLinearLayoutDirectionVertical; 17 | top.weightSum = 1; 18 | return top; 19 | } 20 | 21 | - (void)createViews { 22 | UIView *view0 = [[UIView alloc] initWithDefaultParamsInLayout:self.topLayout]; 23 | view0.backgroundColor = [UIColor blueColor]; 24 | view0.grx_minSize = CGSizeMake(100, 50); 25 | view0.grx_linearLayoutParams.weight = 0.20; 26 | view0.grx_linearLayoutParams.gravity = GRXLinearLayoutGravityEnd; 27 | view0.grx_linearLayoutParams.margins = UIEdgeInsetsMake(0, 0, 10, 0); 28 | 29 | UIImageView *imageView = [[UIImageView alloc] initWithDefaultParamsInLayout:self.topLayout]; 30 | imageView.image = [UIImage imageNamed:@"lab"]; 31 | imageView.grx_linearLayoutParams.gravity = GRXLinearLayoutGravityEnd; 32 | imageView.grx_linearLayoutParams.weight = 0.30; 33 | 34 | UIView *view1 = [[UIView alloc] initWithDefaultParamsInLayout:self.topLayout]; 35 | view1.backgroundColor = [UIColor greenColor]; 36 | view1.grx_minSize = CGSizeMake(30, 80); 37 | view1.grx_linearLayoutParams.weight = 0.4; 38 | 39 | UISwitch *sw = [[UISwitch alloc] initWithDefaultParamsInLayout:self.topLayout]; 40 | sw.backgroundColor = UIColor.yellowColor; 41 | sw.grx_minSize = CGSizeMake(200, 200); 42 | sw.grx_linearLayoutParams.margins = UIEdgeInsetsMake(5, 10, 30, 20); 43 | sw.grx_linearLayoutParams.gravity = GRXLinearLayoutGravityEnd; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "57x57", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "57x57", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "3x" 37 | } 38 | ], 39 | "info" : { 40 | "version" : 1, 41 | "author" : "xcode" 42 | } 43 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "extent" : "full-screen", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "filename" : "Default-568h@2x.png", 15 | "minimum-system-version" : "7.0", 16 | "orientation" : "portrait", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "portrait", 27 | "idiom" : "iphone", 28 | "extent" : "full-screen", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "orientation" : "portrait", 33 | "idiom" : "iphone", 34 | "extent" : "full-screen", 35 | "filename" : "Default-568h@2x-1.png", 36 | "subtype" : "retina4", 37 | "scale" : "2x" 38 | } 39 | ], 40 | "info" : { 41 | "version" : 1, 42 | "author" : "xcode" 43 | } 44 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/LaunchImage.launchimage/Default-568h@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Example/Images.xcassets/LaunchImage.launchimage/Default-568h@2x-1.png -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Example/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIcons 10 | 11 | CFBundleIcons~ipad 12 | 13 | CFBundleIdentifier 14 | com.gskbyte.grxlayout 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | GRXLayout 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 0.2.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 20 27 | LSRequiresIPhoneOS 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/Example/Layouts/cell_test.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "300", 10 | "height" : "wrap_content", 11 | 12 | "debug_bgColor" : "white", 13 | "padding" : "12", 14 | 15 | "subviews" : [ 16 | 17 | { 18 | "id" : "top", 19 | "class" : "GRXRelativeLayout", 20 | "direction" : "vertical", 21 | "width" : "match_parent", 22 | "height" : "wrap_content", 23 | 24 | "subviews" : [ 25 | { 26 | "id" : "image", 27 | "class" : "UIImageView", 28 | "width" : "96", 29 | "height" : "128", 30 | 31 | "alignParentLeft" : "true", 32 | "alignParentTop" : "true", 33 | 34 | "marginRight" : "8", 35 | 36 | "debug_bgColor" : "blue" 37 | }, 38 | 39 | { 40 | "id" : "title", 41 | "class" : "GRXTextView", 42 | "width" : "match_parent", 43 | "height" : "wrap_content", 44 | 45 | "toRightOf" : "image", 46 | "alignParentTop" : "true", 47 | 48 | "debug_bgColor" : "yellow" 49 | }, 50 | 51 | { 52 | "id" : "subtitle", 53 | "class" : "UILabel", 54 | "width" : "match_parent", 55 | "height" : "wrap_content", 56 | 57 | "alignLeft" : "title", 58 | "below" : "title", 59 | "marginTop" : "8", 60 | 61 | "debug_bgColor" : "red" 62 | }, 63 | 64 | ] 65 | 66 | }, 67 | 68 | { 69 | "id" : "message", 70 | "class" : "GRXTextView", 71 | "width" : "match_parent", 72 | "height" : "wrap_content", 73 | 74 | "below" : "top", 75 | "marginTop" : "8", 76 | 77 | "debug_bgColor" : "orange" 78 | }, 79 | 80 | 81 | 82 | ] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Example/Example/Layouts/complex_cell.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXLinearLayout", 6 | "id" : "root", 7 | 8 | "direction" : "vertical", 9 | 10 | "width" : "300", 11 | "height" : "wrap_content", 12 | 13 | "padding" : "8", 14 | 15 | "subviews" : [ 16 | { 17 | "inflate" : { 18 | "filename" : "complex_cell_header.grx", 19 | }, 20 | 21 | "id" : "header", 22 | 23 | "width" : "match_parent", 24 | "height" : "wrap_content", 25 | }, 26 | { 27 | "class" : "GRXLinearLayout", 28 | "id" : "bodyContainer", 29 | 30 | "direction" : "vertical", 31 | 32 | "width" : "match_parent", 33 | "height" : "wrap_content", 34 | 35 | "marginTop" : "4", 36 | "padding" : "1", 37 | 38 | "subviews" : [ 39 | 40 | ] 41 | } 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Example/Example/Layouts/complex_cell_body.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXComplexBodyView", 6 | "id" : "root", 7 | 8 | "width" : "300", 9 | "height" : "wrap_content", 10 | 11 | "debug_bgColor" : "white", 12 | 13 | "marginTop" : "4", 14 | "padding" : "4", 15 | 16 | "subviews" : [ 17 | { 18 | "id" : "image", 19 | "class" : "UIImageView", 20 | "width" : "96", 21 | "height" : "128", 22 | 23 | "alignParentLeft" : "true", 24 | "alignParentTop" : "true", 25 | 26 | "marginRight" : "8", 27 | 28 | "debug_bgColor" : "blue" 29 | }, 30 | 31 | { 32 | "id" : "title", 33 | "class" : "UILabel", 34 | "width" : "match_parent", 35 | "height" : "wrap_content", 36 | 37 | "toRightOf" : "image", 38 | "alignParentTop" : "true", 39 | 40 | "debug_bgColor" : "yellow" 41 | }, 42 | 43 | { 44 | "id" : "subtitle", 45 | "class" : "UILabel", 46 | "width" : "match_parent", 47 | "height" : "wrap_content", 48 | 49 | "alignLeft" : "title", 50 | "below" : "title", 51 | "marginTop" : "8", 52 | 53 | "debug_bgColor" : "red" 54 | }, 55 | 56 | { 57 | "id" : "url", 58 | "class" : "GRXTextView", 59 | "width" : "match_parent", 60 | "height" : "wrap_content", 61 | 62 | "alignLeft" : "subtitle", 63 | "below" : "subtitle", 64 | "marginTop" : "8", 65 | 66 | "debug_bgColor" : "orange" 67 | } 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Example/Example/Layouts/complex_cell_header.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXComplexHeaderView", 7 | "id" : "root", 8 | 9 | "width" : "match_parent", 10 | "height" : "wrap_content", 11 | 12 | "debug_bgColor" : "white", 13 | "padding" : "12", 14 | 15 | "subviews" : [ 16 | 17 | { 18 | "id" : "top", 19 | "class" : "GRXRelativeLayout", 20 | "width" : "match_parent", 21 | "height" : "wrap_content", 22 | 23 | "subviews" : [ 24 | { 25 | "id" : "image", 26 | "class" : "UIImageView", 27 | "width" : "64", 28 | "height" : "96", 29 | 30 | "alignParentRight" : "true", 31 | "alignParentTop" : "true", 32 | 33 | "marginLeft" : "8", 34 | 35 | "debug_bgColor" : "blue" 36 | }, 37 | 38 | { 39 | "id" : "title", 40 | "class" : "GRXTextView", 41 | "width" : "match_parent", 42 | "height" : "wrap_content", 43 | 44 | "toLeftOf" : "image", 45 | "alignParentTop" : "true", 46 | 47 | "debug_bgColor" : "yellow" 48 | }, 49 | 50 | { 51 | "id" : "subtitle", 52 | "class" : "UILabel", 53 | "width" : "match_parent", 54 | "height" : "wrap_content", 55 | 56 | "alignLeft" : "title", 57 | "alignRight" : "title", 58 | "below" : "title", 59 | "marginTop" : "8", 60 | 61 | "debug_bgColor" : "red" 62 | } 63 | ] 64 | }, 65 | 66 | { 67 | "id" : "message", 68 | "class" : "GRXTextView", 69 | "width" : "match_parent", 70 | "height" : "wrap_content", 71 | 72 | "below" : "top", 73 | "marginTop" : "8", 74 | 75 | "debug_bgColor" : "orange" 76 | } 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Example/Example/Layouts/linear_test_1.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXLinearLayout", 6 | "width" : "match_parent", 7 | "height" : "wrap_content", 8 | 9 | "direction" : "vertical", 10 | "debug_bgColor" : "white", 11 | 12 | "subviews" : [ 13 | { 14 | "class" : "UISwitch", 15 | "id" : "switch", 16 | "debug_bgColor" : "red", 17 | "gravity" : "end", // should be aligned to the right 18 | "margin" : "8", 19 | }, 20 | { 21 | "class" : "UIImageView", 22 | "id" : "image", 23 | "width" : "wrap_content", 24 | "height" : "wrap_content", 25 | 26 | "debug_bgColor" : "blue", 27 | "gravity" : "center" 28 | }, 29 | { 30 | "class" : "UIView", 31 | "id" : "green", 32 | "width" : "match_parent", 33 | "height" : "80", 34 | "gravity" : "center", 35 | "margin" : "8", 36 | 37 | "debug_bgColor" : "green", 38 | }, 39 | { 40 | "class" : "UIView", 41 | "id" : "blue", 42 | "width" : "120", 43 | "height" : "120", 44 | "margin" : "10", 45 | 46 | "debug_bgColor" : "blue", 47 | "gravity" : "begin" // should be aligned to the left 48 | } 49 | ] 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /Example/Example/Layouts/relative_test_1.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "wrap_content", 10 | "height" : "wrap_content", 11 | 12 | 13 | "debug_bgColor" : "white", 14 | 15 | "subviews" : [ 16 | { 17 | "id" : "center", 18 | "class" : "UIView", 19 | "width" : "200", 20 | "height" : "100", 21 | 22 | "centerInParent" : "true", 23 | 24 | "debug_bgColor" : "red" 25 | }, 26 | 27 | { 28 | "id" : "rightOfAlignTop", 29 | "class" : "UIView", 30 | "width" : "30", 31 | "height" : "140", 32 | 33 | "toRightOf" : "center", 34 | "alignTop" : "center", 35 | 36 | "debug_bgColor" : "yellow" 37 | }, 38 | 39 | { 40 | "id" : "belowAlignRight", 41 | "class" : "UIView", 42 | "width" : "150", 43 | "height" : "40", 44 | 45 | "below" : "center", 46 | "alignRight" : "center", 47 | 48 | "marginTop" : "-10", 49 | 50 | "debug_bgColor" : "orange" 51 | }, 52 | 53 | 54 | 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/Example/Layouts/relative_test_2.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "300", 10 | "height" : "wrap_content", 11 | 12 | "debug_bgColor" : "white", 13 | "padding" : "12", 14 | 15 | "subviews" : [ 16 | { 17 | "id" : "image", 18 | "class" : "UIImageView", 19 | "width" : "64", 20 | "height" : "96", 21 | 22 | "alignParentLeft" : "true", 23 | "alignParentTop" : "true", 24 | 25 | "marginRight" : "8", 26 | 27 | "debug_bgColor" : "blue" 28 | }, 29 | 30 | { 31 | "id" : "title", 32 | "class" : "GRXTextView", 33 | "width" : "match_parent", 34 | "height" : "wrap_content", 35 | 36 | "toRightOf" : "image", 37 | "alignParentTop" : "true", 38 | 39 | "debug_bgColor" : "yellow" 40 | }, 41 | 42 | { 43 | "id" : "subtitle", 44 | "class" : "UILabel", 45 | "width" : "match_parent", 46 | "height" : "wrap_content", 47 | 48 | "alignLeft" : "title", 49 | "alignRight" : "title", 50 | "below" : "title", 51 | "marginTop" : "8", 52 | 53 | "debug_bgColor" : "red" 54 | } 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/Example/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GRXAppDelegate.h" 3 | #import "GRXTestAppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) { 6 | @autoreleasepool { 7 | BOOL runningTests = NSClassFromString(@"XCTestCase") != nil; 8 | if (runningTests) { 9 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([GRXTestAppDelegate class])); 10 | } else { 11 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([GRXAppDelegate class])); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | workspace '../GranadaLayout' 4 | xcodeproj 'Example.xcodeproj' 5 | 6 | target "Example" do 7 | pod "GranadaLayout", :path => "../GranadaLayout.podspec" 8 | end 9 | 10 | target "Tests" do 11 | pod 'Expecta', '~> 1.0' 12 | pod 'FBSnapshotTestCase', '1.7' 13 | end 14 | 15 | require 'slather' 16 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Expecta (1.0.5) 3 | - FBSnapshotTestCase (1.7) 4 | - GranadaLayout (0.4.1) 5 | 6 | DEPENDENCIES: 7 | - Expecta (~> 1.0) 8 | - FBSnapshotTestCase (= 1.7) 9 | - GranadaLayout (from `../GranadaLayout.podspec`) 10 | 11 | EXTERNAL SOURCES: 12 | GranadaLayout: 13 | :path: "../GranadaLayout.podspec" 14 | 15 | SPEC CHECKSUMS: 16 | Expecta: e1c022fcd33910b6be89c291d2775b3fe27a89fe 17 | FBSnapshotTestCase: 1eea5145c1f2fa907812ab92e1d6063a8d809b4e 18 | GranadaLayout: c1b7592b471490f8d9d1b63d0436c415d2153d85 19 | 20 | COCOAPODS: 0.39.0 21 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/broken.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "match_parent", 10 | "height" : "match_parent", 11 | 12 | "padding" : "8", 13 | "paddingLeft" : "12", 14 | "paddingTop" : "20", 15 | 16 | "subviews" : [ 17 | { 18 | "class" : "UIImageView", 19 | "id" : "image", 20 | "width" : "60" 21 | "height" : "50", 22 | 23 | "margin" : "10", 24 | "marginLeft" : "20", 25 | 26 | "alignParentRight" : "true", 27 | "alignParentBottom" : "true" 28 | }, 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/empty.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/inflate_contained_1.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "match_parent", 10 | "height" : "200", 11 | 12 | "padding" : "4", 13 | 14 | "debug_bgColor" : "white", 15 | 16 | "subviews" : [ 17 | { 18 | "id" : "topLeft", 19 | "class" : "UIView", 20 | "width" : "40", 21 | "height" : "40", 22 | 23 | "alignParentTop" : "true", 24 | "alignParentLeft" : "true", 25 | 26 | "debug_bgColor" : "darkGray" 27 | }, 28 | 29 | { 30 | "id" : "bottomRight", 31 | "class" : "UIView", 32 | "width" : "50", 33 | "height" : "20", 34 | 35 | "alignParentBottom" : "true", 36 | "alignParentRight" : "true", 37 | 38 | "debug_bgColor" : "black" 39 | }, 40 | 41 | 42 | 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/inflate_contained_2.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXLinearLayout", 7 | "id" : "root", 8 | "direction" : "horizontal", 9 | 10 | "width" : "match_parent", 11 | "height" : "200", 12 | 13 | "debug_bgColor" : "white", 14 | "weightSum" : "100", 15 | 16 | "subviews" : [ 17 | { 18 | "id" : "topLeft", 19 | "class" : "UIView", 20 | "height" : "match_parent", 21 | "weight" : "60", 22 | 23 | "debug_bgColor" : "darkGray" 24 | }, 25 | 26 | { 27 | "id" : "bottomRight", 28 | "class" : "UIView", 29 | "height" : "60", 30 | "weight" : "40", 31 | 32 | "debug_bgColor" : "yellow" 33 | }, 34 | 35 | 36 | 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/inflate_container.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXLinearLayout", 6 | "id" : "root", 7 | 8 | "width" : "320", 9 | "height" : "wrap_content", 10 | 11 | "padding" : "10", 12 | 13 | "debug_bgColor" : "white", 14 | 15 | "subviews" : [ 16 | { 17 | "id" : "alone", 18 | "class" : "UIView", 19 | "width" : "match_parent", 20 | "height" : "100", 21 | 22 | "debug_bgColor" : "red", 23 | "marginBottom" : "10" 24 | }, 25 | 26 | { 27 | "id" : "included1", 28 | // import file from layout, same bundle (just specifies file) 29 | "inflate" : "inflate_contained_1.grx", 30 | 31 | "width" : "100", 32 | "height" : "100", 33 | 34 | "gravity" : "center", 35 | 36 | "debug_bgColor" : "green", 37 | "marginBottom" : "10" 38 | }, 39 | 40 | { 41 | "id" : "included2", 42 | /* import file from bundle */ 43 | "inflate" : { 44 | "filename" : "inflate_contained_2.grx", 45 | "bundleId" : "org.gskbyte.GranadaLayout.Tests" 46 | }, 47 | 48 | "width" : "200", 49 | "height" : "200", 50 | 51 | "gravity" : "end", 52 | 53 | "debug_bgColor" : "blue" 54 | } 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/invalid_root.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "INVALID_CLASS", 6 | "id" : "root", 7 | 8 | "width" : "320", 9 | "height" : "wrap_content", 10 | 11 | "padding" : "10", 12 | 13 | "debug_bgColor" : "white", 14 | 15 | "subviews" : [ 16 | { 17 | "id" : "alone", 18 | "class" : "UIView", 19 | "width" : "match_parent", 20 | "height" : "100", 21 | 22 | "debug_bgColor" : "red", 23 | "marginBottom" : "10" 24 | }, 25 | 26 | { 27 | "id" : "included1", 28 | // import file from layout, same bundle (just specifies file) 29 | "inflate" : "inflate_contained_1.grx", 30 | 31 | "width" : "100", 32 | "height" : "100", 33 | 34 | "gravity" : "center", 35 | 36 | "debug_bgColor" : "green", 37 | "marginBottom" : "10" 38 | }, 39 | 40 | { 41 | "id" : "included2", 42 | /* import file from bundle */ 43 | "inflate" : { 44 | "filename" : "inflate_contained_2.grx", 45 | "bundleId" : "org.gskbyte.GranadaLayout.Tests" 46 | }, 47 | 48 | "width" : "200", 49 | "height" : "200", 50 | 51 | "gravity" : "end", 52 | 53 | "debug_bgColor" : "blue" 54 | } 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/lab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/Fixtures/lab.png -------------------------------------------------------------------------------- /Example/Tests/Fixtures/linear_horizontal.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXLinearLayout", 6 | "width" : "match_parent", 7 | "height" : "wrap_content", 8 | 9 | "direction" : "horizontal", 10 | "debug_bgColor" : "white", 11 | 12 | "subviews" : [ 13 | { 14 | "class" : "UISwitch", 15 | "id" : "switch", 16 | "debug_bgColor" : "red", 17 | "gravity" : "end", 18 | "margin" : "8", 19 | }, 20 | { 21 | "class" : "UIImageView", 22 | "id" : "image", 23 | "width" : "wrap_content", 24 | "height" : "wrap_content", 25 | 26 | "debug_bgColor" : "blue", 27 | "gravity" : "center" 28 | }, 29 | { 30 | "class" : "UIView", 31 | "id" : "green", 32 | "width" : "match_parent", 33 | "height" : "100", 34 | "gravity" : "center", 35 | "margin" : "12", 36 | 37 | "visibility" : "gone", 38 | 39 | "debug_bgColor" : "green", 40 | }, 41 | { 42 | "class" : "UIView", 43 | "id" : "red", 44 | "width" : "match_parent", 45 | "height" : "100", 46 | "gravity" : "center", 47 | "margin" : "8", 48 | 49 | "debug_bgColor" : "red", 50 | }, 51 | ] 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /Example/Tests/Fixtures/linear_horizontal_in_vertical.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXLinearLayout", 6 | "width" : "match_parent", 7 | "height" : "wrap_content", 8 | 9 | "direction" : "vertical", 10 | "debug_bgColor" : "white", 11 | 12 | "subviews" : [ 13 | { 14 | "class" : "UIImageView", 15 | "id" : "image", 16 | "width" : "wrap_content", 17 | "height" : "wrap_content", 18 | 19 | "debug_bgColor" : "blue", 20 | "gravity" : "center" 21 | }, 22 | { 23 | "class" : "GRXLinearLayout", 24 | "width" : "match_parent", 25 | "height" : "100", 26 | 27 | "direction" : "horizontal", 28 | "debug_bgColor" : "yellow", 29 | 30 | "marginTop" : "10", 31 | 32 | "weightSum" : 100, 33 | 34 | "subviews" : [ 35 | { 36 | "class" : "UIView", 37 | "height" : "50", 38 | 39 | "margin" : "10", 40 | "weight" : "30", 41 | 42 | "debug_bgColor" : "blue", 43 | "gravity" : "begin" 44 | }, 45 | 46 | { 47 | "class" : "UIView", 48 | "width" : "70", 49 | "height" : "match_parent", 50 | "weight" : "50", 51 | 52 | "margin" : "20", 53 | 54 | "debug_bgColor" : "green", 55 | "gravity" : "center" 56 | }, 57 | 58 | { 59 | "class" : "UIView", 60 | "height" : "60", 61 | "weight" : "50", 62 | 63 | "debug_bgColor" : "gray", 64 | "gravity" : "end" 65 | }, 66 | ] 67 | 68 | }, 69 | { 70 | "class" : "UIView", 71 | "id" : "red", 72 | "width" : "match_parent", 73 | "height" : "80", 74 | "gravity" : "center", 75 | "margin" : "8", 76 | 77 | "debug_bgColor" : "red", 78 | }, 79 | { 80 | "class" : "GRXLinearLayout", 81 | "width" : "wrap_content", 82 | "height" : "wrap_content", 83 | 84 | "direction" : "horizontal", 85 | "debug_bgColor" : "orange", 86 | 87 | "gravity" : "center", 88 | 89 | "subviews" : [ 90 | { 91 | "class" : "UIView", 92 | "width" : "50", 93 | "height" : "50", 94 | 95 | "margin" : "10", 96 | 97 | "debug_bgColor" : "blue", 98 | "gravity" : "begin" 99 | }, 100 | 101 | { 102 | "class" : "UIView", 103 | "width" : "70", 104 | "height" : "60", 105 | 106 | "marginTop" : "20", 107 | "marginBottom" : "20", 108 | "marginRight" : "20", 109 | 110 | "debug_bgColor" : "green", 111 | "gravity" : "center" 112 | }, 113 | 114 | ] 115 | 116 | }, 117 | ] 118 | 119 | } 120 | } -------------------------------------------------------------------------------- /Example/Tests/Fixtures/linear_horizontal_weight.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXLinearLayout", 6 | "width" : "match_parent", 7 | "height" : "wrap_content", 8 | 9 | "direction" : "horizontal", 10 | "debug_bgColor" : "white", 11 | 12 | "weightSum" : "100", 13 | 14 | "subviews" : [ 15 | { 16 | "class" : "UIImageView", 17 | "id" : "image", 18 | "width" : "0", 19 | "height" : "wrap_content", 20 | 21 | "weight" : "10", 22 | 23 | "debug_bgColor" : "#00FF00", 24 | "gravity" : "center" 25 | }, 26 | { 27 | "class" : "UIView", 28 | "id" : "red1", 29 | "width" : "0", 30 | "height" : "match_parent", 31 | 32 | "weight" : "30", 33 | 34 | 35 | "gravity" : "center", 36 | "margin" : "8", 37 | 38 | "debug_bgColor" : "red", 39 | }, 40 | { 41 | "class" : "UIView", 42 | "id" : "green", 43 | "width" : "match_parent", 44 | "height" : "0", 45 | "weight" : "5", 46 | "margin" : "12", 47 | 48 | "visibility" : "gone", 49 | 50 | "debug_bgColor" : "green", 51 | }, 52 | { 53 | "class" : "UIView", 54 | "id" : "green_hidden", 55 | "width" : "wrap_content", 56 | "height" : "100", 57 | 58 | "weight" : "10", 59 | "visibility" : "hidden", 60 | 61 | "gravity" : "center", 62 | 63 | "debug_bgColor" : "green", 64 | }, 65 | { 66 | "class" : "UIView", 67 | "id" : "orange", 68 | "height" : "100", 69 | 70 | "weight" : "10", 71 | 72 | "debug_bgColor" : "orange", 73 | "gravity" : "begin" 74 | }, 75 | { 76 | "class" : "UIView", 77 | "id" : "green_visible", 78 | "height" : "120", 79 | 80 | "weight" : "20", 81 | 82 | "debug_bgColor" : "green", 83 | }, 84 | ] 85 | 86 | } 87 | } -------------------------------------------------------------------------------- /Example/Tests/Fixtures/linear_horizontal_weight_label.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "id": "root", 6 | "class" : "GRXLinearLayout", 7 | "width" : "match_parent", 8 | "height" : "wrap_content", 9 | 10 | "direction" : "horizontal", 11 | "debug_bgColor" : "white", 12 | 13 | "weightSum" : "100", 14 | 15 | "subviews" : [ 16 | { 17 | "id" : "inner", 18 | "class" : "GRXLinearLayout", 19 | "width" : "0", 20 | "height" : "wrap_content", 21 | 22 | "direction" : "horizontal", 23 | "debug_bgColor" : "yellow", 24 | 25 | "weight" : "70", 26 | 27 | "subviews" : [ 28 | { 29 | "class" : "UIImageView", 30 | "id" : "image", 31 | "width" : "wrap_content", 32 | "height" : "wrap_content", 33 | 34 | "debug_bgColor" : "green", 35 | "gravity" : "center" 36 | }, 37 | { 38 | "class" : "UILabel", 39 | "id" : "label", 40 | "width" : "match_parent", 41 | "height" : "wrap_content", 42 | 43 | "gravity" : "center", 44 | "margin" : "8", 45 | 46 | "debug_bgColor" : "red", 47 | }, 48 | 49 | ] 50 | 51 | }, 52 | 53 | { 54 | "class" : "UIView", 55 | "width" : "0", 56 | "height" : "match_parent", 57 | 58 | "debug_bgColor" : "orange", 59 | 60 | "weight" : "30", 61 | 62 | 63 | } 64 | 65 | ] 66 | 67 | } 68 | } -------------------------------------------------------------------------------- /Example/Tests/Fixtures/linear_in_relative.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "match_parent", 10 | "height" : "match_parent", 11 | 12 | "padding" : "8", 13 | "paddingLeft" : "12", 14 | "paddingTop" : "20", 15 | 16 | "subviews" : [ 17 | { 18 | "class" : "UIImageView", 19 | "id" : "image", 20 | "width" : "60", 21 | "height" : "50", 22 | 23 | "margin" : "10", 24 | "marginLeft" : "20", 25 | 26 | "alignParentRight" : "true", 27 | "alignParentBottom" : "true" 28 | }, 29 | { 30 | "class" : "UISwitch", 31 | "id" : "switch", 32 | "width" : "wrap_content", 33 | "height" : "wrap_content", 34 | 35 | "above" : "image", 36 | "toLeftOf" : "image", 37 | 38 | "visibility" : "gone", 39 | }, 40 | { 41 | "class" : "GRXLinearLayout", 42 | "id" : "linear", 43 | "width" : "match_parent", 44 | "height" : "wrap_content", 45 | 46 | "alignParentTop" : "true", 47 | 48 | "subviews" : [ 49 | { 50 | "class" : "UILabel", 51 | "id" : "label", 52 | "width" : "wrap_content", 53 | "height" : "wrap_content", 54 | 55 | "minWidth" : "200", 56 | "minHeight" : 50, 57 | }, 58 | { 59 | "class" : "UIButton", 60 | "id" : "button", 61 | "width" : "wrap_content", 62 | "height" : "wrap_content", 63 | 64 | "visibility" : "hidden", 65 | }, 66 | ] 67 | } 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/linear_root.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXLinearLayout", 7 | "id" : "root", 8 | 9 | "width" : "match_parent", 10 | "height" : "match_parent", 11 | 12 | "direction" : "horizontal", 13 | "weightSum" : "5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/linear_vertical.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXLinearLayout", 6 | "width" : "wrap_content", 7 | "height" : "wrap_content", 8 | 9 | "direction" : "vertical", 10 | "debug_bgColor" : "white", 11 | 12 | "subviews" : [ 13 | { 14 | "class" : "UISwitch", 15 | "id" : "switch", 16 | "debug_bgColor" : "red", 17 | "gravity" : "end", 18 | "margin" : "8", 19 | }, 20 | { 21 | "class" : "UIImageView", 22 | "id" : "image", 23 | "width" : "wrap_content", 24 | "height" : "wrap_content", 25 | "minWidth" : "40", 26 | "minHeight" : "40", 27 | 28 | "debug_bgColor" : "blue", 29 | "gravity" : "center" 30 | }, 31 | { 32 | "class" : "UIView", 33 | "id" : "green", 34 | "width" : "match_parent", 35 | "height" : "100", 36 | "gravity" : "center", 37 | "margin" : "12", 38 | 39 | "visibility" : "gone", 40 | 41 | "debug_bgColor" : "green", 42 | }, 43 | { 44 | "class" : "UIView", 45 | "id" : "red", 46 | "width" : "match_parent", 47 | "height" : "80", 48 | "gravity" : "center", 49 | "margin" : "8", 50 | 51 | "debug_bgColor" : "red", 52 | }, 53 | { 54 | "class" : "UIView", 55 | "id" : "blue", 56 | "width" : "200", 57 | "height" : "120", 58 | "margin" : "10", 59 | 60 | "debug_bgColor" : "blue", 61 | "gravity" : "begin" 62 | } 63 | ] 64 | 65 | } 66 | } -------------------------------------------------------------------------------- /Example/Tests/Fixtures/linear_vertical_weight.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | "layout" : { 5 | "class" : "GRXLinearLayout", 6 | "width" : "300", 7 | "height" : "500", 8 | 9 | "direction" : "vertical", 10 | "debug_bgColor" : "white", 11 | 12 | "weightSum" : "100", 13 | 14 | "subviews" : [ 15 | { 16 | "class" : "UIImageView", 17 | "id" : "image", 18 | "width" : "wrap_content", 19 | "height" : "0", 20 | 21 | "weight" : "40", 22 | 23 | "debug_bgColor" : "#0000FF", 24 | "gravity" : "center" 25 | }, 26 | { 27 | "class" : "UIView", 28 | "id" : "red1", 29 | "width" : "match_parent", 30 | "height" : "80", 31 | 32 | "weight" : "30", 33 | "minWidth" : "100", 34 | "minHeight" : "50", 35 | "gravity" : "center", 36 | "margin" : "8", 37 | 38 | "debug_bgColor" : "red", 39 | }, 40 | { 41 | "class" : "UIView", 42 | "id" : "green", 43 | "width" : "match_parent", 44 | "height" : "0", 45 | "weight" : "5", 46 | "margin" : "12", 47 | 48 | "visibility" : "gone", 49 | 50 | "debug_bgColor" : "green", 51 | }, 52 | 53 | { 54 | "class" : "UIView", 55 | "id" : "green_hidden", 56 | "width" : "100", 57 | "height" : "0", 58 | 59 | "weight" : "10", 60 | "visibility" : "hidden", 61 | 62 | "gravity" : "center", 63 | 64 | "debug_bgColor" : "green", 65 | }, 66 | { 67 | "class" : "UIView", 68 | "id" : "red2", 69 | "width" : "120", 70 | "height" : "120", 71 | 72 | "weight" : "10", 73 | 74 | "margin" : "10", 75 | 76 | "debug_bgColor" : "blue", 77 | "gravity" : "begin" 78 | }, 79 | { 80 | "class" : "UIView", 81 | "id" : "green_visible", 82 | "width" : "50", 83 | "height" : "0", 84 | 85 | "weight" : "5", 86 | 87 | "gravity" : "center", 88 | 89 | "debug_bgColor" : "green", 90 | }, 91 | ] 92 | 93 | } 94 | } -------------------------------------------------------------------------------- /Example/Tests/Fixtures/no_layout_root.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "UIView", 7 | "id" : "root", 8 | 9 | "width" : "match_parent", 10 | "height" : "match_parent", 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/relative_in_relative.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "300", 10 | "height" : "wrap_content", 11 | 12 | "debug_bgColor" : "white", 13 | "padding" : "12", 14 | 15 | "subviews" : [ 16 | 17 | { 18 | "id" : "top", 19 | "class" : "GRXRelativeLayout", 20 | "direction" : "vertical", 21 | "width" : "match_parent", 22 | "height" : "wrap_content", 23 | 24 | "subviews" : [ 25 | { 26 | "id" : "image", 27 | "class" : "UIImageView", 28 | "width" : "96", 29 | "height" : "128", 30 | 31 | "alignParentLeft" : "true", 32 | "alignParentTop" : "true", 33 | 34 | "marginRight" : "8", 35 | "visibility" : "visible", 36 | 37 | "debug_bgColor" : "blue" 38 | }, 39 | 40 | { 41 | "id" : "title", 42 | "class" : "GRXTextView", 43 | "width" : "match_parent", 44 | "height" : "wrap_content", 45 | 46 | "toRightOf" : "image", 47 | "alignParentTop" : "true", 48 | 49 | "debug_bgColor" : "yellow" 50 | }, 51 | 52 | { 53 | "id" : "subtitle", 54 | "class" : "UILabel", 55 | "width" : "match_parent", 56 | "height" : "wrap_content", 57 | 58 | "alignLeft" : "title", 59 | "below" : "title", 60 | "marginTop" : "8", 61 | 62 | "debug_bgColor" : "red" 63 | }, 64 | 65 | ] 66 | 67 | }, 68 | 69 | { 70 | "id" : "message", 71 | "class" : "GRXTextView", 72 | "width" : "match_parent", 73 | "height" : "wrap_content", 74 | 75 | "below" : "top", 76 | "marginTop" : "8", 77 | 78 | "debug_bgColor" : "orange" 79 | }, 80 | 81 | 82 | 83 | ] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/relative_other_views.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "300", 10 | "height" : "400", 11 | 12 | "debug_bgColor" : "white", 13 | 14 | "subviews" : [ 15 | { 16 | "id" : "topLeft", 17 | "class" : "UIView", 18 | "width" : "wrap_content", 19 | "height" : "wrap_content", 20 | 21 | "minWidth" : "40", 22 | "minHeight" : "40", 23 | 24 | "alignParentTop" : "true", 25 | "alignParentLeft" : "true", 26 | 27 | "debug_bgColor" : "red" 28 | }, 29 | { 30 | "id" : "a", 31 | "class" : "UIView", 32 | "width" : "40", 33 | "height" : "40", 34 | 35 | "marginLeft" : "-10", 36 | "marginTop" : "10", 37 | 38 | "below" : "topLeft", 39 | "toRightOf" : "topLeft", 40 | 41 | "debug_bgColor" : "green" 42 | }, 43 | { 44 | "id" : "b", 45 | "class" : "UIView", 46 | "width" : "match_parent", 47 | "height" : "70", 48 | 49 | "below" : "a", 50 | "toRightOf" : "a", 51 | 52 | "debug_bgColor" : "blue" 53 | }, 54 | { 55 | "id" : "c", 56 | "class" : "UIView", 57 | "width" : "wrap_content", 58 | "height" : "wrap_content", 59 | 60 | "minWidth" : "60", 61 | "minHeight" : "50", 62 | 63 | "margin" : "5", 64 | 65 | "alignTop" : "b", 66 | "alignRight" : "b", 67 | 68 | "debug_bgColor" : "yellow" 69 | }, 70 | 71 | 72 | { 73 | "id" : "bottomRight", 74 | "class" : "UIView", 75 | "width" : "120", 76 | "height" : "70", 77 | 78 | "alignParentBottom" : "true", 79 | "alignParentRight" : "true", 80 | 81 | "debug_bgColor" : "red" 82 | }, 83 | { 84 | "id" : "d", 85 | "class" : "UIView", 86 | "width" : "50", 87 | "height" : "40", 88 | 89 | "above" : "bottomRight", 90 | "alignLeft" : "bottomRight", 91 | 92 | "debug_bgColor" : "green" 93 | }, 94 | { 95 | "id" : "e", 96 | "class" : "UIView", 97 | "width" : "40", 98 | "height" : "30", 99 | 100 | "toLeftOf" : "d", 101 | "alignBottom" : "d", 102 | 103 | "debug_bgColor" : "blue" 104 | }, 105 | { 106 | "id" : "gone", 107 | "class" : "UIView", 108 | "width" : "50", 109 | "height" : "50", 110 | "visibility" : "gone", 111 | 112 | "toLeftOf" : "e", 113 | "alignTop" : "e", 114 | 115 | "debug_bgColor" : "gray" 116 | }, 117 | { 118 | "id" : "f", 119 | "class" : "UIView", 120 | "width" : "30", 121 | "height" : "50", 122 | 123 | "below" : "e", 124 | "alignRight" : "e", 125 | 126 | "debug_bgColor" : "yellow" 127 | }, 128 | 129 | 130 | 131 | ] 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/relative_parent.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "200", 10 | "height" : "200", 11 | 12 | "debug_bgColor" : "white", 13 | 14 | "subviews" : [ 15 | { 16 | "id" : "topLeft", 17 | "class" : "UIView", 18 | "width" : "wrap_content", 19 | "height" : "wrap_content", 20 | 21 | "minWidth" : "50", 22 | "minHeight" : "60", 23 | 24 | "alignParentTop" : "true", 25 | "alignParentLeft" : "true", 26 | 27 | "debug_bgColor" : "red" 28 | }, 29 | { 30 | "id" : "topRight", 31 | "class" : "UIView", 32 | "width" : "40", 33 | "height" : "40", 34 | 35 | "margin" : "10", 36 | 37 | "alignParentTop" : "true", 38 | "alignParentRight" : "true", 39 | 40 | "debug_bgColor" : "red" 41 | }, 42 | { 43 | "id" : "bottomLeft", 44 | "class" : "UIView", 45 | "width" : "40", 46 | "height" : "40", 47 | 48 | "marginLeft" : "20", 49 | 50 | "alignParentBottom" : "true", 51 | "alignParentLeft" : "true", 52 | 53 | "debug_bgColor" : "red" 54 | }, 55 | { 56 | "id" : "bottomRight", 57 | "class" : "UIView", 58 | "width" : "70", 59 | "height" : "50", 60 | 61 | "marginBottom" : "10", 62 | 63 | "alignParentBottom" : "true", 64 | "alignParentRight" : "true", 65 | 66 | "debug_bgColor" : "gray" 67 | }, 68 | 69 | 70 | { 71 | "id" : "center", 72 | "class" : "UIView", 73 | "width" : "60", 74 | "height" : "60", 75 | 76 | "centerInParent" : "true", 77 | "marginBottom" : "10", 78 | "marginRight" : 10, 79 | 80 | "debug_bgColor" : "yellow" 81 | }, 82 | { 83 | "id" : "vCenterLeft", 84 | "class" : "UIView", 85 | "width" : "40", 86 | "height" : "40", 87 | 88 | "centerVertical" : "true", 89 | "alignParentLeft" : "true", 90 | 91 | "debug_bgColor" : "blue" 92 | }, 93 | { 94 | "id" : "hCenterTop", 95 | "class" : "UIView", 96 | "width" : "40", 97 | "height" : "40", 98 | 99 | "alignParentTop" : "true", 100 | "centerHorizontal" : "true", 101 | 102 | "debug_bgColor" : "green" 103 | }, 104 | 105 | 106 | ] 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/relative_root.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "match_parent", 10 | "height" : "match_parent", 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/relative_to_gone.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "320", 10 | "height" : "480", 11 | 12 | "debug_bgColor" : "white", 13 | 14 | "subviews" : [ 15 | { 16 | "id" : "first", 17 | "class" : "UIView", 18 | "width" : "100", 19 | "height" : "100", 20 | 21 | "alignParentRight" : "true", 22 | "alignParentBottom" : "true", 23 | 24 | "debug_bgColor" : "red" 25 | }, 26 | 27 | { 28 | "id" : "gone_view", 29 | "class" : "UIView", 30 | "width" : "40", 31 | "height" : "50", 32 | 33 | "toLeftOf" : "first", 34 | "alignTop" : "first", 35 | 36 | "visibility" : "gone", 37 | 38 | "debug_bgColor" : "yellow" 39 | }, 40 | 41 | { 42 | "id" : "third", 43 | "class" : "UIView", 44 | "width" : "70", 45 | "height" : "40", 46 | 47 | "above" : "gone_view", 48 | "toLeftOf" : "gone_view", 49 | 50 | "debug_bgColor" : "orange" 51 | }, 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/relative_wrapped.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "wrap_content", 10 | "height" : "wrap_content", 11 | 12 | "padding" : "10", 13 | 14 | "debug_bgColor" : "white", 15 | 16 | "subviews" : [ 17 | { 18 | "id" : "topLeft", 19 | "class" : "UIView", 20 | "width" : "wrap_content", 21 | "height" : "wrap_content", 22 | 23 | "minWidth" : "100", 24 | "minHeight" : "40", 25 | 26 | "alignParentTop" : "true", 27 | "alignParentLeft" : "true", 28 | 29 | "debug_bgColor" : "red" 30 | }, 31 | 32 | { 33 | "id" : "belowRightOf", 34 | "class" : "UIView", 35 | "width" : "70", 36 | "height" : "60", 37 | 38 | "below" : "topLeft", 39 | "toRightOf" : "topLeft", 40 | 41 | "debug_bgColor" : "blue" 42 | }, 43 | 44 | { 45 | "id" : "alignTopLeftOf", 46 | "class" : "UIView", 47 | "width" : "80", 48 | "height" : "100", 49 | 50 | "toLeftOf" : "belowRightOf", 51 | "alignTop" : "belowRightOf", 52 | "margin" : "10", 53 | 54 | "debug_bgColor" : "green" 55 | }, 56 | 57 | 58 | 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/relative_wrapped_around_center.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "wrap_content", 10 | "height" : "wrap_content", 11 | 12 | 13 | "debug_bgColor" : "white", 14 | 15 | "subviews" : [ 16 | { 17 | "id" : "center", 18 | "class" : "UIView", 19 | "width" : "200", 20 | "height" : "100", 21 | 22 | "centerInParent" : "true", 23 | 24 | "debug_bgColor" : "red" 25 | }, 26 | 27 | { 28 | "id" : "rightOfAlignTop", 29 | "class" : "UIView", 30 | "width" : "30", 31 | "height" : "140", 32 | 33 | "toRightOf" : "center", 34 | "alignTop" : "center", 35 | 36 | "debug_bgColor" : "yellow" 37 | }, 38 | 39 | { 40 | "id" : "belowAlignRight", 41 | "class" : "UIView", 42 | "width" : "150", 43 | "height" : "40", 44 | 45 | "below" : "center", 46 | "alignRight" : "center", 47 | 48 | "marginTop" : "-10", 49 | 50 | "debug_bgColor" : "orange" 51 | }, 52 | 53 | 54 | 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/relative_wrapped_push.grx: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "0.1", 3 | 4 | 5 | "layout" : { 6 | "class" : "GRXRelativeLayout", 7 | "id" : "root", 8 | 9 | "width" : "320", 10 | "height" : "480", 11 | 12 | "debug_bgColor" : "white", 13 | 14 | "subviews" : [ 15 | { 16 | "id" : "topLeft", 17 | "class" : "UIView", 18 | "width" : "wrap_content", 19 | "height" : "wrap_content", 20 | 21 | "minWidth" : "40", 22 | "minHeight" : "40", 23 | 24 | "alignParentTop" : "true", 25 | "alignParentLeft" : "true", 26 | 27 | "debug_bgColor" : "red" 28 | }, 29 | 30 | { 31 | "id" : "bottomRight", 32 | "class" : "UIView", 33 | "width" : "120", 34 | "height" : "70", 35 | 36 | "alignParentBottom" : "true", 37 | "alignParentRight" : "true", 38 | 39 | "debug_bgColor" : "red" 40 | }, 41 | 42 | { 43 | "id" : "center", 44 | "class" : "UIView", 45 | "width" : "60", 46 | "height" : "50", 47 | 48 | "below" : "topLeft", 49 | "toRightOf" : "topLeft", 50 | "above" : "bottomRight", 51 | "alignLeft" : "bottomRight", 52 | 53 | "debug_bgColor" : "yellow" 54 | }, 55 | 56 | 57 | 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Example/Tests/GRXCombinationSnapshotTests.m: -------------------------------------------------------------------------------- 1 | #import "GRXTestHelper.h" 2 | #define EXP_SHORTHAND 3 | 4 | #import 5 | 6 | @interface GRXCombinationSnapshotTests : FBSnapshotTestCase 7 | 8 | @end 9 | 10 | @implementation GRXCombinationSnapshotTests 11 | 12 | - (void)setUp { 13 | [super setUp]; 14 | self.recordMode = NO; 15 | } 16 | 17 | - (void)testRecursiveInflation { 18 | UIView *rootView = [GRXTestHelper rootViewForLayoutFileWithName:@"inflate_container.grx"]; 19 | [rootView layoutSubviews]; 20 | 21 | FBSnapshotVerifyView(rootView, nil); 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Example/Tests/GRXLayoutParamsUnitTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GRXTestHelper.h" 3 | 4 | #import 5 | #import 6 | #import 7 | #define EXP_SHORTHAND 8 | 9 | #import 10 | 11 | @interface GRXLayoutParamsUnitTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation GRXLayoutParamsUnitTests 16 | 17 | - (void)testLinearLayoutParams { 18 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_vertical_weight.grx"]; 19 | UIView *red = [inflater.rootView grx_findViewWithIdentifier:@"red1"]; 20 | GRXLinearLayoutParams *linearParams = red.grx_linearLayoutParams; 21 | expect(linearParams).to.beInstanceOf(GRXLinearLayoutParams.class); 22 | expect(linearParams.width).to.equal(GRXMatchParent); 23 | expect(linearParams.height).to.equal(80); 24 | expect(linearParams.margins.top).to.equal(8); 25 | 26 | expect(linearParams.gravity).to.equal(GRXLinearLayoutGravityCenter); 27 | expect(linearParams.weight).to.equal(30); 28 | 29 | GRXLinearLayoutParams *copy = linearParams.copy; 30 | expect(copy).to.beInstanceOf(linearParams.class); 31 | expect(copy.width).to.equal(linearParams.width); 32 | expect(copy.height).to.equal(linearParams.height); 33 | expect(copy.margins.top).to.equal(linearParams.margins.top); 34 | 35 | expect(copy.gravity).to.equal(linearParams.gravity); 36 | expect(copy.weight).to.equal(linearParams.weight); 37 | } 38 | 39 | - (void)testRelativeParams { 40 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"relative_in_relative.grx"]; 41 | UIImageView *image = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 42 | UIView *title = [inflater.rootView grx_findViewWithIdentifier:@"title"]; 43 | GRXRelativeLayoutParams *relativeParams = title.grx_relativeLayoutParams; 44 | expect(relativeParams).to.beInstanceOf(GRXRelativeLayoutParams.class); 45 | expect(relativeParams.width).to.equal(GRXMatchParent); 46 | expect(relativeParams.height).to.equal(GRXWrapContent); 47 | expect([relativeParams hasParentRule:GRXRelativeLayoutParentRuleAlignTop]).to.beTruthy(); 48 | expect([relativeParams hasRule:GRXRelativeLayoutRuleRightOf]).to.beTruthy(); 49 | expect([relativeParams viewForRule:GRXRelativeLayoutRuleRightOf]).to.beIdenticalTo(image); 50 | 51 | GRXRelativeLayoutParams *copy = relativeParams.copy; 52 | expect(copy).to.beInstanceOf(relativeParams.class); 53 | expect(copy.width).to.equal(relativeParams.width); 54 | expect(copy.height).to.equal(relativeParams.height); 55 | expect(copy.margins.top).to.equal(relativeParams.margins.top); 56 | 57 | for (GRXRelativeLayoutParentRule pr = GRXRelativeLayoutParentRuleAlignLeft; 58 | pr < GRXRelativeLayoutParentRuleCount; 59 | ++pr) { 60 | expect([copy hasParentRule:pr]).to.equal([relativeParams hasParentRule:pr]); 61 | } 62 | 63 | for (GRXRelativeLayoutRule r = GRXRelativeLayoutRuleLeftOf; 64 | r < GRXRelativeLayoutRuleCount; 65 | ++r) { 66 | expect([copy viewForRule:r]).to.equal([relativeParams viewForRule:r]); 67 | } 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Example/Tests/GRXLinearLayoutUnitTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GRXTestHelper.h" 3 | 4 | #import 5 | #import 6 | 7 | #define EXP_SHORTHAND 8 | #import 9 | 10 | @interface GRXLinearLayoutUnitTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation GRXLinearLayoutUnitTests 15 | 16 | - (void)testRootLinear { 17 | GRXLinearLayout *rootView = [GRXTestHelper rootViewForLayoutFileWithName:@"linear_root.grx"]; 18 | expect(rootView).notTo.beNil(); 19 | expect(rootView).to.beInstanceOf(GRXLinearLayout.class); 20 | expect(rootView.direction).to.equal(GRXLinearLayoutDirectionHorizontal); 21 | expect(rootView.weightSum).to.equal(5); 22 | } 23 | 24 | - (void)testLinearVertical { 25 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_vertical.grx"]; 26 | GRXLinearLayout *rootView = (GRXLinearLayout *) inflater.rootView; 27 | expect(rootView).notTo.beNil(); 28 | expect(rootView).to.beInstanceOf(GRXLinearLayout.class); 29 | expect(rootView.direction).to.equal(GRXLinearLayoutDirectionVertical); 30 | expect(rootView.subviews).to.haveCountOf(5); 31 | 32 | UIView *sw = [inflater.rootView grx_findViewWithIdentifier:@"switch"]; 33 | expect(sw).to.beInstanceOf(UISwitch.class); 34 | expect(sw.grx_linearLayoutParams.width).to.equal(GRXWrapContent); 35 | expect(sw.grx_linearLayoutParams.height).to.equal(GRXWrapContent); 36 | expect(sw.grx_linearLayoutParams.gravity).to.equal(GRXLinearLayoutGravityEnd); 37 | expect(sw.grx_linearLayoutParams.hasMargins).to.beTruthy(); 38 | expect(sw.grx_linearLayoutParams.margins.top).to.equal(8); 39 | 40 | UIImageView *image = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 41 | expect(image).to.beInstanceOf(UIImageView.class); 42 | expect(image.grx_linearLayoutParams.width).to.equal(GRXWrapContent); 43 | expect(image.grx_linearLayoutParams.height).to.equal(GRXWrapContent); 44 | expect(image.grx_linearLayoutParams.gravity).to.equal(GRXLinearLayoutGravityCenter); 45 | expect(image.grx_linearLayoutParams.hasMargins).to.beFalsy(); 46 | expect(image.grx_linearLayoutParams.margins.top).to.equal(0); 47 | 48 | UIImageView *blueView = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"blue"]; 49 | expect(blueView).to.beInstanceOf(UIView.class); 50 | expect(blueView.grx_linearLayoutParams.width).to.equal(200); 51 | expect(blueView.grx_linearLayoutParams.height).to.equal(120); 52 | expect(blueView.grx_linearLayoutParams.gravity).to.equal(GRXLinearLayoutGravityBegin); 53 | expect(blueView.grx_linearLayoutParams.hasMargins).to.beTruthy(); 54 | expect(blueView.grx_linearLayoutParams.margins.top).to.equal(10); 55 | 56 | [rootView layoutSubviews]; 57 | 58 | expect(sw.top).to.equal(8); 59 | expect(sw.right).to.equal(rootView.width - 8); 60 | 61 | expect(image.width).to.beGreaterThanOrEqualTo(40); 62 | expect(image.height).to.beGreaterThanOrEqualTo(40); 63 | expect(image.top).to.equal(sw.bottom + 8); 64 | 65 | expect(blueView.top).to.beGreaterThan(image.bottom); 66 | expect(blueView.bottom).to.equal(rootView.height - 10); 67 | expect(blueView.left).to.equal(10); 68 | expect(blueView.width).to.equal(200); 69 | expect(blueView.height).to.equal(120); 70 | } 71 | 72 | - (void)testLinearVerticalWeighted { 73 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_vertical_weight.grx"]; 74 | GRXLinearLayout *rootView = (GRXLinearLayout *) inflater.rootView; 75 | expect(rootView).notTo.beNil(); 76 | expect(rootView).to.beInstanceOf(GRXLinearLayout.class); 77 | expect(rootView.direction).to.equal(GRXLinearLayoutDirectionVertical); 78 | expect(rootView.subviews).to.haveCountOf(6); 79 | expect(rootView.weightSum).to.equal(100); 80 | 81 | UIImageView *image = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 82 | expect(image).to.beInstanceOf(UIImageView.class); 83 | expect(image.grx_linearLayoutParams.width).to.equal(GRXWrapContent); 84 | expect(image.grx_linearLayoutParams.height).to.equal(0); 85 | expect(image.grx_linearLayoutParams.gravity).to.equal(GRXLinearLayoutGravityCenter); 86 | expect(image.grx_linearLayoutParams.hasMargins).to.beFalsy(); 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /Example/Tests/GRXLinearSnapshotTests.m: -------------------------------------------------------------------------------- 1 | #import "GRXTestHelper.h" 2 | 3 | #import 4 | #define EXP_SHORTHAND 5 | 6 | #import 7 | 8 | @interface GRXLinearSnapshotTests : FBSnapshotTestCase 9 | 10 | @end 11 | 12 | @implementation GRXLinearSnapshotTests 13 | 14 | - (void)setUp { 15 | [super setUp]; 16 | self.recordMode = NO; 17 | } 18 | 19 | - (void)testVertical { 20 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_vertical.grx"]; 21 | 22 | UIImageView *imageView = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 23 | imageView.image = [GRXTestHelper imageWithName:@"lab.png"]; 24 | 25 | UIView *rootView = inflater.rootView; 26 | [rootView layoutSubviews]; 27 | 28 | FBSnapshotVerifyLayer(rootView.layer, nil); 29 | } 30 | 31 | - (void)testVerticalWeighted { 32 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_vertical_weight.grx"]; 33 | 34 | UIImageView *imageView = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 35 | imageView.image = [GRXTestHelper imageWithName:@"lab.png"]; 36 | 37 | UIView *rootView = inflater.rootView; 38 | [rootView layoutSubviews]; 39 | 40 | FBSnapshotVerifyLayer(rootView.layer, nil); 41 | } 42 | 43 | - (void)testHorizontal { 44 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_horizontal.grx"]; 45 | 46 | UIImageView *imageView = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 47 | imageView.image = [GRXTestHelper imageWithName:@"lab.png"]; 48 | 49 | UIView *rootView = inflater.rootView; 50 | [rootView layoutSubviews]; 51 | 52 | FBSnapshotVerifyLayer(rootView.layer, nil); 53 | } 54 | 55 | - (void)testHorizontalWeighted { 56 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_horizontal_weight.grx"]; 57 | 58 | UIImageView *imageView = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 59 | imageView.image = [GRXTestHelper imageWithName:@"lab.png"]; 60 | imageView.contentMode = UIViewContentModeScaleAspectFit; 61 | 62 | UIView *rootView = inflater.rootView; 63 | [rootView layoutSubviews]; 64 | 65 | FBSnapshotVerifyLayer(rootView.layer, nil); 66 | } 67 | 68 | - (void)testHorizontalWeightedWithLabel { 69 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_horizontal_weight_label.grx"]; 70 | 71 | UIImageView *imageView = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 72 | imageView.image = [GRXTestHelper imageWithName:@"lab.png"]; 73 | imageView.contentMode = UIViewContentModeScaleAspectFit; 74 | 75 | UILabel *label = (UILabel *) [inflater.rootView grx_findViewWithIdentifier:@"label"]; 76 | label.text = @"This is a text in a UILabel"; 77 | 78 | UIView *rootView = inflater.rootView; 79 | [rootView layoutSubviews]; 80 | 81 | FBSnapshotVerifyLayer(rootView.layer, nil); 82 | } 83 | 84 | - (void)testHorizontalInVertical { 85 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"linear_horizontal_in_vertical.grx"]; 86 | 87 | UIImageView *imageView = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 88 | imageView.image = [GRXTestHelper imageWithName:@"lab.png"]; 89 | imageView.contentMode = UIViewContentModeScaleAspectFit; 90 | 91 | UIView *rootView = inflater.rootView; 92 | [rootView layoutSubviews]; 93 | 94 | FBSnapshotVerifyLayer(rootView.layer, nil); 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Example/Tests/GRXRelativeLayoutUnitTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GRXTestHelper.h" 3 | 4 | #import 5 | #define EXP_SHORTHAND 6 | 7 | #import 8 | 9 | @interface GRXRelativeLayoutUnitTests : XCTestCase 10 | 11 | @end 12 | 13 | @implementation GRXRelativeLayoutUnitTests 14 | 15 | #define expectParentRule(ident, rule) \ 16 | expect([self viewForId:ident hasParentRule:rule inInflater:inflater]).to.beTruthy() 17 | 18 | - (void)testAlignParent { 19 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"relative_parent.grx"]; 20 | 21 | expectParentRule(@"topLeft", GRXRelativeLayoutParentRuleAlignTop); 22 | expectParentRule(@"topLeft", GRXRelativeLayoutParentRuleAlignLeft); 23 | 24 | expectParentRule(@"topRight", GRXRelativeLayoutParentRuleAlignTop); 25 | expectParentRule(@"topRight", GRXRelativeLayoutParentRuleAlignRight); 26 | 27 | expectParentRule(@"bottomLeft", GRXRelativeLayoutParentRuleAlignBottom); 28 | expectParentRule(@"bottomLeft", GRXRelativeLayoutParentRuleAlignLeft); 29 | 30 | expectParentRule(@"bottomRight", GRXRelativeLayoutParentRuleAlignBottom); 31 | expectParentRule(@"bottomRight", GRXRelativeLayoutParentRuleAlignRight); 32 | 33 | expectParentRule(@"center", GRXRelativeLayoutParentRuleCenter); 34 | 35 | expectParentRule(@"vCenterLeft", GRXRelativeLayoutParentRuleCenterVertical); 36 | expectParentRule(@"vCenterLeft", GRXRelativeLayoutParentRuleAlignLeft); 37 | 38 | expectParentRule(@"hCenterTop", GRXRelativeLayoutParentRuleCenterHorizontal); 39 | expectParentRule(@"hCenterTop", GRXRelativeLayoutParentRuleAlignTop); 40 | } 41 | 42 | - (void)testAlignRelative { 43 | // TODO write this test 44 | } 45 | 46 | #pragma mark - Helper methods 47 | 48 | - (BOOL)viewForId:(NSString *)identifier 49 | hasParentRule:(GRXRelativeLayoutParentRule)rule 50 | inInflater:(GRXLayoutInflater *)inflater { 51 | UIView *view = [inflater.rootView grx_findViewWithIdentifier:identifier]; 52 | GRXRelativeLayoutParams *params = (GRXRelativeLayoutParams *)view.grx_layoutParams; 53 | return [params hasParentRule:rule]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Example/Tests/GRXRelativeSnapshotTests.m: -------------------------------------------------------------------------------- 1 | #import "GRXTestHelper.h" 2 | 3 | #import 4 | #define EXP_SHORTHAND 5 | 6 | #import 7 | 8 | @interface GRXRelativeLayoutSnapshotTests : FBSnapshotTestCase 9 | 10 | @end 11 | 12 | @implementation GRXRelativeLayoutSnapshotTests 13 | 14 | - (void)setUp { 15 | [super setUp]; 16 | self.recordMode = NO; 17 | } 18 | 19 | - (void)testAlignParent { 20 | UIView *rootView = [GRXTestHelper rootViewForLayoutFileWithName:@"relative_parent.grx"]; 21 | [rootView layoutSubviews]; 22 | 23 | FBSnapshotVerifyLayer(rootView.layer, nil); 24 | } 25 | 26 | - (void)testAlignRelative { 27 | UIView *rootView = [GRXTestHelper rootViewForLayoutFileWithName:@"relative_other_views.grx"]; 28 | [rootView layoutSubviews]; 29 | 30 | FBSnapshotVerifyLayer(rootView.layer, nil); 31 | } 32 | 33 | - (void)testAlignWrapped { 34 | UIView *rootView = [GRXTestHelper rootViewForLayoutFileWithName:@"relative_wrapped.grx"]; 35 | [rootView layoutSubviews]; 36 | 37 | FBSnapshotVerifyLayer(rootView.layer, nil); 38 | } 39 | 40 | - (void)testAlignWrappedPush { 41 | UIView *rootView = [GRXTestHelper rootViewForLayoutFileWithName:@"relative_wrapped_push.grx"]; 42 | [rootView layoutSubviews]; 43 | 44 | FBSnapshotVerifyLayer(rootView.layer, nil); 45 | } 46 | 47 | - (void)testAlignWrappedAroundCenter { 48 | UIView *rootView = [GRXTestHelper rootViewForLayoutFileWithName:@"relative_wrapped_around_center.grx"]; 49 | [rootView layoutSubviews]; 50 | 51 | FBSnapshotVerifyLayer(rootView.layer, nil); 52 | } 53 | 54 | - (void)testRelativeToGone { 55 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"relative_to_gone.grx"]; 56 | UIView *rootView = inflater.rootView; 57 | [rootView layoutSubviews]; 58 | 59 | FBSnapshotVerifyLayer(rootView.layer, nil); 60 | } 61 | 62 | - (void)testRelativeInRelative { 63 | GRXLayoutInflater *inflater = [GRXTestHelper inflaterForFileWithName:@"relative_in_relative.grx"]; 64 | 65 | UIImageView *imageView = (UIImageView *) [inflater.rootView grx_findViewWithIdentifier:@"image"]; 66 | imageView.image = [GRXTestHelper imageWithName:@"lab.png"]; 67 | 68 | UITextView *title = (UITextView *) [inflater.rootView grx_findViewWithIdentifier:@"title"]; 69 | 70 | title.attributedText = [[NSAttributedString alloc] initWithString:@"This is a test title with two lines" 71 | attributes:@{NSFontAttributeName: [UIFont fontWithName:@"AmericanTypewriter" size:18]}]; 72 | 73 | UILabel *subtitle = (UILabel *) [inflater.rootView grx_findViewWithIdentifier:@"subtitle"]; 74 | subtitle.textColor = [UIColor darkGrayColor]; 75 | subtitle.text = @"This is a test subtitle"; 76 | subtitle.font = [UIFont boldSystemFontOfSize:10]; 77 | 78 | UITextView *message = (UITextView *) [inflater.rootView grx_findViewWithIdentifier:@"message"]; 79 | message.text = @"This is a text message and should be long enough to have at least two lines"; 80 | 81 | UIView *rootView = inflater.rootView; 82 | [rootView layoutSubviews]; 83 | 84 | FBSnapshotVerifyLayer(rootView.layer, nil); 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /Example/Tests/GRXTestHelper.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface GRXTestHelper : NSObject 4 | 5 | + (UIImage *)imageWithName:(NSString *)filename; 6 | + (GRXLayoutInflater *)inflaterForFileWithName:(NSString *)filename; 7 | + (GRXLayoutInflater *)inflaterForFileWithName:(NSString *)filename 8 | rootView:(UIView*)rootView; 9 | + (id)rootViewForLayoutFileWithName:(NSString *)filename; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /Example/Tests/GRXTestHelper.m: -------------------------------------------------------------------------------- 1 | #import "GRXTestHelper.h" 2 | 3 | @implementation GRXTestHelper 4 | 5 | + (UIImage *)imageWithName:(NSString *)filename { 6 | NSBundle *bundle = [NSBundle bundleForClass:self.class]; 7 | NSString *filepath = [bundle pathForResource:filename ofType:nil]; 8 | NSData *data = [NSData dataWithContentsOfFile:filepath]; 9 | return [UIImage imageWithData:data]; 10 | } 11 | 12 | + (GRXLayoutInflater *)inflaterForFileWithName:(NSString *)filename { 13 | GRXLayoutInflater *inflater = [[GRXLayoutInflater alloc] initWithFile:filename 14 | fromBundle:[NSBundle bundleForClass:self.class]]; 15 | if (inflater.parseError) { 16 | NSLog(@"Error inflating file %@: %@", filename, inflater.parseError); 17 | } 18 | return inflater; 19 | } 20 | 21 | + (GRXLayoutInflater *)inflaterForFileWithName:(NSString *)filename 22 | rootView:(UIView *)rootView { 23 | GRXLayoutInflater *inflater = [[GRXLayoutInflater alloc] initWithFile:filename 24 | fromBundle:[NSBundle bundleForClass:self.class] 25 | rootView:rootView]; 26 | if (inflater.parseError) { 27 | NSLog(@"Error inflating file %@: %@", filename, inflater.parseError); 28 | } 29 | return inflater; 30 | } 31 | 32 | + (id)rootViewForLayoutFileWithName:(NSString *)filename { 33 | GRXLayoutInflater *inflater = [self inflaterForFileWithName:filename]; 34 | NSAssert(inflater != nil, @"Invalid layout file: %@", filename); 35 | return inflater.rootView; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Example/Tests/GRXUIViewCategoriesTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "GRXTestHelper.h" 3 | 4 | #import 5 | #import 6 | #define EXP_SHORTHAND 7 | #import 8 | 9 | @interface GRXUIViewCategoriesTests : XCTestCase 10 | 11 | @end 12 | 13 | @implementation GRXUIViewCategoriesTests 14 | 15 | - (void)testLayoutParams { 16 | GRXLinearLayout *linear = [[GRXLinearLayout alloc] initWithFrame:CGRectZero]; 17 | UIView *viewInLinear = [[UIView alloc] initWithDefaultParamsInLayout:linear]; 18 | expect(viewInLinear.grx_layoutParams).to.beInstanceOf(GRXLinearLayoutParams.class); 19 | expect (^{[viewInLinear grx_relativeLayoutParams]; }).to.raiseAny(); 20 | 21 | GRXRelativeLayout *relative = [[GRXRelativeLayout alloc] initWithFrame:CGRectZero]; 22 | UIView *viewInRelative = [[UIView alloc] initWithDefaultParamsInLayout:relative]; 23 | expect(viewInRelative.grx_layoutParams).to.beInstanceOf(GRXRelativeLayoutParams.class); 24 | expect (^{[viewInRelative grx_linearLayoutParams]; }).to.raiseAny(); 25 | 26 | GRXLinearLayoutParams *linearParams = [GRXLinearLayoutParams alloc]; 27 | UIView *standalone = [[UIView alloc] initWithLayoutParams:linearParams]; 28 | expect (^{[relative addSubview:standalone]; }).to.raiseAny(); 29 | 30 | UIView *viewWithSameParams = [[UIView alloc] initWithFrame:CGRectZero]; 31 | viewWithSameParams.grx_layoutParams = linearParams; 32 | expect(viewWithSameParams.grx_layoutParams).notTo.beIdenticalTo(linearParams); 33 | } 34 | 35 | - (void)testVisibility { 36 | UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; 37 | view.grx_visibility = GRXVisibilityGone; 38 | expect(view.hidden).to.beTruthy(); 39 | 40 | view.grx_visibility = GRXVisibilityVisible; 41 | expect(view.hidden).to.beFalsy(); 42 | 43 | view.grx_visibility = GRXVisibilityHidden; 44 | expect(view.hidden).to.beTruthy(); 45 | 46 | view.hidden = NO; 47 | expect(view.grx_visibility).to.equal(GRXVisibilityVisible); 48 | view.hidden = YES; 49 | expect(view.grx_visibility).to.equal(GRXVisibilityHidden); 50 | } 51 | 52 | - (void)testIdentifiers { 53 | GRXLinearLayout *linear = [[GRXLinearLayout alloc] initWithFrame:CGRectZero]; 54 | linear.grx_layoutParams = [[GRXLayoutParams alloc] initWithSize:CGSizeMake(200, 200)]; 55 | linear.grx_identifier = @"linear"; 56 | 57 | UIView *viewInLinear = [[UIView alloc] initWithDefaultParamsInLayout:linear]; 58 | viewInLinear.grx_layoutParams.size = CGSizeMake(GRXMatchParent, GRXMatchParent); 59 | viewInLinear.grx_identifier = @"view"; 60 | 61 | GRXRelativeLayout *relative = [[GRXRelativeLayout alloc] initWithDefaultParamsInLayout:linear]; 62 | relative.grx_identifier = @"relative"; 63 | 64 | UIView *blueView = [[UIView alloc] initWithDefaultParamsInLayout:relative]; 65 | blueView.backgroundColor = [UIColor blueColor]; 66 | blueView.grx_identifier = @"blue"; 67 | 68 | UIView *redView = [[UIView alloc] initWithDefaultParamsInLayout:relative]; 69 | redView.backgroundColor = [UIColor redColor]; 70 | redView.grx_identifier = @"red"; 71 | 72 | expect(linear.grx_identifier).to.equal(@"linear"); 73 | expect([linear grx_findViewWithIdentifier:@"view"]).to.beIdenticalTo(viewInLinear); 74 | expect([linear grx_subviewForIdentifier:@"view"]).to.beIdenticalTo(viewInLinear); 75 | expect([linear grx_findViewWithIdentifier:@"relative"]).to.beIdenticalTo(relative); 76 | expect([linear grx_findViewWithIdentifier:@"blue"]).to.beIdenticalTo(blueView); 77 | expect([linear grx_subviewForIdentifier:@"blue"]).to.beNil(); 78 | expect([relative grx_subviewForIdentifier:@"blue"]).to.beIdenticalTo(blueView); 79 | expect([linear grx_findViewWithIdentifier:@"red"]).to.beIdenticalTo(redView); 80 | expect([linear grx_findViewWithIdentifier:@"green"]).to.beNil(); 81 | } 82 | 83 | - (void)testDebugProperties { 84 | GRXLinearLayout *linear = [[GRXLinearLayout alloc] initWithFrame:CGRectZero]; 85 | linear.grx_layoutParams = [[GRXLayoutParams alloc] initWithSize:CGSizeMake(200, 200)]; 86 | linear.grx_identifier = @"linear"; 87 | UIView *viewInLinear = [[UIView alloc] initWithDefaultParamsInLayout:linear]; 88 | viewInLinear.grx_layoutParams.size = CGSizeMake(GRXMatchParent, GRXMatchParent); 89 | viewInLinear.grx_identifier = @"view"; 90 | viewInLinear.backgroundColor = [UIColor redColor]; 91 | 92 | [linear layoutSubviews]; 93 | 94 | expect(linear.grx_debugDescription.length).to.beGreaterThan(0); 95 | expect(linear.grx_debugDescription).to.contain(@"200"); 96 | } 97 | 98 | // by default, an imageview measurement will keep the relation of the image 99 | // let's modify it using a block 100 | 101 | - (void)testMeasurementBlock { 102 | UIImage *image = [UIImage imageNamed:@"lab.png"]; 103 | expect(image.size.width).to.equal(image.size.height); 104 | 105 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; 106 | 107 | GRXMeasureSpec wspec = GRXMeasureSpecMake(CGFLOAT_MAX, GRXMeasureSpecAtMost); 108 | GRXMeasureSpec hspec = GRXMeasureSpecMake(CGFLOAT_MAX, GRXMeasureSpecAtMost); 109 | 110 | CGSize normalSize = [imageView grx_measuredSizeForWidthSpec:wspec heightSpec:hspec]; 111 | expect(normalSize.width).to.equal(normalSize.height); 112 | 113 | [imageView grx_setMeasurementBlock:^CGSize (GRXMeasureSpec wspec, GRXMeasureSpec hspec) { 114 | return CGSizeMake(200, 300); 115 | }]; 116 | 117 | CGSize modSize = [imageView grx_measuredSizeForWidthSpec:wspec heightSpec:hspec]; 118 | expect(modSize.width).to.equal(200); 119 | expect(modSize.height).to.equal(300); 120 | } 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /Example/Tests/GRXUIViewFrameUnitTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | 5 | #define EXP_SHORTHAND 6 | #import 7 | 8 | @interface GRXUIViewFrameUnitTests : XCTestCase 9 | 10 | @end 11 | 12 | @implementation GRXUIViewFrameUnitTests 13 | 14 | 15 | - (void)testFrameMethods { 16 | UIView *v = [[UIView alloc] initWithFrame:CGRectMake(1, 2, 3, 4)]; 17 | expect(v.origin.x).to.equal(1); 18 | expect(v.origin.y).to.equal(2); 19 | 20 | expect(v.size.width).to.equal(3); 21 | expect(v.size.height).to.equal(4); 22 | 23 | // setting origin and size 24 | v.size = CGSizeMake(200, 300); 25 | v.origin = CGPointMake(20, 50); 26 | expect(v.left).to.equal(20); 27 | expect(v.top).to.equal(50); 28 | expect(v.right).to.equal(220); 29 | expect(v.bottom).to.equal(350); 30 | expect(v.width).to.equal(200); 31 | expect(v.height).to.equal(300); 32 | 33 | v.width = 20; 34 | v.height = 30; 35 | expect(v.size.width).to.equal(20); 36 | expect(v.size.height).to.equal(30); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXCombinationSnapshotTests/testRecursiveInflation@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXCombinationSnapshotTests/testRecursiveInflation@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testHorizontal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testHorizontal@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testHorizontalInVertical@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testHorizontalInVertical@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testHorizontalWeighted@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testHorizontalWeighted@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testHorizontalWeightedWithLabel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testHorizontalWeightedWithLabel@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testVertical@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testVertical@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testVerticalWeighted@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXLinearSnapshotTests/testVerticalWeighted@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignParent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignParent@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignRelative@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignRelative@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignWrapped@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignWrapped@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignWrappedAroundCenter@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignWrappedAroundCenter@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignWrappedPush@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testAlignWrappedPush@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testRelativeInRelative@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testRelativeInRelative@2x.png -------------------------------------------------------------------------------- /Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testRelativeToGone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gskbyte/GranadaLayout/0641a9fe6975e38d7e5c5cca622397b21f17e122/Example/Tests/ReferenceImages/GRXRelativeLayoutSnapshotTests/testRelativeToGone@2x.png -------------------------------------------------------------------------------- /Example/Tests/UIColor+GRXHexParseTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #define EXP_SHORTHAND 4 | #import 5 | 6 | 7 | @interface UIColor_GRXHexParseTests : XCTestCase 8 | 9 | @end 10 | 11 | @implementation UIColor_GRXHexParseTests 12 | 13 | - (void)testHexIntegers { 14 | CGFloat red, green, blue, alpha; 15 | 16 | UIColor *redColor = [UIColor grx_colorFromRGBHex:0xFF0000]; 17 | [redColor getRed:&red green:&green blue:&blue alpha:&alpha]; 18 | expect(red).to.equal(1); 19 | expect(green).to.equal(0); 20 | expect(blue).to.equal(0); 21 | expect(alpha).to.equal(1); 22 | 23 | UIColor *orangeColor = [UIColor grx_colorFromRGBHex:0xffa500]; 24 | [orangeColor getRed:&red green:&green blue:&blue alpha:&alpha]; 25 | expect(red).to.equal(1); 26 | expect(green).to.equal(165/255.0); 27 | expect(blue).to.equal(0); 28 | expect(alpha).to.equal(1); 29 | 30 | UIColor *royalBlue = [UIColor grx_colorFromRGBHex:0x4169e1]; 31 | [royalBlue getRed:&red green:&green blue:&blue alpha:&alpha]; 32 | expect(red).to.equal(65/255.0); 33 | expect(green).to.equal(105/255.0); 34 | expect(blue).to.equal(225/255.0); 35 | expect(alpha).to.equal(1); 36 | } 37 | 38 | - (void)testInvalidStrings { 39 | expect(^{ 40 | [UIColor grx_colorFromRGBHexString:@"#1"]; 41 | }).to.raise(@"Invalid color value"); 42 | 43 | expect(^{ 44 | [UIColor grx_colorFromRGBHexString:@"#dasdasdasdasd da"]; 45 | }).to.raise(@"Invalid color value"); 46 | } 47 | 48 | - (void)testValidRRGGBBStrings { 49 | CGFloat red, green, blue, alpha; 50 | 51 | UIColor *orangeColor = [UIColor grx_colorFromRGBHexString:@"#ffa500"]; 52 | [orangeColor getRed:&red green:&green blue:&blue alpha:&alpha]; 53 | expect(red).to.equal(1); 54 | expect(green).to.equal(165/255.0); 55 | expect(blue).to.equal(0); 56 | expect(alpha).to.equal(1); 57 | 58 | UIColor *royalBlue = [UIColor grx_colorFromRGBHexString:@"#4169e1"]; 59 | [royalBlue getRed:&red green:&green blue:&blue alpha:&alpha]; 60 | expect(red).to.equal(65/255.0); 61 | expect(green).to.equal(105/255.0); 62 | expect(blue).to.equal(225/255.0); 63 | expect(alpha).to.equal(1); 64 | } 65 | 66 | - (void)testValidAARRGGBBStrings { 67 | CGFloat red, green, blue, alpha; 68 | 69 | UIColor *orangeColor = [UIColor grx_colorFromRGBHexString:@"#49ffa500"]; 70 | [orangeColor getRed:&red green:&green blue:&blue alpha:&alpha]; 71 | expect(red).to.equal(1); 72 | expect(green).to.equal(165/255.0); 73 | expect(blue).to.equal(0); 74 | expect(alpha).to.equal(73/255.0); 75 | 76 | UIColor *royalBlue = [UIColor grx_colorFromRGBHexString:@"#254169e1"]; 77 | [royalBlue getRed:&red green:&green blue:&blue alpha:&alpha]; 78 | expect(red).to.equal(65/255.0); 79 | expect(green).to.equal(105/255.0); 80 | expect(blue).to.equal(225/255.0); 81 | expect(alpha).to.equal(37/255.0); 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://www.rubygems.org' 2 | 3 | gem "cocoapods", '~> 0.39' 4 | gem "slather", '~> 2.0.0' 5 | gem "xcpretty" 6 | gem "rake" 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://www.rubygems.org/ 3 | specs: 4 | activesupport (4.2.5.1) 5 | i18n (~> 0.7) 6 | json (~> 1.7, >= 1.7.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | claide (0.9.1) 11 | clamp (0.6.5) 12 | cocoapods (0.39.0) 13 | activesupport (>= 4.0.2) 14 | claide (~> 0.9.1) 15 | cocoapods-core (= 0.39.0) 16 | cocoapods-downloader (~> 0.9.3) 17 | cocoapods-plugins (~> 0.4.2) 18 | cocoapods-search (~> 0.1.0) 19 | cocoapods-stats (~> 0.6.2) 20 | cocoapods-trunk (~> 0.6.4) 21 | cocoapods-try (~> 0.5.1) 22 | colored (~> 1.2) 23 | escape (~> 0.0.4) 24 | molinillo (~> 0.4.0) 25 | nap (~> 1.0) 26 | xcodeproj (~> 0.28.2) 27 | cocoapods-core (0.39.0) 28 | activesupport (>= 4.0.2) 29 | fuzzy_match (~> 2.0.4) 30 | nap (~> 1.0) 31 | cocoapods-downloader (0.9.3) 32 | cocoapods-plugins (0.4.2) 33 | nap 34 | cocoapods-search (0.1.0) 35 | cocoapods-stats (0.6.2) 36 | cocoapods-trunk (0.6.4) 37 | nap (>= 0.8, < 2.0) 38 | netrc (= 0.7.8) 39 | cocoapods-try (0.5.1) 40 | colored (1.2) 41 | escape (0.0.4) 42 | fuzzy_match (2.0.4) 43 | i18n (0.7.0) 44 | json (1.8.3) 45 | mini_portile2 (2.0.0) 46 | minitest (5.8.4) 47 | molinillo (0.4.3) 48 | nap (1.1.0) 49 | netrc (0.7.8) 50 | nokogiri (1.6.7.2) 51 | mini_portile2 (~> 2.0.0.rc2) 52 | rake (10.3.2) 53 | slather (2.0.0) 54 | clamp (~> 0.6) 55 | nokogiri (~> 1.6.3) 56 | xcodeproj (>= 0.28.2, < 1.1.0) 57 | thread_safe (0.3.5) 58 | tzinfo (1.2.2) 59 | thread_safe (~> 0.1) 60 | xcodeproj (0.28.2) 61 | activesupport (>= 3) 62 | claide (~> 0.9.1) 63 | colored (~> 1.2) 64 | xcpretty (0.1.7) 65 | 66 | PLATFORMS 67 | ruby 68 | 69 | DEPENDENCIES 70 | cocoapods (~> 0.39) 71 | rake 72 | slather (~> 2.0.0) 73 | xcpretty 74 | 75 | BUNDLED WITH 76 | 1.11.2 77 | -------------------------------------------------------------------------------- /GranadaLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "GranadaLayout" 3 | s.version = "0.4.1" 4 | s.summary = "A simple layout system for iOS providing Android-like linear and relative layouts." 5 | 6 | s.description = <<-DESC 7 | GranadaLayout tries to emulate the Android Layout system and bring as much as possible from its functionality to the iOS world. 8 | It includes a relative layout to position and size views relative to each other and a linear layout to stack views. 9 | A layout inflater is also provided, so that layouts can be defined in simple JSON files. 10 | DESC 11 | 12 | s.homepage = "https://github.com/gskbyte/GranadaLayout" 13 | s.license = "Apache License, Version 2.0" 14 | 15 | s.author = { "Jose Alcalá Correa" => "jose.alcala.correa@gmail.com" } 16 | s.platform = :ios, "6.0" 17 | 18 | s.source = { :git => "https://github.com/gskbyte/GranadaLayout.git", :tag => "0.4.1" } 19 | s.social_media_url = "http://twitter.com/gskbyte" 20 | 21 | s.source_files = "Classes", "Classes/**/*.{h,m}" 22 | s.exclude_files = "Example/*" 23 | s.private_header_files = "Classes/Private/*.h" 24 | end 25 | -------------------------------------------------------------------------------- /GranadaLayout.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jose Alcalá Correa 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 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "Bootstraps the repo" 2 | task :bootstrap do 3 | sh 'bundle' 4 | sh 'cd Example && bundle exec pod install' 5 | end 6 | 7 | desc "Runs the specs [EMPTY]" 8 | task :spec do 9 | sh 'xcodebuild -workspace GranadaLayout.xcworkspace -derivedDataPath "./build" -scheme \'Example\' -configuration Debug -destination platform=\'iOS Simulator\',OS=9.2,name=\'iPhone 5s\' clean build test -sdk iphonesimulator | xcpretty -sc && exit ${PIPESTATUS[0]}' 10 | end 11 | --------------------------------------------------------------------------------