LKLayoutLibrary has been written to allow iOS developers designing applications with dynamic and complex layouts.
It provides some of the default layout functionality such as linear- and stack layout.
LKLayoutLibrary has been designed for being modular in terms of cooperating with UIKit and being extensible with an easy API for writing custom layouts with custom layout properties.
There are two different approches to work with LKLayoutLibrary:
- Create a custom view class (recommended)
- Use the layout within a view controller
LKLayout library has been designed to be used within UIView subclasses.
Here is an example of view with a vertical linear layout:
MKVerticalLayoutView.h:
@interface MKVerticalLayoutView : UIView
@property (weak, nonatomic, readonly) UILabel *titleLabel;
@property (weak, nonatomic, readonly) UITextView *descriptionTextView;
@end
MKVerticalLayoutView.m:
#import "MKVerticalLayoutView"
@interface MKVerticalLayoutView ()
@property (strong, nonatomic) MKLinearLayout *layout;
@property (weak, nonatomic) MKLinearLayoutItem *titleItem;
@property (weak, nonatomic) MKLinearLayoutItem *descriptionItem;
@end
@implementation
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_layout = [[MKLinearLayout alloc] init];
_layout.view = self;
_layout.orientation = MKLinearLayoutOrientationVertical;
[self createLayout];
}
return self;
}
- (void)createLayout
{
// Adds the view to the layout and automatically to the layouts associated view
UILabel *titleLabel = [[UILabel alloc] init];
self.titleItem = [self.layout addSubview:titleLabel];
// Once the layout items exists, it can be used to configure layout specifics such as paddings or sizes
self.titleItem.padding = UIEdgeInsetsMake(1.0f, 1.0f, 1.0f, 1.0f);
// Specifies 30 px height but streches the width to the full available layout size so it matches the parent view
self.titleItem.size = CGSizeMake(kLKLayoutItemSizeValueMatchParent, 30.0f);
UITextView *descriptionTextView = [[UITextView alloc] init];
self.descriptionItem = [self.layout addSubview:descriptionTextView];
// Specifies that the item gots all available left space.
// Weight and size can be used at the same time. See LKLayoutItem and MKLinearLayoutItem for further details.
self.titleItem.weight = 1.0f;
}
- (void)layoutSubviews
{
[self.layout layout];
}
- (UILabel *)titleLabel
{
return (UILabel *)self.titleLabelItem.view;
}
- (UITextView *)descriptionTextView
{
return (UITextView *)self.descriptionItem.view;
}
@end
LKLayout can be used in any context with UIKit but UIView's method - (void)layoutSubviews perfectly manages the times when a view needs to be layouted. Of course - (void)layout can be called at any time.
LKLayout also support sublayouts:
// Create a layout instance
MKStackLayout *iconLayout = [[MKStackLayout alloc] init];
UIImageView *backgroundPatternImage = [[UIImageView alloc] init];
// Work with the sublayout as usal
MKStackLayoutItem *backgroundPatternImage = [iconLayout addSubview:iconLayout];
// Add the sublayout to the root layout
MKLinearLayoutItem *iconLayoutLayoutItem = [self.layout addSublayout:iconLayout];
LKLayoutLibrary is all about LKLayoutItem and its related subclasses.
Imagine a layout as a collection of boxes that can contain a single view or a layout identified as sublayout. These boxes will be represent as LKLayoutItem. Every view or sublayout of these boxes will be positioned by the layouts logic, so for example every boxes position follows another box frame in a linear layout or they overlap in a stack layout.
However, LKLayoutItem specifies properties to adjust and configure the boxes layout behavior.
All properties specified directly in LKLayoutItem will and needs to be supported by every layout implementation. Layout specific subclasses of LKLayoutItem can provide per layout configuration properties.
This section introduces the most important properties and their consistent behavior through all layouts.
A size is a specified length in a vertical and horizontal direction. It can only be affected by layout items padding. Otherwise, the views size must match the layout items size.
This configuration of an item must result in a view.bounds.size with a width of 10 points and a height of 10 points:
item.size = CGSizeMake(10.0f, 10.0f);
Padding is the only property that can and must affect the resulting bounds of a layout items view or sublayout.
The following configuration of an item must result in a view.bounds.size with a width of 9 points and height of 9 points:
item.size = CGSizeMake(10.0f, 10.0f);
item.padding = UIEdgeInsetsMake(0.0f, 0.0f, 1.0f, 1.0f); // Just shrink the view by reducing width and height
The following configuration of an item must result in a view.bounds.size with a width of 9 points and height of 9 points must move the resulting frame by x of 1 point and y of 1 point:
item.size = CGSizeMake(10.0f, 10.0f);
item.padding = UIEdgeInsetsMake(1.0f, 1.0f, 0.0f, 0.0f); // Shrink the view and move it by 1 point on x and y axis
The next configuration is a combination of the previous ones and must result in a view.bounds.size with a width of 8 points and height of 8 points and must move the resulting frame by x of 1 point and y of 1 point:
item.size = CGSizeMake(10.0f, 10.0f);
item.padding = UIEdgeInsetsMake(1.0f, 1.0f, 1.0f, 1.0f); // Shrink the view on all edges and move it
Padding should be assumed to an inset to an outer box. If it is just necessary to move a item view to ensure offsets to a specific edge layout item, offsets could be a more reasonable approach. Here an example:
This is a padding definition that should be represented as offset: So these lines:
UIEdgeInsets padding = UIEdgeInsetsMake(0.0f, 0.0f, 5.0f, 5.0f); // Define padding to calculate the overall item size
item.gravity = LKLayoutGravityTop | LKLayoutGravityRight;
item.padding = padding;
item.size = CGSizeMake(30.0f + padding.right, 30.0f + padding.bottom); // Ensure the 30.0f pixels size by splitting up the padding definition
can be refactored to this:
item.gravity = LKLayoutGravityTop | LKLayoutGravityRight;
item.offset = UIOffsetMake(-5.0f, -5.0f);
As mentioned in the introduction, layout items represent boxes that can contain a view or sublayout. These boxes tells the layout which space is reserved for the specified layout item. Depending on the layouts implementation, the size of a view can be smaller than the layout items reserved space. Therefore gravity makes sense and let us stick the view or sublayout of an item to an selected edge of the items box.
Therefore gravity is a bitmask and allows us to:
- Stick it to the center on a vertical axis
- Stick it to the center on a horizontal
- Stick it to the top
- Stick it to the left
- Stick it to the bottom
- Stick it to the right
- and all the reasonable combinations out of these options
Gravity must keep any offsets provided by layout items padding when moving the view or sublayout.
After size, padding and gravity may have made changes to the views position (center), offset provides us to move the resulting view or sublayout.
An offset helps us to specify a padding between edges of the item box and the items view.
This example moves the layout items view by 5 points to the right and bottom:
item.offset = UIOffsetMake(5.0f, 5.0f);
This example moves the layout items view by 5 points to the top and left:
item.offset = UIOffsetMake(-5.0f, -5.0f);
Here are some hints for working with LKLayout.
LKLayout works best with keeping LKLayoutItems as properties instead of the subviews itself if you want to edit the layout during runtime. So instead of:
@interface MKSampleLayoutView ()
@property (weak, nonatomic) UILabel *titleLabel;
@end
use:
@interface MKSampleLayoutView ()
@property (weak, nonatomic) MK<LayoutType>LayoutItem *<descriptiveName>Item;
@end
The items view or subview can be accessed via property:
LKLayoutItem.h:
- (UIView *)view
- (LKLayout *)sublayout;
For access some accessor methods can be written:
- (UILabel *)titleLabel
{
return (UILabel *)self.titleLabelLayoutItem.subview;
}
LKLayoutLibrary is based on LKLayout and LKLayoutItem. These two classes provide the implementation for managing the layout hirarchy. Here is a description of what these classes exactly do.
An layout item is an object containing a view or a partial-layout that will be managed by any of the LKLayout subclasses. Additionally LKLayoutItem itself provides properties that all layouts needs to support. LKLayoutItem can be subclassed to provided layout specific properties.
The name scheme for layout specific items looks like the following: MKItem. For example MKLinearLayout works together with MKLinearLayoutItem.
LKLayoutItem gets rid of the need to work with a specific UIView subclass which allows developers to include LKLayoutLibrary as it is with just extending their existing code instead of refactoring their base classes and structures.
Hint: Subclasses should include LKLayoutItem_SubclassAccessors to use the default initializer.
LKLayout is the base class of LKLayoutLibrary and needs to be subclassed to provide specific layout behavior. The class itself has the purpose the build up the object tree of layout items and to translate this into the UIKits UIView hierarchy.
It is also a good place to share calculation code for layout purpose.