EasyStackView
简介
本项目为 iOS 原生开发提供了一种简单优雅的 flex 布局实现。此项目可以与 Swift 和 Objective-C 语言一起使用。
如果您喜欢,请星标。
Cocoapods 集成
与使用该工具找到您的项目中的 podfile
脚本。
pod 'EasyStackView'
如何使用
目前有四种类型的 flex 容器
ESVStackView
-> UIView
ESVStackPlaceHolder
-> UIView
(在使用弹性布局时,我们创建一些仅用于布局参考的 <div>
。为了避免创建过多的无用的 UIView
对象,请使用 ESVStackPlaceHolder
而不是 ESVStackView
。与 ESVStackView
相比,ESVStackPlaceHolder
要少得多。)
注意: ESVStackPlaceHolder
实例不应作为柔性布局树的根。)
ESVScrollView
-> UIScrollView
ESVRecycleView
-> UITableView
或 UICollectionView
示例
构建与上图相同的 UI 只需几个步骤。
- 首先,为整个 alertView 创建一个容器。容器内的每个元素按垂直方向渲染,并尽可能拉伸以填充宽度。
let container: ESVStackView = createContainer()
container.flexDirection = .column
container.alignItems = .stretch
- 添加标题。
let title: UILabel = createTitle()
container.addArrangedItem(title)
- 添加内容并设置内容的间距。
let content: UITextView = createContent()
container.addArrangedItem(content)
container.manageConfig(of: content) { (config) in
config?.margin = .init(top: 10, left: 10, bottom: 10, right: 10)
config?.growth = true
}
- 创建一个可滚动的容器以包含四个图标。隐藏水平滚动指示器。元素横向布局。
let iconHolder: ESVScrollView = createIconHolder()
iconHolder.showsHorizontalScrollIndicator = false
container.addArrangedItem(iconHolder)
iconHolder.flexDirection = .row
container.manageConfig(of: iconHolder) { (config) in
config?.margin = .init(top: 10, left: 10, bottom: 10, right: 10)
}
- 创建四个项目并将它们添加到可滚动的容器中。
for i in 0...4 {
let icon: UIView = createIcon()
if i % 3 == 0 {
icon.backgroundColor = UIColor.red
} else {
icon.backgroundColor = UIColor.white
}
iconHolder.addArrangedItem(icon)
if i == 4 {
iconHolder.manageConfig(of: UInt(i)) { (config) in
config?.margin = .init(top: 0, left: 5, bottom: 0, right: 5)
}
} else {
iconHolder.manageConfig(of: UInt(i)) { (config) in
config?.margin = .init(top: 0, left: 5, bottom: 0, right: 0)
}
}
}
- 创建另一个可滚动的容器以包含十个图标。如果有太多的图标,我们更愿意使用可循环容器。
let recycleIconHolder: ESVRecycleView = createRecycleIconHolder()
recycleIconHolder.showsHorizontalScrollIndicator = false
container.addArrangedItem(recycleIconHolder)
recycleIconHolder.flexDirection = .row
container.manageConfig(of: recycleIconHolder) { (config) in
config?.margin = .init(top: 10, left: 10, bottom: 10, right: 10)
}
- 定义一个
UIView
的子类,将其作为显示中的可循环元素使用。
class RecycleIcon: UIView, ESVRecycleCellType {
func prepareForReuse() {
}
func config(with model: ESVRecyclableModelType, index: UInt) {
self.layer.cornerRadius = 6
self.backgroundColor = index % 3 == 0 ? UIColor.white : UIColor.red
}
}
- 使用键
"RecycleIcon"
注册RecycleIcon
生成器。
recycleIconHolder.registerGenerator({ () -> UIView & ESVRecycleCellType in
return RecycleIcon()
}, forIdentifier: "RecycleIcon")
- 将十个模型对象添加到容器中。
for i in 0...10 {
let model: ESVRecyclableModelType = ESVRecyclableModel()
model.frame = .init(x: 0, y: 0, width: 50, height: 50)
model.identifier = "RecycleIcon"
recycleIconHolder.addArrangedItem(model)
if i == 10 {
recycleIconHolder.manageConfig(of: UInt(i)) { (config) in
config?.margin = .init(top: 0, left: 5, bottom: 0, right: 5)
}
} else {
recycleIconHolder.manageConfig(of: UInt(i)) { (config) in
config?.margin = .init(top: 0, left: 5, bottom: 0, right: 0)
}
}
}
- 添加按钮容器。它仅用作渲染参考,不具有显示功能。因此,使用
ESVStackPlaceHolder
是一个经济实惠的选择。
let buttonContainer: ESVStackPlaceHolder = createButtonContainer()
container.addArrangedItem(buttonContainer)
buttonContainer.flexDirection = .row
buttonContainer.alignItems = .stretch
- 将两个按钮添加到按钮容器中。两个按钮大小相等,应拉伸以填充所有空间。
let confirmButton: UIButton = createConfirmButton()
buttonContainer.addArrangedItem(confirmButton)
buttonContainer.manageConfig(of: confirmButton) { (config) in
config?.growth = true
config?.margin = .init(top: 5, left: 5, bottom: 5, right: 5)
}
let cancelButton: UIButton = createCancelButton()
buttonContainer.addArrangedItem(cancelButton)
buttonContainer.manageConfig(of: cancelButton) { (config) in
config?.growth = true
config?.margin = .init(top: 5, left: 5, bottom: 5, right: 5)
}
此示例编写在此存储库的 ./Demo/Sample4ViewController.swift
中。您可以使用 Xcode 运行 Demo
目标以检查此内容。
特性
所有容器都遵守 ESVFlexManageType
协议,这为它们提供了布局属性。
@protocol ESVFlexManageType <NSObject>
/// Flex direction.
@property (nonatomic, assign) ESVDirection flexDirection;
/// Distribution mode along axis.
@property (nonatomic, assign) ESVJustify justifyContent;
/// Distribution mode cross axis.
@property (nonatomic, assign) ESVAlign alignItems;
/// Minium space between items.
@property (nonatomic, assign) CGFloat spaceBetween;
@end
所有容器都遵守 ESVItemManageType
协议,这为它们提供了管理项目的能力。
@protocol ESVItemManageType <NSObject>
/// All items arranged in flex layout.
@property (nonatomic, readonly) NSArray<__kindof NSObject<ESVStackItemType> *> *arrangedItems;
/// Add item at the end of arranged subviews.
/// @param item Item to add
- (void)addArrangedItem:(NSObject<ESVStackItemType> *)item;
/// Delete item from arranged subview.
/// @param item Item to delete.
- (void)deleteArrangedItem:(NSObject<ESVStackItemType> *)item;
/// Insert item into arranged subviews.
/// @param item Item to insert.
/// @param index Index to insert.
- (void)insertArrangedItem:(NSObject<ESVStackItemType> *)item atIndex:(NSUInteger)index;
@property (nonatomic, readonly) NSArray<__kindof UIView *> *managedViews;
@property (nonatomic, weak) NSObject<ESVStackItemType> * superItem;
@end
注意: 只有使用函数 - (void)addArrangedItem:(NSObject<ESVStackItemType> *)item;
添加的对象才能加入柔性布局。使用 -(void)addSubview:(UIView *)view
添加的视图将以与普通视图相同的方式进行布局。
所有容器都遵守 ESVRefreshManageType
协议,这提供了关于渲染的一些操作。
@protocol ESVRefreshManageType <NSObject>
@property (readonly) BOOL dirty;
/// Mark this item as dirty, it layout soon after.
- (void)markAsDirty;
/// Try to all layout subviews if self or arranged items are dirty.
- (void)render;
/// This should not be called mannually.
- (void)applyItemFrame;
/// The preffered size of this view, which can properly hold all its arranged items.
@property (readonly) CGSize preferedSize;
@end
所有容器都遵守 ESVConfigManageType
协议,这提供了操作排列项目配置的方法。
@protocol ESVConfigManageType <ESVItemManageType>
/// Settle config of item. -setNeedLayout will be called automatically after change config.
/// @param item The item you want to change config of.
/// @param configAction Change config in this block, will be called synchronized. There is no retain circle problem.
- (void)manageConfigOfItem:(NSObject<ESVStackItemType> *)item configAction:(void(^)(ESVStackItemConfig * _Nullable config))configAction;
/// Settle config of item. -setNeedLayout will be called automatically after change config.
/// @param index The index you want to change config of.
/// @param configAction Change config in this block, will be called synchronized. There is no retain circle problem.
- (void)manageConfigOfIndex:(NSUInteger)index configAction:(void(^)(ESVStackItemConfig * _Nullable config))configAction;
/// Get config of item. You must call -setNeedLayout by yourself to layout again.
/// @param item The item.
- (nullable ESVStackItemConfig *)configOfItem:(NSObject<ESVStackItemType> *)item;
/// Get config of index. You must call -setNeedLayout by yourself to layout again.
/// @param index The index of config.
- (nullable ESVStackItemConfig *)configOfIndex:(NSUInteger)index;
@end
每个排列项都与它的 ESVStackItemConfig
配置对象相关联。您可以使用 ESVStackItemConfig
实例指定此排列项的布局属性。
@interface ESVStackItemConfig : NSObject
#pragma mark - Cache
/// Frame cache of view. DO NOT change this property value unless you are pretty sure what you are doing.
@property (nonatomic, assign) CGRect cacheTransformedFrame;
@property (nonatomic, assign) CGRect cacheFrame;
#pragma mark - Associate
/// The view this config is associated to.
@property (nonatomic, readonly) NSObject<ESVStackItemType> * item;
#pragma mark - Config
/// Is this view layouted according to alignSelf in this config. Default is false.
@property (nonatomic, assign) BOOL alignSelfOn;
/// Describe how to layout this view in flex layout. Only work when alignSelfOn is true.
@property (nonatomic, assign) ESVAlign alignSelf;
/// Orignal frame of the view. This is the reference size of the view during layout. Default is view's size when added into arranged views array.
@property (nonatomic, assign) CGSize originSize;
/// Minium margin inset of view in four direction. Default is {0, 0, 0, 0,}.
@property (nonatomic, assign) UIEdgeInsets margin;
/// Indicates whether this view will auto stretch when super view is not filled along axis. Default is false.
@property (nonatomic, assign) BOOL growth;
/// Indicates whether this view will auto shrink when super view is too narrow along axis. Default is false.
@property (nonatomic, assign) BOOL shrink;
- (instancetype)initWithItem:(NSObject<ESVStackItemType> *)item;
@end
待办事项
【完成】带有弹性布局的滚动视图。
【完成】带有可重用视图的滚动视图,以便替换 UITableView
。
【待办】尝试使 EasyStackView 的行为与 Web 弹性布局基本相同。