GithubHelp home page GithubHelp logo

yolayout / yolayout Goto Github PK

View Code? Open in Web Editor NEW
34.0 34.0 10.0 463 KB

A frame-based layout framework. Avoid Interface Builder and AutoLayout and write your layouts in code.

License: MIT License

Objective-C 97.33% Swift 2.02% Ruby 0.65%

yolayout's Introduction

YOLayout

Version Platform

A frame-based layout framework that works with UIView, NSView, CALayer, any anything else that implements setFrame:. Avoid Interface Builder and Auto Layout and take full control over your layouts.

Usage

Here is an example of a view with an image, title label, and multi-line description label with a dynamic height.

// TableViewCellView.h
#import <YOLayout/YOLayout.h>

@interface TableViewCellView : YOView // Subclass YOView
@end

// TableViewCellView.m
@interface TableViewCellView ()
@property UILabel *titleLabel;
@property UILabel *descriptionLabel;
@end

@implementation TableViewCellView

- (void)viewInit {
  [super viewInit];
  UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"information.png"]];
  [self addSubview:imageView];

  self.titleLabel = [[UILabel alloc] init];
  self.titleLabel.numberOfLines = 1;
  self.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
  self.titleLabel.font = [UIFont boldSystemFontOfSize:16];
  [self addSubview:self.titleLabel];

  self.descriptionLabel = [[UILabel alloc] init];
  self.descriptionLabel.font = [UIFont systemFontOfSize:16];
  self.descriptionLabel.numberOfLines = 0; // Multi-line label (word wrapping
  self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
  [self addSubview:self.descriptionLabel];

  YOSelf yself = self;
  self.viewLayout = [YOLayout layoutWithLayoutBlock:^CGSize(id<YOLayout> layout, CGSize size) {
    CGFloat x = 10;
    CGFloat y = 10;

    // imageView's size is set by the UIImage when using initWithImage:
    CGRect imageViewFrame = [layout setOrigin:CGPointMake(x, y) view:imageView options:0];
    x += imageViewFrame.size.width + 10;

    y += [layout sizeToFitVerticalInFrame:CGRectMake(x, y, size.width - x - 10, 0) view:yself.titleLabel].size.height;
    y += [layout sizeToFitVerticalInFrame:CGRectMake(x, y, size.width - x - 10, 1000) view:yself.descriptionLabel].size.height;

    // Ensure the y position is at least as high as the image view
    y = MAX(y, (imageViewFrame.origin.y + imageViewFrame.size.height));
    y += 10;

    return CGSizeMake(size.width, y);
  }];
}

TableViewCell.png

  • viewLayout performs layout and returns the size it requires. This unifies layoutSubviews and sizeThatFits: into a single method. This example returns the same width passed in but with a variable height. To make a view fill all size available you can return size (that was passed in).
  • viewInit is a unified initializer. initWithFrame: and initWithCoder: both call this method.
  • Layouts are block based allowing you to capture local references.
  • YOSelf weak reference helps prevent self retain cycle.
  • Since layout is block based, you can create multiple views with subviews and layouts all in a single method.

Example Project

The best way to follow and learn YOLayout is by seeing it in action. Open the example project: YOLayoutExample. It contains both a iOS and OSX targets.

NSView

NSView and Cocoa is supported. YOLayout makes layout consistent for both UIKit and Cocoa.

FAQ

Why do layout in code when we have Interface Builder?

Have you ever tried to do a significant project in IB? It's maddening. Small tweaks can break Auto Layout constraints in unexpected ways. Plus, Interface Builder isn't great for projects with multiple committers; git-merging storyboards and xibs can be very difficult. Ultimately we're coders; we like to do things with code.

OK, so why not just use AutoLayout in code?

Auto Layout in code is a step in the right direction but carries several disadvantages to frame-based layouts:

  • Auto Layout is generally slower than frame-based layouts. Here's an excellent writeup on the performance disadvantage of Auto Layout.
  • Auto Layout can also be tricky to animate, since you need to animate all the relevant constraints.
  • Auto Layout debugging can be hellish because there is little transparency into what is happening on the inside. With YOLayout, you have full visibility into the layout process. If your layout breaks, just step through the layout code and see what went wrong! Easy.

So why not just write your own layout code in layoutSubviews?

The core problem with writing your layout code in layoutSubviews is the massive duplication of layout code you have to write in both layoutSubviews and sizeThatFits:. When you have multiple subviews that themselves may size themselves dynamically, this code gets repetitive and tricky to maintain. You can't call layoutIfNeeded in sizeThatFits: without the risk of infinite recursion if a superview uses your sizeThatFits: method in its layoutSubviews.

Will it lay out views that aren't in the view hierarchy?

You probably weren't asking this question, but you should have! One of the neat things about YOLayout is that layouts also work for views that get drawn in drawRect:. Because YOLayout works independently from the view hierarchy, you can easily switch between adding a subview to the hierarchy, or just drawing it in drawRect: without having to change your layout code. Try to do that with Auto Layout!

YOLayout is a great fit for custom drawn controls. Views that render in drawRect: can use the IB_DESIGNABLE attribute to be rendered live in interface builder. Open up the example project (pod try YOLayout) and take a look at DrawableView.m and DrawableView.xib.

What is viewInit?

- (void)viewInit; is the unified initializer for views. With YOLayout there is no need to specify a default frame or handle initialization differently if the view is from Interface Builder or from code.

Do I have to use YOLayout for all my views?

Nope. If your layout is really simple, or it doesn't have dynamic sizing, just use layoutSubviews (UIKit) or layout (AppKit) like you normally would. YOLayout doesn't do anything special to override behavior and is compatible with existing layout methods.

Disadvantages of using YOLayout

YOLayout, like most things, has trade-offs. We like using it especially for really complex layouts with lots of different alignments and for things that are dynamically sized.

But there are downsides:

  • You might find yourself hardcoding pixel values for things like padding and insets.
  • With simple layouts AutoLayout can be easier.
  • YOLayout is a custom framework whereas Interface Builder and AutoLayout are part of Apple and their SDK, and so are better supported.

And probably many more, feel free to tell us why you hate it, by submitting an issue.

Dependencies

There are none!

Installation

YOLayout is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "YOLayout"

Box Model

Using YOLayout we implemented a box model (YOVBox and YOHBox), that supports some basic layout properties such as spacing, insets, horizontalAlignment, minSize, and maxSize.

YOVBox is for vertical layout, and YOHBox is for horizontal layout. For example:

#import <YOLayout/YOBox.h>

@interface BoxExample : YOVBox
@end

@implementation BoxExample

- (void)viewInit {
  [super viewInit];
  self.backgroundColor = UIColor.whiteColor;

  // Labels stacked vertically (VBox)
  YOVBox *labelsView = [YOVBox box:@{@"spacing": @"10", @"insets": @"20,20,0,20"}];
  {
    UILabel *label1 = [[UILabel alloc] init];
    label1.text = @"Box Model Test";
    label1.font = [UIFont boldSystemFontOfSize:20];
    label1.textAlignment = NSTextAlignmentCenter;
    [labelsView addSubview:label1];

    UILabel *label2 = [[UILabel alloc] init];
    label2.font = [UIFont systemFontOfSize:14];
    label2.numberOfLines = 0;
    label2.text = @"PBR&B Intelligentsia shabby chic. Messenger bag flexitarian cold-pressed VHS 90's. Tofu chillwave pour-over Marfa cold-pressed, kogi bespoke High Life semiotics readymade authentic wolf sriracha craft beer. Next level direct trade shabby chic vegan cliche. Mlkshk butcher church-key cornhole 3 wolf moon, YOLO cold-pressed cronut";
    [labelsView addSubview:label2];
  }
  [self addSubview:labelsView];

  // Buttons with min size right aligned (HBox)
  YOHBox *buttons = [YOHBox box:@{@"spacing": @"20", @"insets": @"10,20,10,20", @"minSize": @"50,50", @"horizontalAlignment": @"right"}];
  {
    UIButton *button1 = [self buttonWithText:@"A"];
    [buttons addSubview:button1];
    UIButton *button2 = [self buttonWithText:@"B"];
    [buttons addSubview:button2];
    UIButton *button3 = [self buttonWithText:@"C"];
    [buttons addSubview:button3];
  }
  [self addSubview:buttons];

  // Buttons centered horizontally (HBox)
  YOHBox *buttonsCenter = [YOHBox box:@{@"spacing": @"20", @"horizontalAlignment": @"center"}];
  {
    [buttonsCenter addSubview:[self buttonWithText:@"D"]];
    [buttonsCenter addSubview:[self buttonWithText:@"E"]];
    [buttonsCenter addSubview:[self buttonWithText:@"F"]];
  }
  [self addSubview:buttonsCenter];
}

- (UIButton *)buttonWithText:(NSString *)text {
  UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
  button.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
  [button setTitle:text forState:UIControlStateNormal];
  button.layer.borderWidth = 1.0;
  button.layer.borderColor = UIColor.grayColor.CGColor;
  button.contentEdgeInsets = UIEdgeInsetsMake(5, 10, 5, 10);
  return button;
}

@end

BoxView.png

Border Layouts

For border layouts, you can use YOVBorderLayout or YOHBorderLayout. For example,

@implementation BorderView

- (void)viewInit {
  [super viewInit];
  self.backgroundColor = UIColor.blackColor;

  UILabel *topView = [self label:@"Top" backgroundColor:UIColor.redColor];
  [self addSubview:topView];

  YOView *centerView = [YOView view];
  {
    UILabel *leftLabel = [self label:@"Left" backgroundColor:UIColor.greenColor];
    [centerView addSubview:leftLabel];
    UILabel *centerLabel = [self label:@"Center" backgroundColor:UIColor.blueColor];
    [centerView addSubview:centerLabel];
    UILabel *rightLabel = [self label:@"Right" backgroundColor:UIColor.cyanColor];
    [centerView addSubview:rightLabel];

    centerView.viewLayout = [YOHBorderLayout layoutWithCenter:centerLabel left:@[leftLabel] right:@[rightLabel] insets:UIEdgeInsetsZero spacing:10];
  }
  [self addSubview:centerView];

  UILabel *bottomView = [self label:@"Bottom" backgroundColor:UIColor.orangeColor];
  [self addSubview:bottomView];

  self.viewLayout = [YOVBorderLayout layoutWithCenter:centerView top:@[topView] bottom:@[bottomView] insets:UIEdgeInsetsMake(20, 20, 20, 20) spacing:10];
}

- (UILabel *)label:(NSString *)text backgroundColor:(UIColor *)backgroundColor {
  UILabel *label = [[UILabel alloc] init];
  label.font = [UIFont systemFontOfSize:20];
  label.text = text;
  label.numberOfLines = 0;
  label.textAlignment = NSTextAlignmentCenter;
  label.backgroundColor = backgroundColor;
  label.textColor = [UIColor whiteColor];
  return label;
}

@end

BorderView.png

License

YOLayout is available under the MIT license. See the LICENSE file for more info.

yolayout's People

Contributors

gabriel avatar jmah avatar johnboiles avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

yolayout's Issues

Write tests

No need to pull in anything heavy like view tests --we could just write tests that run layouts and make sure the frames are set correctly. We should write tests for all the options and convenience methods.

- (CGSize)sizeThatFits:(CGSize)size view:(id)view options:(YOLayoutOptions)options

Thinking that it might be nice to have a sizing method, similar to sizeThatFits: except that accepts all the YOLayoutOptions. The use case I'm hitting is this: I have a cell with a label that animates in and out by changing its height. When animating the cell from 0 height, I need to know how tall the label needs to be so that I can set the height to animate to. However, I don't want the label to size to it's full height, but instead I want it to reveal from it's position in the center of the cell.

Currently I achieve this by two calls to setFrame:view:options:. The first figures out what size the label should be. Then the second actually sets the frame to the height it can be based on the current size of the cell. This works, but it seems a little clunky to call setFrame: twice.

Maybe the method could be called. sizeThatFits:(CGSize)size view:(id)view options:(YOLayoutOptions)options

Clang can't detect retain cycles due to layout block

Because the layoutBlock belongs to the YOLayout class, clang is unable to detect that references to the view's self in the layoutBlock create a retain cycle that looks like this: YOView instance -> YOLayout instance -> Layout Block -> YOView instance. For example, the following does not raise a compile-time warning about the block retaining self:

@implementation MyView

- (void)viewInit {
    self.layout = [YOLayout layoutWithLayoutBlock:^CGSize(id<YOLayout> layout, CGSize size) {
        // Retains self, does not raise compile time warning
        [layout setFrame:CGRectMake:(0, 0, size.width, size.height) view:self.mySubview];
    }];
}

@end

However, a structure like this would raise a warning

@implementation MyView

- (void)viewInit {
    self.layoutBlock = ^CGSize(id<YOLayout> layout, CGSize size) {
        // Retains self, does raise a compile time warning
        [layout setFrame:CGRectMake:(0, 0, size.width, size.height) view:self.mySubview];
    };
}

@end

I'm not sure what the right solution is here. I don't think there's a way to tip off clang that the YOLayout is retaining the layoutBlock and thus could create a retain loop if self is referenced.

We could move towards the self.layoutBlock syntax and use [self setLayoutBlock: in order to get the autocompletion of ^CGSize(id<YOLayout> layout, CGSize size), but it's a pretty big directional change.

More asserts to tell you when you're doing stupid things

We should add some assertions to handle undefined combinations of options and other errors.

For example YOLayoutOptionsAlignCenterVertical and YOLayoutOptionsAlignBottom can't work together. Neither can 'YOLayoutOptionsAlignCenterHorizontalandYOLayoutOptionsAlignRight`.

Not sure if it makes sense for YOLayoutOptionsConstrainSizeMaintainAspectRatio to be set along with the other constrain options (not totally sure on this one).

Also we should assert that the view passed into setFrame:view:options isn't the same view that's being laid out. Maybe with something like this:

    if ([view respondsToSelector:@selector(layout)]) {
        NSAssert(self != [view layout], @"Attempt to set a view's frame from within the view's layout block.");
    }

Are YOCGUtils really necessary?

From @jmah:

Are the custom CGPoint/CGSize/CGRect equality methods really necessary? There are built-ins like CGSizeEqualToSize. The floating-point comparison stuff is common, but I don't know of a case when it's actually necessary.

Sizes/origins are almost always integers, and they're not greater than 2^24, so the FP comparison is probably not necessary. Only thing I can think of is if you were using fractional origins or sizes, maybe if you were laying out drawables and wanted to pixel align something instead of point align it?

Maybe we should renamed sharedInit.

sharedInit is "shared" because its working around decades of initWithFrame, initWithCoder bullshit but would be nice to just hide that blemish and call it something like, umm.... viewInit or viewLoad.

Add screen-scale based rounding

Might make sense to add a YOLayoutOption that takes the screen scale and rounds the resulting CGRect to the nearest screen pixel. Perhaps call it YOLayoutOptionPixelRounded

Maybe swizzle layoutSubviews, setNeedsLayout, and sizeThatFits: to avoid subclassing

Feedback I got from a fellow dev was that he's super hesitant to subclass UIView. I think I understand; I think I'd feel the same if I hadn't been a part of creating YOLayout.

Opening this issue to discuss the possibility of swizzling in the required layout/sizing methods (probably setFrame:, layoutSubviews, sizeThatFits:, setNeedsLayout) and implementing layout as a category of UIView. Benefit of this is that it should work on UIControl, UIButton, UITableViewCell & UIView right out of the box.

I think we'd still keep YOView subclass around --the viewInit method itself is useful enough to merit this subclass. But we'd also provide the option of adding a YOLayout to an arbitrary UIView subclass.

What do you think?

Create a benchmark of YOLayout vs AutoLayout

YOLayout is almost certainly faster than AutoLayout. Hard to imagine a Cassowary-style constraint resolution running faster than simple math.

It'd be great to know how much faster it is. Could be a great launching point for us to be able to say 'up to 500% faster than AutoLayout' or something like that.

Try out YOLayout with Facebook's AsyncDisplayKit

YOLayout could be a good companion to Facebook's AsyncDisplayKit. Doesn't look like it'd be too hard to adapt since ASDisplayNode has most of the relevant properties/methods.

We'll have to add support for ASDisplayNode's - (CGSize)measure:(CGSize)constrainedSize method in addition to sizeThatFits:

YOLayout could be a great fit here, since display nodes can't use autolayout.

Opening this method to try it out. Having all your layout and rendering happening in the background sounds killer.

Investigate interoperability with Auto Layout

Question from @jmah:

Does this integrate with autolayout โ€” that is, can you use a view (with subviews) that lays out with YOLayout inside an autolayout hierarchy, and vice-versa?

The layout will certainly work on views in an autolayout view hierarchy. However, I bet sizing doesn't work. It all depends on whether autolayout uses sizeThatFits: for the intrinsic size if no intrinsic size is specified.

We should investigate this. Interoperability with autolayout would be a big selling point.

Gifs in README.md

We should make some gifs of cool layouts in action. Maybe make some cool IB_DESIGNABLE layouts and make gifs of them expanding and contracting in width.

Center doesn't seem to work

[layout setFrame:CGRectMake(0, 0, 160, 48) inRect:CGRectMake(10, 0, 300, 100) view:yself.button options:YOLayoutOptionsAlignCenter];

And variations.

It should be easy to center something. Options are so hard to use, maybe we should hide them.

Centering should be like:

// Height is the same for size and rect so its centering horizontally
[layout centerWithSize:CGSizeMake(160, 44) inRect:CGRectMake(10, 10, 300, 44) view:button];

// Centering both vertically and horizontally because width and height are different
[layout centerWithSize:CGSizeMake(160, 44) inRect:CGRectMake(10, 10, 300, 100) view:button];

View sizeThatFits has no idea about options.

If we are sizing to fit horizontal or vertical we call [view sizeThatFits:..] but it has no idea how its sizing (though usually vertical).

Options:

  • Create new method sizeThatFits:options:
  • If size.width is 0, then we are sizing horizontal, size.height is 0 then vertical. If we do this though, we can't set a max width or height and size to fit at the same time.

2nd option is easier and requires no work. We might want to do the first option down the road?

We should cleanup YOLayout methods.

Lots of methods I'm not sure if we need them or what they really do?

We could nuke:
- (CGRect)setFrame:(CGRect)frame view:(id)view needsLayout:(BOOL)needsLayout;

What do these really do? I forget:
- (CGRect)setFrameInRect:(CGRect)inRect view:(id)view;
- (CGRect)setX:(CGFloat)x frame:(CGRect)frame view:(id)view; (Why not user setOrigin?)
- (CGRect)setY:(CGFloat)y frame:(CGRect)frame view:(id)view; (Why not user setOrigin?)

And some deprecated ones.

We should do some examples/documentation for:

- (CGRect)setFrame:(CGRect)frame inRect:(CGRect)inRect view:(id)view options:(YOLayoutOptions)options;

Cause that can be useful sometimes but it's hard to grok.

Should we set a default layout?

It would be cool to have a default layout set for views with only a single view, and for multiple views, laid out vertically.

If subviews.count == 1:

self.layout = [YOLayout layoutWithLayoutBlock:^(id<YOLayout> layout, CGSize size) {
  [layout setFrame:CGRectMake(0, 0, size.width, size.height) view:view];
  return size;  
}];

If subviews.count > 1:

self.layout = [YOLayout layoutWithLayoutBlock:^(id<YOLayout> layout, CGSize size) {
  CGFloat y = 0;
  for (id subview in view.subviews) {
    y += [layout sizeToFitVerticallyInFrame:(0, y, size.width, 0) view:subview].size.height;
  }
  return CGSizeMake(size.width, y);
}];

Maybe even allow insets.

We could also define these blocks and allow people to use them (manually):

self.layout = [YOLayout layoutWithLayoutBlock:YOLayoutVerticalBlock(insets)];

Example: circular layout

Create an example that demonstrates laying out sub views in a circle around a central point. This is something you can't do with auto layout but can do with YOLayout + math.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.