UIContainer
安装
UIContainer可通过CocoaPods获取。要安装它,只需将以下行添加到您的Podfile中
pod 'UIContainer'
macOS支持
该库在macOS上的实际开发阶段尚未经过测试。
定义
1. UIContainer
UIContainer协议是每个容器创建的主要核心。每个UIView都可以持有另一个UIView或UIViewController。使用容器,你不必担心在屏幕上展示任何内容是否做得正确。UIContainer是一个通用的协议,可以扩展以允许其他视图成为某些内容的容器。每个容器应该从ContainerBox类型创建,但你可以创建自己的容器和容器框,没有这方面的规则。
为任何UIContainer开发的方法定义如下
ParentView
: UIViewControllerView
: AnyObject- .prepareContainer(inside parentView: ParentView!, loadHandler: (() -> View?)?)
- .removeContainer()
- .insertContainer(view: View!)
- .prepare(parentView: ParentView!)
- .loadView<T: UIView>(_ view: T) -> UIView
- .containerDidLoad()
- .init(in parentView: ParentView!, loadHandler: (() -> View?)?)
考虑到这一点,您可以从创建容器类和使用它们开始。我们已经开发了用于UIViewControllers的容器和用于UIView的ContainerView。这里将展示一些派生类,用于Cells(尚不完全实现)和Storyboard的出口。
1.a) ContainerViewParent
这个协议很重要,因为所有容器都需要知道作为调用堆栈中叶子根的父视图控制器。
我们从顶层viewController开始。任何应用中都应该存在的第一个viewController是UIWindow.rootViewController
,这是UIKit强制规定的唯一一个。所以假设根类是WindowViewController,现在所有在WindowViewController内部显示的UIView都将持有WindowViewController作为父类。
如果你有一个WindowViewController,并且有containerA<UIView>
、containerB<UIViewController>
和 containerC<UIView>
,这意味着containerA和containerB的父视图控制器将是WindowViewController,而containerC的父类将是containerB.UIViewController。
ContainerViewParent允许你保持对一个参考或可能父类的引用,但这并不意味着它将是正确的父类,因为有时父类是“superview”。
1.c) NibView和View
这里应定义两种视图类型。第一种是用.xib文件创建的视图,第二种是用代码创建的视图。UIKit没有为这些情况实现正确的代码,也许Apple认为这些都是不同的事情。经一番研究后,我们决定创建NibView
和View
。它们非常相似,但nib类型的一个通常用作其他开发者的contentView,具有@IBOutlet weak var view: UIView!
。最终,生命周期是.init
和prepare
函数。
你应该记住,这两个视图都有在实例化过程中调用的prepare函数。这意味着父类或superview没有对其的引用。
1.d) ContainerView<视图:UIView 与 ContainerViewParent> 和 Container<视图:UIViewController>。
这些类是为了创建视图或视图控制器而设计的。以下是一些示例
第一种情况是针对 UIView
import UIKit
import UIContainer
class ExampleViewController: UIViewController {
weak var containerView: ContainerView<CartView>!
override func viewDidLoad() {
super.viewDidLoad()
let containerView = ContainerView<CartView>(in: self) // Will be explained
self.view.addSubview(containerView)
self.containerView = containerView
}
}
另一种情况是使用 Container 针对 UIViewController
import UIKit
import UIContainer
class ExampleViewController: UIViewController {
weak var containerView: Container<CartViewController>!
override func viewDidLoad() {
super.viewDidLoad()
let containerView = Container<CartViewController>(in: self) // Will be explained
self.view.addSubview(containerView)
self.containerView = containerView
}
}
现在,我唯一喜欢的方法是在我的视图内部创建子类,并在视图控制器中使用它
import UIKit
import UIContainer
class CartView: View {
override func prepare() {
super.prepare()
// Do stuff here
}
}
extension CartView {
class Container: ContainerView<CartView> {
override func containerDidLoad() {
super.containerDidLoad()
// Stylize your container
}
}
}
class ExampleViewController: UIViewController {
weak var cartView: CartView.Container!
override func viewDidLoad() {
super.viewDidLoad()
let cartView = CartView.Container(in: self) // Will be explained
self.view.addSubview(cartView)
self.cartView = cartView
}
}
请记住,Container 和 ContainerView 具有相同的方法和生命周期。containerDidLoad 会在生命周期结束时调用,并且在将视图装载到其内部后,它尚未在父视图中呈现。
2. UIContainerCell
此协议是 UIContainer 的封装,帮助创建表格单元格。它尚未完全实现,因为我们还有诸如 UICollectionViewCell 这样的其他情况尚未测试,但您可以使用 ContainerCollectionViewCell 尝试它。
这里的魔法在于 UIContainerCell 已经实现了实现此协议的类的必需方法。当您处于 tableView.dequeueReusableCell 状态时,应调用 cell.prepareContainer(inside: self),请记住,您的单元格必须是 UIContainerCell 衍生。
prepareContainer 防止重新创建视图,并保持作为可重用单元格的安全。如果您在使用具有 DisposeBag 的视图的响应式库时,这是一个重要因素。
有两个类,一个是针对 UIView,另一个是针对 UIViewController。第一个是 ContainerTableViewCell,第二个是 ContainerTableCell。它们具有相同的方法和调用栈。
因此,曾经用作 UITableViewCell 的视图现在只是 UIView,您该如何做到这一点?以下是一个示例
import UIKit
import UIContainer
class CartView: View, ContainerCellDelegate {
override func prepare() {
super.prepare()
self.applyLayouts()
self.configureButtons()
}
func reloadView(_ cart: Cart) {
self.titleLabel.text = cart.name;
self.onPlayTouch = {
cart.startRunning()
}
self.onStopTouch = {
cart.stopRunning()
}
}
}
class CartsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(ContainerTableViewCell<CartView>.self, forCellReuseIdentifier: "CartViewCellIdentifier")
self.tableView.delegate = self
self.tableView.dataSource = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartViewCellIdentifier", for: indexPath) as! ContainerTableViewCell<CartView>
cell.prepareContainer(in: self)
cell.view.reloadView(carts[indexPath.row])
return cell
}
}
2.a) ContainerCellDelegate
注意 CartView 超类类型中的 ContainerCellDelegate 符合性。ContainerCellDelegate 是我们的助手,因为它可以或不能用作单元格,具体取决于您是否将其创建为 ContainerView 示例。
ContainerCellDelegate 有两种行为,它将 cellDelegate: Delegate 关联类型添加到视图。第一种行为是如果您没有在视图类中指定添加 var cellDelegate: Delegate?
行。会发生什么?ContainerCellDelegate 会理解您没有指定单元格委托,并将 EmptyCellDelegate 与您的类关联。请注意,如果您键入 "self.cellDelegate",类型将是 EmptyCellDelegate?。
3. UIContainerStoryboard
这是一个为以下场景创建的协议:您希望使用Storyboard或.xib将视图添加到其他视图内部。您需要实现其Storyboard视图并提供一些初始化期间的帮助方法。以下是一些示例
import UIKit
import UIContainer
class CartViewStoryboard: View, UIContainerStoryboard {
typealias View = CartView
weak var containerView: ContainerView<CartView>!
var edgeInsets: UIEdgeInsets = .init(top: 15, left: 15, bottom: 15, right: 15)
func containerDidLoad() {
// stylize the container
}
}
class ExampleViewController: UIViewController {
@IBOutlet weak var cartWrapper: CartViewStoryboard!
override func viewDidLoad() {
super.viewDidLoad()
self.cartWrapper.prepareContainer(inside: self)
}
}
4. ContainerController
ContainerController通过创建符合ViewController的视图来帮助。您只需要将您的视图符合ViewControllerType协议,并使用ViewControllerMaker.dynamic(_: (UIViewController) -> Void)实现content: ViewControllerMaker { get}。
之后,您应该使用ContainerController.init(_: View)创建UIViewController实例,其中View符合ViewControllerType。
WindowContainer是我们最新的新特性,可能会在某些版本更新中被更改。它只能与没有类或特殊方法的淡入效果一起使用。
要使用此功能,您必须在项目中实现WindowContainerType,并实现启动器静态函数和容器获取器。要设置AppDelegate类中的窗口,应调用UIWindow.container(WindowContainerType.self)
。以下是一个示例
import UIKit
import UIContainer
enum WindowType {
case main
}
extension WindowType: WindowContainerType {
var container: UIView! {
switch self {
case .main:
return Container(in: AppDelegate.shared.windowView) {
MainViewController.shared // Provided by ViewSharedContext
}
}
static func launcher(in windowContainer: WindowContainer<WindowType>) -> UIView! {
return Container(in: windowContainer) {
UIViewController.laucherViewController()
}
}
}
现在,AppDelegate
import UIContainer
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var windowView: WindowContainer<WindowType> {
return window!.rootViewController as! WindowContainer<WindowType>
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow.container(WindowType.self)
self.window?.makeKeyAndVisible()
self.windowView.transition(to: .main)
return true
}
}
作者
brennobemoura, [email protected]
许可
UIContainer遵从MIT许可。更多信息请参阅LICENSE文件。