Skip to content

Commit

Permalink
created voirhooker for dynamic constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurdapaz committed Sep 10, 2023
1 parent cdd8cb5 commit df067d0
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 57 deletions.
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ let package = Package(
products: [
.library(name: "Voir", type: .static, targets: ["Voir"]),
.library(name: "VoirBuilder", type: .static, targets: ["VoirBuilder"]),
.library(name: "VoirHooker", type: .dynamic, targets: ["VoirHooker"]),
.executable(name: "TemplateInstaller", targets: ["TemplateInstaller"])
],
targets: [
.target(name: "Voir", dependencies: [.target(name: "VoirBuilder")]),
.testTarget(name: "VoirTests", dependencies: [.target(name: "Voir")]),

.target(name: "VoirBuilder"),
.target(name: "VoirBuilder", dependencies: [.target(name: "VoirHooker")]),
.testTarget(name: "VoirBuilderTests", dependencies: [.target(name: "VoirBuilder")]),

.target(name: "VoirHooker"),

.executableTarget(name: "TemplateInstaller")
]
)
3 changes: 2 additions & 1 deletion Sources/Voir/VoirController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import VoirBuilder

open class VoirController<View: VoirComponent, ViewModel: VoirModel>: UIViewController {

public let component = View()
public let component: View
public let viewModel: ViewModel

public init(_ component: View.Type, _ viewModel: ViewModel) {
self.component = component.init()
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/VoirBuilder/VoirBuilder+Is.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ public extension IsVoirable where Self: Any {
}
}

extension NSObject: IsVoirable {}

extension UIView: IsVoirable {}
extension CALayer: IsVoirable {}
43 changes: 31 additions & 12 deletions Sources/VoirBuilder/VoirBuilder+Traits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import UIKit

public struct VoirTraits {
public enum Orientation: Hashable {
case always
case portrait
case landscape
case always

fileprivate init(traitCollection: UITraitCollection) {
self = traitCollection.verticalSizeClass == .compact ? .landscape : .portrait
}
}

fileprivate let view: UIView
Expand All @@ -19,15 +23,23 @@ public struct VoirTraits {
}
}

public extension UIView {
private extension UIView {
private static var orientedConstraintsKey: UInt8 = 0
private static var orientationConstraintsKey: UInt8 = 0
private static var orientationKey: UInt8 = 0

fileprivate var orientedConstraints: [VoirTraits.Orientation: [NSLayoutConstraint]] {
var orientedConstraints: [VoirTraits.Orientation: [NSLayoutConstraint]] {
get { objc_getAssociatedObject(self, &Self.orientedConstraintsKey) as? [VoirTraits.Orientation: [NSLayoutConstraint]] ?? [:] }
set { objc_setAssociatedObject(self, &Self.orientedConstraintsKey, newValue, .OBJC_ASSOCIATION_RETAIN) }
}

var orientation: VoirTraits.Orientation {
get { objc_getAssociatedObject(self, &Self.orientationKey) as? VoirTraits.Orientation ?? .portrait }
set {
objc_setAssociatedObject(self, &Self.orientationKey, newValue, .OBJC_ASSOCIATION_RETAIN)
enableConstraints(for: newValue)
}
}

private func enableConstraints(for orientation: VoirTraits.Orientation) {
guard !orientedConstraints[orientation, default: []].isEmpty else { return }

Expand All @@ -40,16 +52,23 @@ public extension UIView {

orientedConstraints[orientation, default: []].forEach { $0.isActive = true }
}
}

var orientation: VoirTraits.Orientation {
get { objc_getAssociatedObject(self, &Self.orientationConstraintsKey) as? VoirTraits.Orientation ?? .portrait }
set {
objc_setAssociatedObject(self, &Self.orientationConstraintsKey, newValue, .OBJC_ASSOCIATION_RETAIN)
enableConstraints(for: newValue)
}
}

// MARK: - VoirHooker API Approach
public extension UIView {
func activate(@ConstraintsBuilder constraints: () -> [NSLayoutConstraint]) -> VoirTraits {
VoirTraits(view: self, constraints: constraints())
}

@objc
func notifyOrientation() {
orientation = .init(traitCollection: traitCollection)
}
}

public extension UIViewController {
@objc
func notifyOrientation() {
view.orientation = .init(traitCollection: traitCollection)
}
}
54 changes: 54 additions & 0 deletions Sources/VoirHooker/VoirHooker.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

#if DEBUG
__attribute__((constructor)) static void voir() {
printf("VoirHooker loaded.\n");
}
#endif

@interface NSObject (Swizzling)
+ (void)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector;
@end

@implementation NSObject (Swizzling)
+ (void)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector {
Class class = [self class];
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end

@implementation UIView (Orientation)
+ (void)load {
[self swizzleMethod:@selector(traitCollectionDidChange:) withMethod:@selector(swizzled_traitCollectionDidChange:)];
}

- (void)swizzled_traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[self swizzled_traitCollectionDidChange:previousTraitCollection];
if ([self respondsToSelector:@selector(notifyOrientation)]) {
[self performSelector:@selector(notifyOrientation)];
}
}
@end

@implementation UIViewController (Orientation)
+ (void)load {
[self swizzleMethod:@selector(viewDidLoad) withMethod:@selector(swizzled_viewDidLoad)];
}

- (void)swizzled_viewDidLoad {
[self swizzled_viewDidLoad];
if ([self respondsToSelector:@selector(notifyOrientation)]) {
[self performSelector:@selector(notifyOrientation)];
}
}
@end
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
256524DD2AA4273300D06305 /* ViewComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 256524DC2AA4273300D06305 /* ViewComponent.swift */; };
256524DF2AA44D7D00D06305 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 256524DE2AA44D7D00D06305 /* Extensions.swift */; };
256524E12AA451D800D06305 /* DisclaimerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 256524E02AA451D800D06305 /* DisclaimerViewController.swift */; };
256524EC2AA4659900D06305 /* VoirBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 256524EB2AA4659900D06305 /* VoirBuilder */; };
2583F4322AAE4080004945CE /* VoirBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 2583F4312AAE4080004945CE /* VoirBuilder */; };
25D3B3ED2A9B021400761DC8 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25D3B3EC2A9B021400761DC8 /* ViewController.swift */; };
/* End PBXBuildFile section */

Expand All @@ -37,7 +37,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
256524EC2AA4659900D06305 /* VoirBuilder in Frameworks */,
2583F4322AAE4080004945CE /* VoirBuilder in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -126,7 +126,7 @@
);
name = VoirBuilderDemo;
packageProductDependencies = (
256524EB2AA4659900D06305 /* VoirBuilder */,
2583F4312AAE4080004945CE /* VoirBuilder */,
);
productName = VoirDemo;
productReference = 2553F01C2A9AF3B600F07A3A /* VoirBuilderDemo.app */;
Expand Down Expand Up @@ -157,7 +157,7 @@
);
mainGroup = 2553F0132A9AF3B600F07A3A;
packageReferences = (
256524EA2AA4659900D06305 /* XCLocalSwiftPackageReference "../.." */,
2583F4302AAE4080004945CE /* XCLocalSwiftPackageReference "../.." */,
);
productRefGroup = 2553F01D2A9AF3B600F07A3A /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -405,14 +405,14 @@
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
256524EA2AA4659900D06305 /* XCLocalSwiftPackageReference "../.." */ = {
2583F4302AAE4080004945CE /* XCLocalSwiftPackageReference "../.." */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../..;
};
/* End XCLocalSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
256524EB2AA4659900D06305 /* VoirBuilder */ = {
2583F4312AAE4080004945CE /* VoirBuilder */ = {
isa = XCSwiftPackageProductDependency;
productName = VoirBuilder;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
guard let windowScene = (scene as? UIWindowScene) else { return }

let navigation = UINavigationController(rootViewController: DisclaimerViewController())
navigation.navigationBar.tintColor = #colorLiteral(red: 0.1098039216, green: 0.1137254902, blue: 0.4156862745, alpha: 1)

let window = UIWindow(windowScene: windowScene)
window.rootViewController = navigation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,15 @@ final class DisclaimerViewController: UIViewController {
homeButton.heightAnchor.constraint(equalToConstant: 48)
}.when(.always)

view.backgroundColor = .white

notifyOrientation()

homeButton => { [unowned self] in
if navigationController?.viewControllers.first == self {
navigationController?.pushViewController(ViewController(), animated: true)
} else {
navigationController?.popViewController(animated: true)
}
}

view.backgroundColor = .white
title = "Disclaimer"
}
}
20 changes: 0 additions & 20 deletions Support/VoirBuilderDemo/VoirDemo/View/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,3 @@ extension UIImage {
return UIGraphicsGetImageFromCurrentImageContext()!
}
}

extension UIViewController {
func notifyOrientation() {
if traitCollection.verticalSizeClass == .regular && traitCollection.horizontalSizeClass == .compact {
view.orientation = .portrait
} else {
view.orientation = .landscape
}
}
}

extension UIView {
func notifyOrientation() {
if traitCollection.verticalSizeClass == .regular && traitCollection.horizontalSizeClass == .compact {
orientation = .portrait
} else {
orientation = .landscape
}
}
}
12 changes: 3 additions & 9 deletions Support/VoirBuilderDemo/VoirDemo/View/ViewComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ final class ViewComponent: UIView {
noiseImage
logoImage
stackView {

titleLabel
subtitleLabel
usernameTextField
Expand All @@ -117,7 +116,7 @@ final class ViewComponent: UIView {
}.when(.always)

activate {
logoImage.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: -32)
logoImage.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 0)
logoImage.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20)
logoImage.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20)
logoImage.heightAnchor.constraint(equalToConstant: 300)
Expand All @@ -133,12 +132,12 @@ final class ViewComponent: UIView {
}.when(.portrait)

activate {
logoImage.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: -32)
logoImage.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 0)
logoImage.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor)
logoImage.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20)
logoImage.trailingAnchor.constraint(equalTo: centerXAnchor, constant: -20)

stackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: -12)
stackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor)
stackView.leadingAnchor.constraint(equalTo: logoImage.trailingAnchor, constant: 20)
stackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -20)

Expand All @@ -161,11 +160,6 @@ final class ViewComponent: UIView {
delegate?.forgotPasswordButtonTapped()
}
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
notifyOrientation()
}
}

extension ViewComponent: CAAnimationDelegate {
Expand Down
3 changes: 1 addition & 2 deletions Support/VoirBuilderDemo/VoirDemo/View/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ final class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
notifyOrientation()

(view as! ViewComponent).delegate = self
title = "Voir Demo"
}
}

Expand Down

0 comments on commit df067d0

Please sign in to comment.