Skip to content

Commit

Permalink
Updates for tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
hmlongco committed Dec 22, 2021
1 parent 9491d31 commit dc2bfc7
Show file tree
Hide file tree
Showing 38 changed files with 352 additions and 119 deletions.
6 changes: 5 additions & 1 deletion Builder.xcodeproj/project.pbxproj
Expand Up @@ -66,6 +66,7 @@
4C8B50B9275FAB94004FFC15 /* UITextField+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8B50B8275FAB94004FFC15 /* UITextField+Styles.swift */; };
4C8B50BB275FAC93004FFC15 /* MetaTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8B50BA275FAC93004FFC15 /* MetaTextField.swift */; };
4C8B50BD275FED84004FFC15 /* TextField+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8B50BC275FED84004FFC15 /* TextField+Styles.swift */; };
4C915525276D2CA3009EBA64 /* ScrollingTabBarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C915524276D2CA3009EBA64 /* ScrollingTabBarTest.swift */; };
4C9CC36525B4F078002BE06D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9CC36425B4F078002BE06D /* AppDelegate.swift */; };
4C9CC36725B4F078002BE06D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9CC36625B4F078002BE06D /* SceneDelegate.swift */; };
4C9CC36925B4F078002BE06D /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9CC36825B4F078002BE06D /* MainViewController.swift */; };
Expand Down Expand Up @@ -195,6 +196,7 @@
4C8B50B8275FAB94004FFC15 /* UITextField+Styles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Styles.swift"; sourceTree = "<group>"; };
4C8B50BA275FAC93004FFC15 /* MetaTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaTextField.swift; sourceTree = "<group>"; };
4C8B50BC275FED84004FFC15 /* TextField+Styles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TextField+Styles.swift"; sourceTree = "<group>"; };
4C915524276D2CA3009EBA64 /* ScrollingTabBarTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollingTabBarTest.swift; sourceTree = "<group>"; };
4C9CC36125B4F078002BE06D /* Builder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Builder.app; sourceTree = BUILT_PRODUCTS_DIR; };
4C9CC36425B4F078002BE06D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
4C9CC36625B4F078002BE06D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -405,6 +407,7 @@
4C1F921526E820670075A5F4 /* README.md */,
4C288FB825B6129D004D54AC /* Configuration */,
4C9CC3B025B4F26E002BE06D /* Application */,
4C9CC3B225B4F2D4002BE06D /* Builder */,
4C9CC3EA25B4F618002BE06D /* Models */,
4C9CC3AC25B4F246002BE06D /* Resources */,
4CD0522825B544180099B277 /* Services */,
Expand Down Expand Up @@ -466,7 +469,6 @@
4C9CC3B125B4F29A002BE06D /* Shared */ = {
isa = PBXGroup;
children = (
4C9CC3B225B4F2D4002BE06D /* Builder */,
4C9CC3E425B4F47C002BE06D /* Extensions */,
4C8B50A7275FA61C004FFC15 /* Fields */,
4C9CC3D225B4F2DF002BE06D /* Networking */,
Expand Down Expand Up @@ -566,6 +568,7 @@
4CA19EE2273E0B5D00EE7433 /* TestViewController.swift */,
4C4AD771275189E000EF12C6 /* TestViews.swift */,
4C7C3FD0276A527E00F697BA /* TabBarTest.swift */,
4C915524276D2CA3009EBA64 /* ScrollingTabBarTest.swift */,
);
path = Test;
sourceTree = "<group>";
Expand Down Expand Up @@ -769,6 +772,7 @@
4C9CC3FE25B5099D002BE06D /* UserService.swift in Sources */,
4C8B50B0275FA656004FFC15 /* TextFieldBehaviorAggregator.swift in Sources */,
4CC5D7A2270D1A80003137BD /* TestServices.swift in Sources */,
4C915525276D2CA3009EBA64 /* ScrollingTabBarTest.swift in Sources */,
4CF1A64326B399D800E26446 /* Functions+Extensions.swift in Sources */,
4C9CC3C225B4F2D4002BE06D /* Builder+ScrollView.swift in Sources */,
4C47033226CB17EE006B6DEC /* Builder+Padding.swift in Sources */,
Expand Down
File renamed without changes.
File renamed without changes.
Expand Up @@ -104,80 +104,83 @@ extension UIView {
case bottomRight
}

public func embed(_ view: UIView, padding: UIEdgeInsets? = nil, safeArea: Bool = false) {
self.addConstrainedSubview(view, position: .fill, padding: padding ?? .zero, safeArea: safeArea)
}

public func embed(_ view: View, padding: UIEdgeInsets? = nil, safeArea: Bool = false) {
self.addConstrainedSubview(view.build(), position: .fill, padding: padding ?? .zero, safeArea: safeArea)
}

public func embed(in view: View, padding: UIEdgeInsets? = nil, safeArea: Bool = false) {
view.build().addConstrainedSubview(self, position: .fill, padding: padding ?? .zero, safeArea: safeArea)
}

public func addConstrainedSubview(_ view: UIView, position: EmbedPosition, padding: UIEdgeInsets, safeArea: Bool = false) {
view.translatesAutoresizingMaskIntoConstraints = false

addSubview(view)
addVerticalConstraints(view, position: position, padding: padding, safeArea: safeArea)
addHorizontalConstraints(view, position: position, padding: padding, safeArea: safeArea)
}

private func addVerticalConstraints(_ view: UIView, position: EmbedPosition, padding: UIEdgeInsets, safeArea: Bool) {
let guides: UIViewAnchoring = safeArea ? safeAreaLayoutGuide : self

if [EmbedPosition.center, .centerLeft, .centerRight].contains(position) {
view.centerYAnchor.constraint(equalTo: guides.centerYAnchor)
.identifier("centerY")
.activate()
} else {
// top
if [EmbedPosition.fill, .top, .left, .right, .topLeft, .topCenter, .topRight].contains(position) {
view.topAnchor.constraint(equalTo: guides.topAnchor, constant: padding.top)
.identifier("top")
.activate()
} else {
view.topAnchor.constraint(lessThanOrEqualTo: guides.topAnchor, constant: padding.top)
.priority(.defaultHigh)
.identifier("top")
.activate()
}

// bottom
if [EmbedPosition.fill, .bottom, .left, .right, .bottomLeft, .bottomCenter, .bottomRight].contains(position) {
view.bottomAnchor.constraint(equalTo: guides.bottomAnchor, constant: -padding.bottom)
.identifier("bottom")
.activate()
} else {
view.bottomAnchor.constraint(greaterThanOrEqualTo: guides.bottomAnchor, constant: -padding.bottom)
.priority(.defaultHigh)
.identifier("bottom")
.activate()
}
}
}

private func addHorizontalConstraints(_ view: UIView, position: EmbedPosition, padding: UIEdgeInsets, safeArea: Bool = false) {
let guides: UIViewAnchoring = safeArea ? safeAreaLayoutGuide : self

if [EmbedPosition.center, .topCenter, .bottomCenter].contains(position) {
view.centerXAnchor.constraint(equalTo: guides.centerXAnchor)
.identifier("centerX")
.activate()
} else {
// left
if [EmbedPosition.fill, .left, .top, .bottom, .topLeft, .centerLeft, .bottomLeft].contains(position) {
view.leftAnchor.constraint(equalTo: guides.leftAnchor, constant: padding.left)
.identifier("left")
.activate()
} else {
view.leftAnchor.constraint(lessThanOrEqualTo: guides.leftAnchor, constant: padding.left)
.priority(.defaultHigh)
.identifier("left")
.activate()
}

// right
if [EmbedPosition.fill, .right, .top, .bottom, .topRight, .centerRight, .bottomRight].contains(position) {
view.rightAnchor.constraint(equalTo: guides.rightAnchor, constant: -padding.right)
.identifier("right")
.activate()
} else {
view.rightAnchor.constraint(greaterThanOrEqualTo: guides.rightAnchor, constant: -padding.right)
.priority(.defaultHigh)
.identifier("right")
.activate()
}
}

}

// deprecated
public func addSubviewWithConstraints(_ view: View, _ padding: UIEdgeInsets?, _ safeArea: Bool) {
addConstrainedSubview(view.build(), position: .fill, padding: padding ?? .zero, safeArea: safeArea)
}

}
Expand Down
File renamed without changes.
Expand Up @@ -52,8 +52,14 @@ extension ViewBuilderContextProvider {
view.transition(to: viewController, delay: delay)
}

}

// some utilility operations

extension ViewBuilderContextProvider {

public func endEditing() {
view.rootView().endEditing(true)
view.rootview.firstSubview(where: { $0.isFirstResponder })?.resignFirstResponder()
}

public var disposeBag: DisposeBag {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Expand Up @@ -28,7 +28,7 @@ extension UIView {

public func reset(_ view: View, padding: UIEdgeInsets? = nil, safeArea: Bool = false) {
let existingSubviews = subviews
addSubviewWithConstraints(view.build(), padding, safeArea)
addConstrainedSubview(view.build(), position: .fill, padding: padding ?? .zero, safeArea: safeArea)
existingSubviews.forEach { $0.removeFromSuperview() }
}

Expand Down Expand Up @@ -62,73 +62,84 @@ extension UIView {
// deprecated version
@discardableResult
public func embedModified<V:UIView>(_ view: V, padding: UIEdgeInsets? = nil, safeArea: Bool = false, _ modifier: (_ view: V) -> Void) -> V {
addSubviewWithConstraints(view, padding, safeArea)
addConstrainedSubview(view, position: .fill, padding: padding ?? .zero, safeArea: safeArea)
modifier(view)
return view
}
}

extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}

extension UIView {

// goes to top of view chain, then initiates full search of view tree
public func find<K:RawRepresentable>(_ key: K) -> UIView? where K.RawValue == Int {
recursiveFind(key.rawValue, keyPath: \.tag, in: rootView())
rootview.firstSubview(where: { $0.tag == key.rawValue })
}
public func find<K:RawRepresentable>(_ key: K) -> UIView? where K.RawValue == String {
recursiveFind(key.rawValue, keyPath: \.accessibilityIdentifier, in: rootView())
rootview.firstSubview(where: { $0.accessibilityIdentifier == key.rawValue })
}

// searches down the tree looking for identifier
public func find<K:RawRepresentable>(subview key: K) -> UIView? where K.RawValue == Int {
recursiveFind(key.rawValue, keyPath: \.tag, in: self)
firstSubview(where: { $0.tag == key.rawValue })
}
public func find<K:RawRepresentable>(subview key: K) -> UIView? where K.RawValue == String {
recursiveFind(key.rawValue, keyPath: \.accessibilityIdentifier, in: self)
firstSubview(where: { $0.accessibilityIdentifier == key.rawValue })
}

// searches up the tree looking for identifier in superview path
public func find<K:RawRepresentable>(superview key: K) -> UIView? where K.RawValue == Int {
superviewFind(key.rawValue, keyPath: \.tag)
firstSuperview(where: { $0.tag == key.rawValue })
}
public func find<K:RawRepresentable>(superview key: K) -> UIView? where K.RawValue == String {
superviewFind(key.rawValue, keyPath: \.accessibilityIdentifier)
firstSuperview(where: { $0.accessibilityIdentifier == key.rawValue })
}

internal func rootView() -> UIView {
var root = self.superview ?? self
while let view = root.superview { root = view}
return root
}
}

extension UIView {

internal func recursiveFind<T:Equatable>(_ key: T, keyPath: KeyPath<UIView, T>, in view: UIView) -> UIView? {
if view[keyPath: keyPath] == key {
return view
public func scrollIntoView() {
guard let scrollview = firstSuperview(where: { $0 is UIScrollView }) as? UIScrollView else {
return
}
for child in view.subviews {
if let foundView = recursiveFind(key, keyPath: keyPath, in: child) {
return foundView
let visible = convert(frame, to: scrollview)
UIViewPropertyAnimator(duration: 0.1, curve: .linear) {
scrollview.scrollRectToVisible(visible, animated: false)
}.startAnimation()
}

}

extension UIView {

public func firstSubview(where predicate: (_ view: UIView) -> Bool) -> UIView? {
for child in subviews {
if predicate(child) {
return child
} else if let found = child.firstSubview(where: predicate){
return found
}
}
return nil
}

internal func superviewFind<T:Equatable>(_ key: T, keyPath: KeyPath<UIView, T>) -> UIView? {
var parent: UIView? = self
while let view = parent {
if view[keyPath: keyPath] == key {
return view
}
parent = view.superview
public func firstSuperview(where predicate: (_ view: UIView) -> Bool) -> UIView? {
if let parent = superview {
return predicate(parent) ? parent : parent.firstSuperview(where: predicate)
}
return nil
}

public var rootview: UIView {
firstSuperview(where: { $0.superview == nil }) ?? self
}

}

extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}

extension Int: RawRepresentable {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Expand Up @@ -13,7 +13,7 @@ import RxSwift
public struct ImageView: ModifiableView {

public let modifiableView = Modified(UIImageView())

// lifecycle
public init(_ image: UIImage?) {
modifiableView.image = image
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Expand Up @@ -17,11 +17,15 @@ public struct ScrollView: ModifiableView {

public init(_ view: View?, padding: UIEdgeInsets? = nil, safeArea: Bool = false) {
guard let view = view else { return }
modifiableView.embed(view, padding: padding, safeArea: safeArea)
modifiableView.views = [view]
modifiableView.padding = padding ?? .zero
modifiableView.safeArea = safeArea
}

public init(padding: UIEdgeInsets? = nil, safeArea: Bool = false, @ViewResultBuilder _ builder: () -> ViewConvertable) {
builder().asViews().forEach { modifiableView.embed($0, padding: padding, safeArea: safeArea) }
modifiableView.views = builder()
modifiableView.padding = padding ?? .zero
modifiableView.safeArea = safeArea
}

}
Expand Down Expand Up @@ -77,7 +81,9 @@ public struct VerticalScrollView: ModifiableView {
}

public init(padding: UIEdgeInsets? = nil, safeArea: Bool = false, @ViewResultBuilder _ builder: () -> ViewConvertable) {
builder().asViews().forEach { modifiableView.embed($0, padding: padding, safeArea: safeArea) }
modifiableView.views = builder()
modifiableView.padding = padding ?? .zero
modifiableView.safeArea = safeArea
}

}
Expand All @@ -86,6 +92,11 @@ public class BuilderInternalScrollView: UIScrollView, UIScrollViewDelegate {

public var scrollViewDidScrollHandler: ((_ context: ViewBuilderContext<UIScrollView>) -> Void)?

fileprivate var views: ViewConvertable?
fileprivate var padding: UIEdgeInsets = .zero
fileprivate var position: EmbedPosition = .fill
fileprivate var safeArea: Bool = false

@objc public func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollViewDidScrollHandler?(ViewBuilderContext(view: self))
}
Expand All @@ -94,6 +105,17 @@ public class BuilderInternalScrollView: UIScrollView, UIScrollViewDelegate {
optionalBuilderAttributes()?.commonDidMoveToWindow(self)
}

override public func didMoveToSuperview() {
views?.asViews().forEach {
let view = $0.build()
let attributes = view.optionalBuilderAttributes()
let position = attributes?.position ?? position
let padding = attributes?.insets ?? padding
addConstrainedSubview(view, position: position, padding: padding, safeArea: safeArea)
}
super.didMoveToSuperview()
}

}

public class BuilderVerticalScrollView: BuilderInternalScrollView {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit dc2bfc7

Please sign in to comment.