From 6eddf321b848b373d89b2dfa87952e8f14561de6 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:45:27 +0200 Subject: [PATCH 01/24] Add support for snapping to views representing root UIViewControllers of different sizes, such as a small device or a small device with a presented keyboard Add support for snapping to views representing root UIViewControllers of different sizes, such as a small device or a small device with a presented keyboard Rename UIViewControllerFreeFormPreview to FreeForm Add support for previewing a view in freeform for view controllers Delete support for iPhone 11 frame, keep only the small device frame Keep only FreeForm for UIView and UIViewController Remove context argument from view and viewcontroller init methods of FreeForm Previews --- .../IBPreviews/IBPreview+FreeFormView.swift | 44 ---- .../IBPreview+FreeFormViewController.swift | 193 ++++++++++++++---- .../IBPreviews/IBPreview+FullScreenView.swift | 41 ---- .../IBPreview+FullScreenViewController.swift | 31 --- .../IBPreview+SizeThatFitsView.swift | 45 ---- 5 files changed, 150 insertions(+), 204 deletions(-) delete mode 100644 Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift delete mode 100644 Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift delete mode 100644 Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift delete mode 100644 Sources/UIViewKit/IBPreviews/IBPreview+SizeThatFitsView.swift diff --git a/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift b/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift deleted file mode 100644 index 90355dd..0000000 --- a/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// IBPreview+FreeFormView.swift -// UIViewKit -// -// Created by Blazej SLEBODA on 14/11/2023. -// - -import UIKit -import SwiftUI - -extension IBPreview { - - @available(iOS 13.0, *) - public struct FreeFormView: UIViewControllerRepresentable { - - private let viewMaker: (Context) -> UIView - - public init(_ view: UIView) { - self.viewMaker = { _ in - view - } - } - - public init(viewMaker: @escaping (Context) -> UIView) { - self.viewMaker = viewMaker - } - - public func makeUIViewController(context: Context) -> UIViewController { - let controller = UIViewController() - let view = viewMaker(context) - controller.view.addSubview(view) - view.frame = controller.view.bounds - view.autoresizingMask = [.flexibleHeight, .flexibleWidth] - view.translatesAutoresizingMaskIntoConstraints = true - - let freeFormContainer = ContainerViewController() - _ = freeFormContainer.view - freeFormContainer.childViewController = controller - return freeFormContainer - } - - public func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } - } -} diff --git a/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift b/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift index a5d8dc7..276059c 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift @@ -11,60 +11,167 @@ import SwiftUI extension IBPreview { @available(iOS 13.0, *) - public struct FreeFormViewController: UIViewControllerRepresentable { - - private let makeUIViewController: () -> UIViewController - - public init(_ maker: @autoclosure @escaping () -> UIViewController) { - makeUIViewController = maker + public struct FreeForm: UIViewControllerRepresentable { + + public typealias UIViewControllerType = UIViewController + + private let viewControllerMaker: () -> T + + public init(_ view: UIView) { + self.viewControllerMaker = { + let vc = T() + vc.loadViewIfNeeded() + vc.view.ibSubviews { + view.ibAttributes { + $0.ibConstraints(to: vc.view, guide: .view, anchors: .all) + } + } + return vc + } } - - public init(_ maker: @escaping () -> UIViewController) { - makeUIViewController = maker + + public init(_ viewController: T) { + self.viewControllerMaker = { viewController } } - + public init(viewControllerMaker: @escaping () -> T) { + self.viewControllerMaker = viewControllerMaker + } + public func makeUIViewController(context: Context) -> UIViewController { - let containerVC = ContainerViewController() - containerVC.childViewController = makeUIViewController() - return containerVC + let controller = viewControllerMaker() + controller.loadViewIfNeeded() + let freeFormContainer = ViewControllerFreeFormContainer() + freeFormContainer.loadViewIfNeeded() + freeFormContainer.containerView.ibSubviews { superview in + controller.view.ibAttributes { + $0.ibConstraints(to: superview, guide: .view, anchors: .all) + } + } + freeFormContainer.addChild(controller) + controller.willMove(toParent: freeFormContainer) + return freeFormContainer } - - public func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } + + public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } } -} - -extension IBPreview { - - final class ContainerViewController: UIViewController { - var childViewController: UIViewController? { - didSet { - setupChildViewController() + final class ViewControllerFreeFormContainer: UIViewController { + var containerView: UIView! + var heightConstraint: NSLayoutConstraint! + var widthConstraint: NSLayoutConstraint! + private var iPhoneSE2FrameView: UIView! + private var iPhoneSE2WithKeyboardFrameView: UIView! + private var snapToViewFeature: SnapToViewFeature! + private let iPhoneSE2Frame = CGRect(origin: .zero, size: .init(width: 375, height: 667)) + private let iPhoneSE2WithKeyboardFrame = CGRect(origin: .zero, size: .init(width: 375, height: 451)) + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .lightGray + view.ibSubviews { superview in + UIView().ibOutlet(&iPhoneSE2FrameView).ibSubviews { superview in + UILabel().ibAttributes { + $0.ibConstraints(to: superview, guide: .view, anchors: .left, .bottom, .right) + $0.text = "iPhone SE2" + $0.textAlignment = .center + $0.textColor = .cyan + } + }.ibAttributes { + $0.topAnchor.constraint(equalTo: superview.topAnchor) + $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) + $0.widthAnchor.constraint(equalToConstant: iPhoneSE2Frame.width) + $0.heightAnchor.constraint(equalToConstant: iPhoneSE2Frame.height) + $0.layer.borderColor = UIColor.cyan.cgColor + $0.layer.borderWidth = 2 + } + } + view.ibSubviews { superview in + UIView().ibOutlet(&iPhoneSE2WithKeyboardFrameView).ibSubviews { superview in + UILabel().ibAttributes { + $0.ibConstraints(to: superview, guide: .view, anchors: .left, .bottom, .right) + $0.text = "iPhone SE2 With Keyboard" + $0.textAlignment = .center + $0.textColor = UIColor.yellow + } + }.ibAttributes { + $0.topAnchor.constraint(equalTo: superview.topAnchor) + $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) + $0.widthAnchor.constraint(equalToConstant: iPhoneSE2WithKeyboardFrame.width) + $0.heightAnchor.constraint(equalToConstant: iPhoneSE2WithKeyboardFrame.height) + $0.layer.borderColor = UIColor.cyan.cgColor + $0.layer.borderWidth = 2 + } } + view.ibSubviews { superview in + UIView().ibOutlet(&containerView).ibAttributes { + $0.topAnchor.constraint(equalTo: superview.topAnchor) + $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) + $0.widthAnchor.constraint(equalToConstant: view.frame.width).ibOutlet(&widthConstraint) + $0.heightAnchor.constraint(equalToConstant: view.frame.height).ibOutlet(&heightConstraint) + } + } + snapToViewFeature = .init( + viewToSnap: [ + iPhoneSE2WithKeyboardFrameView, + iPhoneSE2FrameView, + view, + ], + containerView: containerView, + controller: self + ) + view.addGestureRecognizer(snapToViewFeature.tapGesture()) } - - private func setupChildViewController() { - guard let childVC = childViewController else { return } - - addChild(childVC) - view.addSubview(childVC.view) - - childVC.view.frame = .init(origin: .zero, size: view.frame.size) - - childVC.didMove(toParent: self) - - let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) - view.addGestureRecognizer(panGesture) + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + workaroundHideKeyboardAndSizeContainerView() } + + private func workaroundHideKeyboardAndSizeContainerView() { + DispatchQueue.main.async { + UIView.performWithoutAnimation { + self.view.endEditing(true) + } + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + guard let touch = touches.first else { return } + let loc = touch.location(in: view) + heightConstraint.constant = loc.y + widthConstraint.constant = loc.x + } + } - @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { - let translation = gesture.translation(in: view) - if let childView = childViewController?.view { - let newWidth = max(20, childView.frame.width + translation.x) - let newHeight = max(20, childView.frame.height + translation.y) - childView.frame.size = CGSize(width: newWidth, height: newHeight) + final class SnapToViewFeature { + + private let viewToSnap: [UIView] + private let containerView: UIView + private let controller: ViewControllerFreeFormContainer + + init(viewToSnap: [UIView], containerView: UIView, controller: ViewControllerFreeFormContainer) { + self.viewToSnap = viewToSnap + self.containerView = containerView + self.controller = controller + } + + func tapGesture() -> UIGestureRecognizer { + UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:))) + } + + @objc + func didTap(gesture: UIGestureRecognizer) { + guard let tapGesture = gesture as? UITapGestureRecognizer else { return } + for viewToSnap in self.viewToSnap { + let loc = tapGesture.location(in: viewToSnap) + if viewToSnap.frame.contains(loc) { + controller.widthConstraint.constant = viewToSnap.frame.width + controller.heightConstraint.constant = viewToSnap.frame.height + return + } } - gesture.setTranslation(.zero, in: view) } + } } diff --git a/Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift b/Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift deleted file mode 100644 index 626fce2..0000000 --- a/Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// IBPreview+FullScreenView.swift -// UIViewKit -// -// Created by Blazej SLEBODA on 16/11/2023. -// - -import UIKit -import SwiftUI - -extension IBPreview { - - @available(iOS 13.0, *) - public struct FullScreenView: UIViewRepresentable { - - private let viewMaker: () -> UIView - - public init(_ viewMaker: @escaping @autoclosure () -> UIView) { - self.viewMaker = viewMaker - } - - public init(_ viewMaker: @escaping () -> UIView) { - self.viewMaker = viewMaker - } - - public func makeUIView(context: Context) -> UIView { - UIView().ibSubviews { containerView in - viewMaker().ibAttributes { - $0.topAnchor.constraint(equalTo: containerView.topAnchor) - $0.leftAnchor.constraint(equalTo: containerView.leftAnchor) - $0.centerXAnchor.constraint(equalTo: containerView.centerXAnchor) - $0.centerYAnchor.constraint(equalTo: containerView.centerYAnchor) - } - } - } - - public func updateUIView(_ uiView: UIView, context: Context) { - uiView.setNeedsUpdateConstraints() - } - } -} diff --git a/Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift b/Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift deleted file mode 100644 index c16bd05..0000000 --- a/Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// IBPreview+FullScreenViewController.swift -// UIViewKit -// -// Created by Blazej SLEBODA on 20/09/2023. -// - -import SwiftUI - -extension IBPreview { - - @available(iOS 13.0, *) - public struct FullScreenViewController: UIViewControllerRepresentable { - - private let viewController: UIViewController - - public init(_ viewController: @escaping @autoclosure () -> UIViewController) { - self.viewController = viewController() - } - - public init(_ viewController: @escaping () -> UIViewController) { - self.viewController = viewController() - } - - public func makeUIViewController(context: Context) -> UIViewController { - viewController - } - - public func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } - } -} diff --git a/Sources/UIViewKit/IBPreviews/IBPreview+SizeThatFitsView.swift b/Sources/UIViewKit/IBPreviews/IBPreview+SizeThatFitsView.swift deleted file mode 100644 index 66532a2..0000000 --- a/Sources/UIViewKit/IBPreviews/IBPreview+SizeThatFitsView.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// IBPreview+SizeThatFitsView.swift -// UIViewKit -// -// Created by Blazej SLEBODA on 13/11/2023. -// - -import SwiftUI - -extension IBPreview { - - @available(iOS 16.0, *) - public struct SizeThatFitsView: UIViewRepresentable { - - private let viewMaker: () -> UIView - - public init(_ viewMaker: @escaping @autoclosure () -> UIView) { - self.viewMaker = viewMaker - } - - public init(_ viewMaker: @escaping () -> UIView) { - self.viewMaker = viewMaker - } - - public func makeUIView(context: Context) -> UIView { - UIView().ibSubviews { containerView in - viewMaker().ibAttributes { - $0.topAnchor.constraint(equalTo: containerView.topAnchor).ibPriority(.init(1)) - $0.leftAnchor.constraint(equalTo: containerView.leftAnchor).ibPriority(.init(1)) - $0.centerXAnchor.constraint(equalTo: containerView.centerXAnchor) - $0.centerYAnchor.constraint(equalTo: containerView.centerYAnchor) - } - } - } - - public func updateUIView(_ uiView: UIView, context: Context) { - uiView.setNeedsUpdateConstraints() - } - - @available(iOS 16, *) - public func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIView, context: Context) -> CGSize? { - uiView.subviews[0].systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) - } - } -} From 049e45c11e7b7239146b4475de5c889cfd45121c Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:57:19 +0200 Subject: [PATCH 02/24] Change color of frame --- .../IBPreviews/IBPreview+FreeFormViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift b/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift index 276059c..b9d9628 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift @@ -33,7 +33,7 @@ extension IBPreview { public init(_ viewController: T) { self.viewControllerMaker = { viewController } } - public init(viewControllerMaker: @escaping () -> T) { + public init(_ viewControllerMaker: @escaping () -> T) { self.viewControllerMaker = viewControllerMaker } @@ -98,7 +98,7 @@ extension IBPreview { $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) $0.widthAnchor.constraint(equalToConstant: iPhoneSE2WithKeyboardFrame.width) $0.heightAnchor.constraint(equalToConstant: iPhoneSE2WithKeyboardFrame.height) - $0.layer.borderColor = UIColor.cyan.cgColor + $0.layer.borderColor = UIColor.yellow.cgColor $0.layer.borderWidth = 2 } } From 0af9c833971f3198a38a69b45eb928d02461f8c4 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:23:09 +0200 Subject: [PATCH 03/24] Delete IBPreview namespace --- .../IBPreview+FreeFormViewController.swift | 177 ------------------ Sources/UIViewKit/IBPreviews/IBPreview.swift | 13 -- .../IBPreviews/IBPreviewFreeForm.swift | 174 +++++++++++++++++ 3 files changed, 174 insertions(+), 190 deletions(-) delete mode 100644 Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift delete mode 100644 Sources/UIViewKit/IBPreviews/IBPreview.swift create mode 100644 Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift diff --git a/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift b/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift deleted file mode 100644 index b9d9628..0000000 --- a/Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift +++ /dev/null @@ -1,177 +0,0 @@ -// -// IBPreview+FreeFormViewController.swift -// UIViewKit -// -// Created by Blazej SLEBODA on 13/11/2023. -// - -import UIKit -import SwiftUI - -extension IBPreview { - - @available(iOS 13.0, *) - public struct FreeForm: UIViewControllerRepresentable { - - public typealias UIViewControllerType = UIViewController - - private let viewControllerMaker: () -> T - - public init(_ view: UIView) { - self.viewControllerMaker = { - let vc = T() - vc.loadViewIfNeeded() - vc.view.ibSubviews { - view.ibAttributes { - $0.ibConstraints(to: vc.view, guide: .view, anchors: .all) - } - } - return vc - } - } - - public init(_ viewController: T) { - self.viewControllerMaker = { viewController } - } - public init(_ viewControllerMaker: @escaping () -> T) { - self.viewControllerMaker = viewControllerMaker - } - - public func makeUIViewController(context: Context) -> UIViewController { - let controller = viewControllerMaker() - controller.loadViewIfNeeded() - let freeFormContainer = ViewControllerFreeFormContainer() - freeFormContainer.loadViewIfNeeded() - freeFormContainer.containerView.ibSubviews { superview in - controller.view.ibAttributes { - $0.ibConstraints(to: superview, guide: .view, anchors: .all) - } - } - freeFormContainer.addChild(controller) - controller.willMove(toParent: freeFormContainer) - return freeFormContainer - } - - public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } - } - - final class ViewControllerFreeFormContainer: UIViewController { - var containerView: UIView! - var heightConstraint: NSLayoutConstraint! - var widthConstraint: NSLayoutConstraint! - private var iPhoneSE2FrameView: UIView! - private var iPhoneSE2WithKeyboardFrameView: UIView! - private var snapToViewFeature: SnapToViewFeature! - private let iPhoneSE2Frame = CGRect(origin: .zero, size: .init(width: 375, height: 667)) - private let iPhoneSE2WithKeyboardFrame = CGRect(origin: .zero, size: .init(width: 375, height: 451)) - - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = .lightGray - view.ibSubviews { superview in - UIView().ibOutlet(&iPhoneSE2FrameView).ibSubviews { superview in - UILabel().ibAttributes { - $0.ibConstraints(to: superview, guide: .view, anchors: .left, .bottom, .right) - $0.text = "iPhone SE2" - $0.textAlignment = .center - $0.textColor = .cyan - } - }.ibAttributes { - $0.topAnchor.constraint(equalTo: superview.topAnchor) - $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) - $0.widthAnchor.constraint(equalToConstant: iPhoneSE2Frame.width) - $0.heightAnchor.constraint(equalToConstant: iPhoneSE2Frame.height) - $0.layer.borderColor = UIColor.cyan.cgColor - $0.layer.borderWidth = 2 - } - } - view.ibSubviews { superview in - UIView().ibOutlet(&iPhoneSE2WithKeyboardFrameView).ibSubviews { superview in - UILabel().ibAttributes { - $0.ibConstraints(to: superview, guide: .view, anchors: .left, .bottom, .right) - $0.text = "iPhone SE2 With Keyboard" - $0.textAlignment = .center - $0.textColor = UIColor.yellow - } - }.ibAttributes { - $0.topAnchor.constraint(equalTo: superview.topAnchor) - $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) - $0.widthAnchor.constraint(equalToConstant: iPhoneSE2WithKeyboardFrame.width) - $0.heightAnchor.constraint(equalToConstant: iPhoneSE2WithKeyboardFrame.height) - $0.layer.borderColor = UIColor.yellow.cgColor - $0.layer.borderWidth = 2 - } - } - view.ibSubviews { superview in - UIView().ibOutlet(&containerView).ibAttributes { - $0.topAnchor.constraint(equalTo: superview.topAnchor) - $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) - $0.widthAnchor.constraint(equalToConstant: view.frame.width).ibOutlet(&widthConstraint) - $0.heightAnchor.constraint(equalToConstant: view.frame.height).ibOutlet(&heightConstraint) - } - } - snapToViewFeature = .init( - viewToSnap: [ - iPhoneSE2WithKeyboardFrameView, - iPhoneSE2FrameView, - view, - ], - containerView: containerView, - controller: self - ) - view.addGestureRecognizer(snapToViewFeature.tapGesture()) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - workaroundHideKeyboardAndSizeContainerView() - } - - private func workaroundHideKeyboardAndSizeContainerView() { - DispatchQueue.main.async { - UIView.performWithoutAnimation { - self.view.endEditing(true) - } - } - } - - override func touchesMoved(_ touches: Set, with event: UIEvent?) { - super.touchesBegan(touches, with: event) - guard let touch = touches.first else { return } - let loc = touch.location(in: view) - heightConstraint.constant = loc.y - widthConstraint.constant = loc.x - } - } - - final class SnapToViewFeature { - - private let viewToSnap: [UIView] - private let containerView: UIView - private let controller: ViewControllerFreeFormContainer - - init(viewToSnap: [UIView], containerView: UIView, controller: ViewControllerFreeFormContainer) { - self.viewToSnap = viewToSnap - self.containerView = containerView - self.controller = controller - } - - func tapGesture() -> UIGestureRecognizer { - UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:))) - } - - @objc - func didTap(gesture: UIGestureRecognizer) { - guard let tapGesture = gesture as? UITapGestureRecognizer else { return } - for viewToSnap in self.viewToSnap { - let loc = tapGesture.location(in: viewToSnap) - if viewToSnap.frame.contains(loc) { - controller.widthConstraint.constant = viewToSnap.frame.width - controller.heightConstraint.constant = viewToSnap.frame.height - return - } - } - } - - } -} diff --git a/Sources/UIViewKit/IBPreviews/IBPreview.swift b/Sources/UIViewKit/IBPreviews/IBPreview.swift deleted file mode 100644 index 9e74dea..0000000 --- a/Sources/UIViewKit/IBPreviews/IBPreview.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// IBPreview.swift -// UIViewKit -// -// Created by Blazej SLEBODA on 10/02/2024. -// - -import Foundation - -public final class IBPreview { - - private init() { } -} diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift new file mode 100644 index 0000000..0ed18bb --- /dev/null +++ b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift @@ -0,0 +1,174 @@ +// +// IBPreview+FreeFormViewController.swift +// UIViewKit +// +// Created by Blazej SLEBODA on 13/11/2023. +// + +import UIKit +import SwiftUI + +@available(iOS 13.0, *) +public struct IBPreviewFreeForm: UIViewControllerRepresentable { + + public typealias UIViewControllerType = UIViewController + + private let viewControllerMaker: () -> T + + public init(_ view: UIView) { + self.viewControllerMaker = { + let vc = T() + vc.loadViewIfNeeded() + vc.view.ibSubviews { + view.ibAttributes { + $0.ibConstraints(to: vc.view, guide: .view, anchors: .all) + } + } + return vc + } + } + + public init(_ viewController: T) { + self.viewControllerMaker = { viewController } + } + public init(_ viewControllerMaker: @escaping () -> T) { + self.viewControllerMaker = viewControllerMaker + } + + public func makeUIViewController(context: Context) -> UIViewController { + let controller = viewControllerMaker() + controller.loadViewIfNeeded() + let freeFormContainer = ViewControllerFreeFormContainer() + freeFormContainer.loadViewIfNeeded() + freeFormContainer.containerView.ibSubviews { superview in + controller.view.ibAttributes { + $0.ibConstraints(to: superview, guide: .view, anchors: .all) + } + } + freeFormContainer.addChild(controller) + controller.willMove(toParent: freeFormContainer) + return freeFormContainer + } + + public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } +} + +final class ViewControllerFreeFormContainer: UIViewController { + var containerView: UIView! + var heightConstraint: NSLayoutConstraint! + var widthConstraint: NSLayoutConstraint! + private var iPhoneSE2FrameView: UIView! + private var iPhoneSE2WithKeyboardFrameView: UIView! + private var snapToViewFeature: SnapToViewFeature! + private let iPhoneSE2Frame = CGRect(origin: .zero, size: .init(width: 375, height: 667)) + private let iPhoneSE2WithKeyboardFrame = CGRect(origin: .zero, size: .init(width: 375, height: 451)) + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .lightGray + view.ibSubviews { superview in + UIView().ibOutlet(&iPhoneSE2FrameView).ibSubviews { superview in + UILabel().ibAttributes { + $0.ibConstraints(to: superview, guide: .view, anchors: .left, .bottom, .right) + $0.text = "iPhone SE2" + $0.textAlignment = .center + $0.textColor = .cyan + } + }.ibAttributes { + $0.topAnchor.constraint(equalTo: superview.topAnchor) + $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) + $0.widthAnchor.constraint(equalToConstant: iPhoneSE2Frame.width) + $0.heightAnchor.constraint(equalToConstant: iPhoneSE2Frame.height) + $0.layer.borderColor = UIColor.cyan.cgColor + $0.layer.borderWidth = 2 + } + } + view.ibSubviews { superview in + UIView().ibOutlet(&iPhoneSE2WithKeyboardFrameView).ibSubviews { superview in + UILabel().ibAttributes { + $0.ibConstraints(to: superview, guide: .view, anchors: .left, .bottom, .right) + $0.text = "iPhone SE2 With Keyboard" + $0.textAlignment = .center + $0.textColor = UIColor.yellow + } + }.ibAttributes { + $0.topAnchor.constraint(equalTo: superview.topAnchor) + $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) + $0.widthAnchor.constraint(equalToConstant: iPhoneSE2WithKeyboardFrame.width) + $0.heightAnchor.constraint(equalToConstant: iPhoneSE2WithKeyboardFrame.height) + $0.layer.borderColor = UIColor.yellow.cgColor + $0.layer.borderWidth = 2 + } + } + view.ibSubviews { superview in + UIView().ibOutlet(&containerView).ibAttributes { + $0.topAnchor.constraint(equalTo: superview.topAnchor) + $0.leadingAnchor.constraint(equalTo: superview.leadingAnchor) + $0.widthAnchor.constraint(equalToConstant: view.frame.width).ibOutlet(&widthConstraint) + $0.heightAnchor.constraint(equalToConstant: view.frame.height).ibOutlet(&heightConstraint) + } + } + snapToViewFeature = .init( + viewToSnap: [ + iPhoneSE2WithKeyboardFrameView, + iPhoneSE2FrameView, + view, + ], + containerView: containerView, + controller: self + ) + view.addGestureRecognizer(snapToViewFeature.tapGesture()) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + workaroundHideKeyboardAndSizeContainerView() + } + + private func workaroundHideKeyboardAndSizeContainerView() { + DispatchQueue.main.async { + UIView.performWithoutAnimation { + self.view.endEditing(true) + } + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + guard let touch = touches.first else { return } + let loc = touch.location(in: view) + heightConstraint.constant = loc.y + widthConstraint.constant = loc.x + } +} + +final class SnapToViewFeature { + + private let viewToSnap: [UIView] + private let containerView: UIView + private let controller: ViewControllerFreeFormContainer + + init(viewToSnap: [UIView], containerView: UIView, controller: ViewControllerFreeFormContainer) { + self.viewToSnap = viewToSnap + self.containerView = containerView + self.controller = controller + } + + func tapGesture() -> UIGestureRecognizer { + UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:))) + } + + @objc + func didTap(gesture: UIGestureRecognizer) { + guard let tapGesture = gesture as? UITapGestureRecognizer else { return } + for viewToSnap in self.viewToSnap { + let loc = tapGesture.location(in: viewToSnap) + if viewToSnap.frame.contains(loc) { + controller.widthConstraint.constant = viewToSnap.frame.width + controller.heightConstraint.constant = viewToSnap.frame.height + return + } + } + } + +} From 6381c8bdd83c53ac956b8ab00dbc8c1bd78325f3 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:33:53 +0200 Subject: [PATCH 04/24] Add support for center constraint which --- Sources/UIViewKit/IBConstraints/IBConstraints.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/UIViewKit/IBConstraints/IBConstraints.swift b/Sources/UIViewKit/IBConstraints/IBConstraints.swift index c91ce4d..edb3a87 100644 --- a/Sources/UIViewKit/IBConstraints/IBConstraints.swift +++ b/Sources/UIViewKit/IBConstraints/IBConstraints.swift @@ -39,6 +39,9 @@ public final class IBConstraints { constraints.append(view.topAnchor.constraint(equalTo: (target as? UILayoutGuide)?.topAnchor ?? (target as! UIView).topAnchor, constant: value)) case .bottom(let value): constraints.append(view.bottomAnchor.constraint(equalTo: (target as? UILayoutGuide)?.bottomAnchor ?? (target as! UIView).bottomAnchor, constant: value)) + case .center: + constraints.append(view.centerXAnchor.constraint(equalTo: (target as? UILayoutGuide)?.centerXAnchor ?? (target as! UIView).centerXAnchor)) + constraints.append(view.centerYAnchor.constraint(equalTo: (target as? UILayoutGuide)?.centerYAnchor ?? (target as! UIView).centerYAnchor)) case .centerX(let value): constraints.append(view.centerXAnchor.constraint(equalTo: (target as? UILayoutGuide)?.centerXAnchor ?? (target as! UIView).centerXAnchor, constant: value)) case .centerY(let value): @@ -66,6 +69,7 @@ public final class IBConstraints { case right(CGFloat) case top(CGFloat) case bottom(CGFloat) + case center case centerX(CGFloat) case centerY(CGFloat) case leading(CGFloat) From bdcbf448d8e601da762ffd6f0a2038d62b99e4ee Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:26:04 +0200 Subject: [PATCH 05/24] Add ContainerView for embeding View Controllers like in Storyboards --- Sources/UIViewKit/Views/File.swift | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Sources/UIViewKit/Views/File.swift diff --git a/Sources/UIViewKit/Views/File.swift b/Sources/UIViewKit/Views/File.swift new file mode 100644 index 0000000..a92e057 --- /dev/null +++ b/Sources/UIViewKit/Views/File.swift @@ -0,0 +1,58 @@ +// +// ContainerView.swift +// UIViewKit +// +// Created by blz on 02/09/2025. +// + +import UIKit + +public class ContainerView: UIView { + private var controllerCreator: (() -> UIViewController)? + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + public init(controller: UIViewController? = nil) { + super.init(frame: .zero) + if let controller = controller { + self.controllerCreator = { controller } + } + } + + public override func didMoveToWindow() { + super.didMoveToWindow() + guard let controllerCreator else { return } + embedViewContrloller(controllerCreator()) + self.controllerCreator = nil + } + + public func embedViewContrloller(_ viewControllerToEmbed: UIViewController) { + guard let parent = nearestViewController() else { return } + guard !subviews.contains(viewControllerToEmbed.view) else { return } + removeChildrenWithRootView(parentViewController: parent) + parent.ibEmbed(viewControllerToEmbed, self) + } + + private func removeChildrenWithRootView(parentViewController parent: UIViewController) { + let controllers = subviews.compactMap { subview in + parent.children.first { $0.view === subview } + } + controllers.forEach { parent.ibUnembed($0) } + } +} + +private extension UIView { + /// Returns the first UIViewController up the responder chain, if any + func nearestViewController() -> UIViewController? { + var responder: UIResponder? = self + while let r = responder { + if let vc = r as? UIViewController { + return vc + } + responder = r.next + } + return nil + } +} From aa9f755d75ffb5e324bc41d96740d55e095cba01 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:47:50 +0200 Subject: [PATCH 06/24] Improve embeding to be aworkable in view definition phase --- .../{File.swift => IBContainerView.swift} | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) rename Sources/UIViewKit/Views/{File.swift => IBContainerView.swift} (66%) diff --git a/Sources/UIViewKit/Views/File.swift b/Sources/UIViewKit/Views/IBContainerView.swift similarity index 66% rename from Sources/UIViewKit/Views/File.swift rename to Sources/UIViewKit/Views/IBContainerView.swift index a92e057..de00699 100644 --- a/Sources/UIViewKit/Views/File.swift +++ b/Sources/UIViewKit/Views/IBContainerView.swift @@ -7,7 +7,8 @@ import UIKit -public class ContainerView: UIView { +public class IBContainerView: UIView { + private var controllerCreator: (() -> UIViewController)? public required init?(coder: NSCoder) { @@ -24,22 +25,17 @@ public class ContainerView: UIView { public override func didMoveToWindow() { super.didMoveToWindow() guard let controllerCreator else { return } - embedViewContrloller(controllerCreator()) self.controllerCreator = nil + embed(controllerCreator()) } - public func embedViewContrloller(_ viewControllerToEmbed: UIViewController) { - guard let parent = nearestViewController() else { return } - guard !subviews.contains(viewControllerToEmbed.view) else { return } - removeChildrenWithRootView(parentViewController: parent) - parent.ibEmbed(viewControllerToEmbed, self) + public func ibEmbed(_ viewControllerToEmbed: UIViewController) { + self.controllerCreator = { viewControllerToEmbed } } - private func removeChildrenWithRootView(parentViewController parent: UIViewController) { - let controllers = subviews.compactMap { subview in - parent.children.first { $0.view === subview } - } - controllers.forEach { parent.ibUnembed($0) } + private func embed(_ viewControllerToEmbed: UIViewController) { + guard let parent = nearestViewController() else { return } + parent.ibEmbed(viewControllerToEmbed, self) } } From ac1053067faf143f5f1beba5f4f120154dfb485e Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:50:59 +0200 Subject: [PATCH 07/24] Add auto-closure for ibEmbed method --- Sources/UIViewKit/Views/IBContainerView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/UIViewKit/Views/IBContainerView.swift b/Sources/UIViewKit/Views/IBContainerView.swift index de00699..a43a8a4 100644 --- a/Sources/UIViewKit/Views/IBContainerView.swift +++ b/Sources/UIViewKit/Views/IBContainerView.swift @@ -29,8 +29,8 @@ public class IBContainerView: UIView { embed(controllerCreator()) } - public func ibEmbed(_ viewControllerToEmbed: UIViewController) { - self.controllerCreator = { viewControllerToEmbed } + public func ibEmbed(_ viewControllerToEmbed: @autoclosure @escaping () -> UIViewController) { + self.controllerCreator = viewControllerToEmbed } private func embed(_ viewControllerToEmbed: UIViewController) { From 03c713ef41fd7bac27dd61adb9d9158dd617a78c Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:55:50 +0200 Subject: [PATCH 08/24] Add support for building a view controller with closure for ibEmbed method --- Sources/UIViewKit/Views/IBContainerView.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/UIViewKit/Views/IBContainerView.swift b/Sources/UIViewKit/Views/IBContainerView.swift index a43a8a4..bfb7cf2 100644 --- a/Sources/UIViewKit/Views/IBContainerView.swift +++ b/Sources/UIViewKit/Views/IBContainerView.swift @@ -29,7 +29,11 @@ public class IBContainerView: UIView { embed(controllerCreator()) } - public func ibEmbed(_ viewControllerToEmbed: @autoclosure @escaping () -> UIViewController) { + public func ibEmbed(_ viewControllerToEmbed: UIViewController) { + self.controllerCreator = { viewControllerToEmbed } + } + + public func ibEmbed(maker viewControllerToEmbed: @escaping () -> UIViewController) { self.controllerCreator = viewControllerToEmbed } From 0d837b983627faa8e4f0947a07fc67750e04ed39 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 12:00:47 +0200 Subject: [PATCH 09/24] Add IB prefixes to Vertical and Horizontal Stacks convenience maker functions --- .../Views/{VerticalStack.swift => IBHorizontalStack.swift} | 6 +++--- .../Views/{HorizontalStack.swift => IBVerticalStack.swift} | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) rename Sources/UIViewKit/Views/{VerticalStack.swift => IBHorizontalStack.swift} (61%) rename Sources/UIViewKit/Views/{HorizontalStack.swift => IBVerticalStack.swift} (78%) diff --git a/Sources/UIViewKit/Views/VerticalStack.swift b/Sources/UIViewKit/Views/IBHorizontalStack.swift similarity index 61% rename from Sources/UIViewKit/Views/VerticalStack.swift rename to Sources/UIViewKit/Views/IBHorizontalStack.swift index 2366adb..5ca7c15 100644 --- a/Sources/UIViewKit/Views/VerticalStack.swift +++ b/Sources/UIViewKit/Views/IBHorizontalStack.swift @@ -1,5 +1,5 @@ // -// VerticalStack.swift +// IBHorizontalStack.swift // UIViewKit // // Created by Blazej SLEBODA on 11/02/2024. @@ -7,9 +7,9 @@ import UIKit -public func VerticalStack(spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) -> UIStackView { +public func IBHorizontalStack(spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) -> UIStackView { let stackView = UIStackView() - stackView.axis = .vertical + stackView.axis = .horizontal if let alignment { stackView.alignment = alignment } diff --git a/Sources/UIViewKit/Views/HorizontalStack.swift b/Sources/UIViewKit/Views/IBVerticalStack.swift similarity index 78% rename from Sources/UIViewKit/Views/HorizontalStack.swift rename to Sources/UIViewKit/Views/IBVerticalStack.swift index 905c279..871a8b5 100644 --- a/Sources/UIViewKit/Views/HorizontalStack.swift +++ b/Sources/UIViewKit/Views/IBVerticalStack.swift @@ -1,5 +1,5 @@ // -// HorizontalStack.swift +// IBVerticalStack.swift // UIViewKit // // Created by Blazej SLEBODA on 11/02/2024. @@ -7,9 +7,9 @@ import UIKit -public func HorizontalStack(spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) -> UIStackView { +public func IBVerticalStack(spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) -> UIStackView { let stackView = UIStackView() - stackView.axis = .horizontal + stackView.axis = .vertical if let alignment { stackView.alignment = alignment } From ba39a0a3a2c1052e99aadb1a7989c94af1fa55cc Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:46:08 +0200 Subject: [PATCH 10/24] Add support for for-loop expressions in ibSuviews --- Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift | 3 ++- .../UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift | 4 ++++ Tests/UIViewKitTests/IBSubviewsTests.swift | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift index 0ed18bb..1ec94cf 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift @@ -31,10 +31,11 @@ public struct IBPreviewFreeForm: UIViewControllerRepresenta public init(_ viewController: T) { self.viewControllerMaker = { viewController } } + public init(_ viewControllerMaker: @escaping () -> T) { self.viewControllerMaker = viewControllerMaker } - + public func makeUIViewController(context: Context) -> UIViewController { let controller = viewControllerMaker() controller.loadViewIfNeeded() diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift b/Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift index 90083ac..17f3301 100644 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift +++ b/Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift @@ -37,6 +37,10 @@ public enum IBSubviewsBuilder { public static func buildEither(second component: [UIView]) -> [UIView] { component } + + public static func buildArray(_ components: [[UIView]]) -> [UIView] { + components.flatMap { $0 } + } } @resultBuilder diff --git a/Tests/UIViewKitTests/IBSubviewsTests.swift b/Tests/UIViewKitTests/IBSubviewsTests.swift index c198d0e..6883a00 100644 --- a/Tests/UIViewKitTests/IBSubviewsTests.swift +++ b/Tests/UIViewKitTests/IBSubviewsTests.swift @@ -73,4 +73,13 @@ class IBSubviewsTests: XCTestCase { subviews.filter { _ = $0; return true } } } + + func testForLoop() throws { + let rootView = UIView().ibSubviews { + for _ in (0...2) { + UIView() + } + } + XCTAssertEqual(rootView.subviews.count, 3) + } } From 6a6ee3aa057b1211578bd4b7b00d6983a5a2157b Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:32:20 +0200 Subject: [PATCH 11/24] Testing Simpler Preview controller Fix containerView is not available --- .../IBPreviews/IBPreviewFreeForm.swift | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift index 1ec94cf..aae9900 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift @@ -9,15 +9,13 @@ import UIKit import SwiftUI @available(iOS 13.0, *) -public struct IBPreviewFreeForm: UIViewControllerRepresentable { +public class IBPreviewFreeForm: ViewControllerFreeFormContainer { - public typealias UIViewControllerType = UIViewController - - private let viewControllerMaker: () -> T + private let viewControllerMaker: () -> UIViewController public init(_ view: UIView) { self.viewControllerMaker = { - let vc = T() + let vc = UIViewController() vc.loadViewIfNeeded() vc.view.ibSubviews { view.ibAttributes { @@ -26,35 +24,39 @@ public struct IBPreviewFreeForm: UIViewControllerRepresenta } return vc } + super.init(nibName: nil, bundle: nil) } - public init(_ viewController: T) { + public init(_ viewController: UIViewController) { self.viewControllerMaker = { viewController } + super.init(nibName: nil, bundle: nil) } - public init(_ viewControllerMaker: @escaping () -> T) { + public init(_ viewControllerMaker: @escaping () -> UIViewController) { self.viewControllerMaker = viewControllerMaker + super.init(nibName: nil, bundle: nil) + } + + public required init?(coder: NSCoder) { + fatalError() } - public func makeUIViewController(context: Context) -> UIViewController { + public override func loadView() { + super.loadView() let controller = viewControllerMaker() controller.loadViewIfNeeded() - let freeFormContainer = ViewControllerFreeFormContainer() - freeFormContainer.loadViewIfNeeded() - freeFormContainer.containerView.ibSubviews { superview in + containerView.ibSubviews { superview in controller.view.ibAttributes { $0.ibConstraints(to: superview, guide: .view, anchors: .all) } } - freeFormContainer.addChild(controller) - controller.willMove(toParent: freeFormContainer) - return freeFormContainer + addChild(controller) + controller.willMove(toParent: self) } - - public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } + } -final class ViewControllerFreeFormContainer: UIViewController { +public class ViewControllerFreeFormContainer: UIViewController { var containerView: UIView! var heightConstraint: NSLayoutConstraint! var widthConstraint: NSLayoutConstraint! @@ -64,8 +66,8 @@ final class ViewControllerFreeFormContainer: UIViewController { private let iPhoneSE2Frame = CGRect(origin: .zero, size: .init(width: 375, height: 667)) private let iPhoneSE2WithKeyboardFrame = CGRect(origin: .zero, size: .init(width: 375, height: 451)) - override func viewDidLoad() { - super.viewDidLoad() + public override func loadView() { + super.loadView() view.backgroundColor = .lightGray view.ibSubviews { superview in UIView().ibOutlet(&iPhoneSE2FrameView).ibSubviews { superview in @@ -121,7 +123,7 @@ final class ViewControllerFreeFormContainer: UIViewController { view.addGestureRecognizer(snapToViewFeature.tapGesture()) } - override func viewDidAppear(_ animated: Bool) { + public override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) workaroundHideKeyboardAndSizeContainerView() } @@ -134,7 +136,7 @@ final class ViewControllerFreeFormContainer: UIViewController { } } - override func touchesMoved(_ touches: Set, with event: UIEvent?) { + public override func touchesMoved(_ touches: Set, with event: UIEvent?) { super.touchesBegan(touches, with: event) guard let touch = touches.first else { return } let loc = touch.location(in: view) From ff05a367f74d4b76b607b5b0ec552f8005754dab Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 21:14:37 +0200 Subject: [PATCH 12/24] Imrove FreeForm and SizeThatFits Previews, Add Representable View Controllers --- .../IBPreviews/IBPreviewFreeForm.swift | 65 +++++++++++-------- .../IBPreviews/IBPreviewSizeThatFits.swift | 65 +++++++++++++++++++ .../IBPreviews/IBRepresentables.swift | 51 +++++++++++++++ 3 files changed, 153 insertions(+), 28 deletions(-) create mode 100644 Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift create mode 100644 Sources/UIViewKit/IBPreviews/IBRepresentables.swift diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift index aae9900..5376f31 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift @@ -1,62 +1,71 @@ // -// IBPreview+FreeFormViewController.swift +// IBPreviewFreeForm.swift // UIViewKit // // Created by Blazej SLEBODA on 13/11/2023. // import UIKit -import SwiftUI @available(iOS 13.0, *) public class IBPreviewFreeForm: ViewControllerFreeFormContainer { - private let viewControllerMaker: () -> UIViewController + private var viewControllerMaker: (() -> UIViewController)? + private var viewMaker: (() -> UIView)? - public init(_ view: UIView) { - self.viewControllerMaker = { - let vc = UIViewController() - vc.loadViewIfNeeded() - vc.view.ibSubviews { - view.ibAttributes { - $0.ibConstraints(to: vc.view, guide: .view, anchors: .all) - } - } - return vc - } + public required init?(coder: NSCoder) { + fatalError() + } + + public init(view: UIView) { super.init(nibName: nil, bundle: nil) + self.viewMaker = { view } } - public init(_ viewController: UIViewController) { - self.viewControllerMaker = { viewController } + public init(_ viewMaker: @escaping () -> UIView) { + super.init(nibName: nil, bundle: nil) + self.viewMaker = viewMaker + } + + public init(viewController: UIViewController) { super.init(nibName: nil, bundle: nil) + self.viewControllerMaker = { viewController } } public init(_ viewControllerMaker: @escaping () -> UIViewController) { - self.viewControllerMaker = viewControllerMaker super.init(nibName: nil, bundle: nil) - } - - public required init?(coder: NSCoder) { - fatalError() + self.viewControllerMaker = viewControllerMaker } public override func loadView() { super.loadView() - let controller = viewControllerMaker() - controller.loadViewIfNeeded() - containerView.ibSubviews { superview in - controller.view.ibAttributes { - $0.ibConstraints(to: superview, guide: .view, anchors: .all) + if let viewMaker = viewMaker { + let viewToPreview = viewMaker() + view.ibSubviews { + viewToPreview.ibAttributes { + $0.ibConstraints(to: view, guide: .view, anchors: .all) + } } + return + } else if let viewControllerMaker = viewControllerMaker { + let controller = viewControllerMaker() + controller.loadViewIfNeeded() + addChild(controller) + containerView.ibSubviews { superview in + controller.view.ibAttributes { + $0.ibConstraints(to: superview, guide: .view, anchors: .all) + } + } + controller.didMove(toParent: self) + } else { + fatalError() } - addChild(controller) - controller.willMove(toParent: self) } } public class ViewControllerFreeFormContainer: UIViewController { + var containerView: UIView! var heightConstraint: NSLayoutConstraint! var widthConstraint: NSLayoutConstraint! diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift b/Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift new file mode 100644 index 0000000..ddc0975 --- /dev/null +++ b/Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift @@ -0,0 +1,65 @@ +// +// IBPreviewSizeThatFits.swift +// UIViewKit +// +// Created by blz on 02/09/2025. +// + +import UIKit + +@available(iOS 13.0, *) +public class IBPreviewSizeThatFits: UIViewController { + + private var viewControllerMaker: (() -> UIViewController)? + private var viewMaker: (() -> UIView)? + + public required init?(coder: NSCoder) { + fatalError() + } + + public init(view: UIView) { + super.init(nibName: nil, bundle: nil) + self.viewMaker = { view } + } + + public init(_ viewMaker: @escaping () -> UIView) { + super.init(nibName: nil, bundle: nil) + self.viewMaker = viewMaker + } + + public init(viewController: UIViewController) { + super.init(nibName: nil, bundle: nil) + self.viewControllerMaker = { viewController } + } + + public init(_ viewControllerMaker: @escaping () -> UIViewController) { + super.init(nibName: nil, bundle: nil) + self.viewControllerMaker = viewControllerMaker + } + + public override func loadView() { + super.loadView() + if let viewMaker = viewMaker { + let viewToPreview = viewMaker() + view.ibSubviews { + viewToPreview.ibAttributes { + $0.ibConstraints(to: view, guide: .view, anchors: .center) + } + } + return + } else if let viewControllerMaker = viewControllerMaker { + let controller = viewControllerMaker() + controller.loadViewIfNeeded() + addChild(controller) + view.ibSubviews { superview in + controller.view.ibAttributes { + $0.ibConstraints(to: superview, guide: .view, anchors: .center) + } + } + controller.didMove(toParent: self) + } else { + fatalError() + } + } + +} diff --git a/Sources/UIViewKit/IBPreviews/IBRepresentables.swift b/Sources/UIViewKit/IBPreviews/IBRepresentables.swift new file mode 100644 index 0000000..7a4e956 --- /dev/null +++ b/Sources/UIViewKit/IBPreviews/IBRepresentables.swift @@ -0,0 +1,51 @@ +// +// IBRepresentableViewController.swift +// UIViewKit +// +// Created by blz on 02/09/2025. +// + +import UIKit +import SwiftUI + +public struct IBRepresentableViewController: UIViewControllerRepresentable { + + public typealias UIViewControllerType = UIViewController + + private let viewControllerMaker: () -> UIViewController + + init(_ viewController: UIViewController) { + viewControllerMaker = { viewController } + } + + init(_ viewControllerMaker: @escaping () -> UIViewController) { + self.viewControllerMaker = viewControllerMaker + } + + public func makeUIViewController(context: Context) -> UIViewController { + viewControllerMaker() + } + + public func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } +} + +public struct IBRepresentableView: UIViewRepresentable { + + public typealias UIViewType = UIView + + private let viewMaker: () -> UIView + + init(_ view: UIView) { + viewMaker = { view } + } + + init (_ viewMaker: @escaping () -> UIView) { + self.viewMaker = viewMaker + } + + public func makeUIView(context: Context) -> UIView { + viewMaker() + } + + public func updateUIView(_ uiView: UIView, context: Context) { } +} From ebc195f163f7cb6f49116fc68af977d17a39dbbf Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 21:17:39 +0200 Subject: [PATCH 13/24] Fix missing public on init methods of RepresentableControllers --- Sources/UIViewKit/IBPreviews/IBRepresentables.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/UIViewKit/IBPreviews/IBRepresentables.swift b/Sources/UIViewKit/IBPreviews/IBRepresentables.swift index 7a4e956..dfffc91 100644 --- a/Sources/UIViewKit/IBPreviews/IBRepresentables.swift +++ b/Sources/UIViewKit/IBPreviews/IBRepresentables.swift @@ -14,11 +14,11 @@ public struct IBRepresentableViewController: UIViewControllerRepresentable { private let viewControllerMaker: () -> UIViewController - init(_ viewController: UIViewController) { + public init(_ viewController: UIViewController) { viewControllerMaker = { viewController } } - init(_ viewControllerMaker: @escaping () -> UIViewController) { + public init(_ viewControllerMaker: @escaping () -> UIViewController) { self.viewControllerMaker = viewControllerMaker } @@ -35,11 +35,11 @@ public struct IBRepresentableView: UIViewRepresentable { private let viewMaker: () -> UIView - init(_ view: UIView) { + public init(_ view: UIView) { viewMaker = { view } } - init (_ viewMaker: @escaping () -> UIView) { + public init (_ viewMaker: @escaping () -> UIView) { self.viewMaker = viewMaker } From ea15b4efa9591643292a41ea5095cfd21dfb40fe Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Tue, 2 Sep 2025 21:32:56 +0200 Subject: [PATCH 14/24] Fix FreeForm is not updating size --- Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift index 5376f31..570e2b5 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift @@ -41,9 +41,9 @@ public class IBPreviewFreeForm: ViewControllerFreeFormContainer { super.loadView() if let viewMaker = viewMaker { let viewToPreview = viewMaker() - view.ibSubviews { + containerView.ibSubviews { superview in viewToPreview.ibAttributes { - $0.ibConstraints(to: view, guide: .view, anchors: .all) + $0.ibConstraints(to: superview, guide: .view, anchors: .all) } } return From 0f5ca5c43f972b3f1b86927b10256f6712638f2b Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:18:50 +0200 Subject: [PATCH 15/24] Add available keyboard for PreviewRepresentable types, Add support SwiftUI Views in IBPreviewFreeForm --- Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift | 6 ++++++ Sources/UIViewKit/IBPreviews/IBRepresentables.swift | 2 ++ 2 files changed, 8 insertions(+) diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift index 570e2b5..277dc56 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift @@ -6,6 +6,7 @@ // import UIKit +import SwiftUI @available(iOS 13.0, *) public class IBPreviewFreeForm: ViewControllerFreeFormContainer { @@ -36,6 +37,11 @@ public class IBPreviewFreeForm: ViewControllerFreeFormContainer { super.init(nibName: nil, bundle: nil) self.viewControllerMaker = viewControllerMaker } + + public init(view: some View) { + super.init(nibName: nil, bundle: nil) + self.viewControllerMaker = { UIHostingController(rootView: view) } + } public override func loadView() { super.loadView() diff --git a/Sources/UIViewKit/IBPreviews/IBRepresentables.swift b/Sources/UIViewKit/IBPreviews/IBRepresentables.swift index dfffc91..8bcc722 100644 --- a/Sources/UIViewKit/IBPreviews/IBRepresentables.swift +++ b/Sources/UIViewKit/IBPreviews/IBRepresentables.swift @@ -8,6 +8,7 @@ import UIKit import SwiftUI +@available(iOS, introduced: 13, obsoleted: 17) public struct IBRepresentableViewController: UIViewControllerRepresentable { public typealias UIViewControllerType = UIViewController @@ -29,6 +30,7 @@ public struct IBRepresentableViewController: UIViewControllerRepresentable { public func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } } +@available(iOS, introduced: 13, obsoleted: 17) public struct IBRepresentableView: UIViewRepresentable { public typealias UIViewType = UIView From 6823123d939251e03d11e0269854c2c84fe7cbeb Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:27:16 +0200 Subject: [PATCH 16/24] Add support for SwiftUI Views maker in IBPreviewFreeForm --- Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift index 277dc56..d7277ca 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift @@ -42,6 +42,11 @@ public class IBPreviewFreeForm: ViewControllerFreeFormContainer { super.init(nibName: nil, bundle: nil) self.viewControllerMaker = { UIHostingController(rootView: view) } } + + public init(_ viewMaker: @escaping () -> some View) { + super.init(nibName: nil, bundle: nil) + self.viewControllerMaker = { UIHostingController(rootView: viewMaker()) } + } public override func loadView() { super.loadView() From b0d7b57e28de95761aa1e7720b136387bf7be61e Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:44:04 +0200 Subject: [PATCH 17/24] Add frame argument to UIStackView convenience init method --- .../UIViewKit/UIKitExtensions/UIStackView+Extensions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift b/Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift index b783952..9d00fc1 100644 --- a/Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift +++ b/Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift @@ -9,8 +9,8 @@ import UIKit extension UIStackView { - public convenience init(axis: NSLayoutConstraint.Axis, spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) { - self.init() + public convenience init(frame: CGRect = .zero, axis: NSLayoutConstraint.Axis, spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) { + self.init(frame: frame) self.axis = axis if let spacing { self.spacing = spacing From 47e644fc90489c2cba5e7b14d09461fdafc207b3 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:24:34 +0200 Subject: [PATCH 18/24] Move Self constraint for UIViewDSL to protocol definition which is more global place --- Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift | 2 +- Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift | 2 +- Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift | 2 +- Sources/UIViewKit/UIViewDSL/UIViewDSL.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift index f36ea7a..db096be 100644 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift +++ b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift @@ -8,7 +8,7 @@ import UIKit @MainActor -extension UIViewDSL where Self: UIView { +extension UIViewDSL { @discardableResult public func ibAttributes(@IBLayoutConstraintBuilder _ block: (Self) -> [NSLayoutConstraint]) -> Self { diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift index 7e6e2f6..91dfd9a 100644 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift +++ b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift @@ -7,7 +7,7 @@ import UIKit -extension UIViewDSL where Self: UIView { +extension UIViewDSL { @discardableResult public func ibOutlet(_ outlet: inout Self?) -> Self { diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift index fb34d4c..a0aac24 100644 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift +++ b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift @@ -8,7 +8,7 @@ import UIKit @MainActor -extension UIViewDSL where Self: UIView { +extension UIViewDSL { @discardableResult public func ibSubviews(@IBSubviewsBuilder _ content: () -> [UIView]) -> Self { diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL.swift b/Sources/UIViewKit/UIViewDSL/UIViewDSL.swift index 4e7621e..3adbbd9 100644 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL.swift +++ b/Sources/UIViewKit/UIViewDSL/UIViewDSL.swift @@ -7,6 +7,6 @@ import UIKit -public protocol UIViewDSL { } +public protocol UIViewDSL where Self: UIView { } extension UIView: UIViewDSL { } From 4efb03601af375e33ef2d2018e31df23e25c2c22 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:29:51 +0200 Subject: [PATCH 19/24] Add umbrela for UIKit and SwiftUI with export to client --- Sources/UIViewKit/UIViewKit.swift | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Sources/UIViewKit/UIViewKit.swift diff --git a/Sources/UIViewKit/UIViewKit.swift b/Sources/UIViewKit/UIViewKit.swift new file mode 100644 index 0000000..54325ba --- /dev/null +++ b/Sources/UIViewKit/UIViewKit.swift @@ -0,0 +1,9 @@ +// +// UIViewKit.swift +// UIViewKit +// +// Created by blz on 03/09/2025. +// + +@_exported import UIKit +@_exported import SwiftUI From 089b5b6a16114eb0a2b9995397c9a7796b81dc92 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:42:33 +0200 Subject: [PATCH 20/24] Add constraint on ibOutlet owner to be AnyObject Any is working well when owner is AnyObject but when owner is a struct then async calls to struct variables makes the Any unusable --- Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift index 91dfd9a..c4195d2 100644 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift +++ b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift @@ -34,13 +34,13 @@ extension UIViewDSL { } @discardableResult - public func ibOutlet(_ owner: Owner?, _ property: ReferenceWritableKeyPath) -> Self { + public func ibOutlet(_ owner: Owner?, _ property: ReferenceWritableKeyPath) -> Self { owner?[keyPath: property] = self return self } @discardableResult - public func ibOutlet(_ owner: Owner?, _ property: ReferenceWritableKeyPath) -> Self { + public func ibOutlet(_ owner: Owner?, _ property: ReferenceWritableKeyPath) -> Self { owner?[keyPath: property] = self return self } From ad966756f0bc4f2dcf1f64cd6c901c14b7d6f7ef Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:46:33 +0200 Subject: [PATCH 21/24] Add explicit MainActor to UIViewDSL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add explicit @MainActor to UIViewDSL, though it seems unnecessary since the protocol’s Self constraint on UIView is already @MainActor-protected by UIView --- Sources/UIViewKit/UIViewDSL/UIViewDSL.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL.swift b/Sources/UIViewKit/UIViewDSL/UIViewDSL.swift index 3adbbd9..826a18f 100644 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL.swift +++ b/Sources/UIViewKit/UIViewDSL/UIViewDSL.swift @@ -7,6 +7,7 @@ import UIKit +@MainActor public protocol UIViewDSL where Self: UIView { } extension UIView: UIViewDSL { } From 6a144b1af82064bb9f16a71ffa82bea4ebd565cb Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:55:54 +0200 Subject: [PATCH 22/24] Rename author to full name --- .../FoundationExtensions/NSObjectProtocol+ibApply.swift | 2 +- Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift | 2 +- Sources/UIViewKit/IBPreviews/IBRepresentables.swift | 2 +- Sources/UIViewKit/UIViewKit.swift | 5 ++++- Sources/UIViewKit/Views/IBContainerView.swift | 2 +- Tests/UIViewKitTests/FoundationExtensionsTests.swift | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/UIViewKit/FoundationExtensions/NSObjectProtocol+ibApply.swift b/Sources/UIViewKit/FoundationExtensions/NSObjectProtocol+ibApply.swift index f316c67..78a41d2 100644 --- a/Sources/UIViewKit/FoundationExtensions/NSObjectProtocol+ibApply.swift +++ b/Sources/UIViewKit/FoundationExtensions/NSObjectProtocol+ibApply.swift @@ -2,7 +2,7 @@ // NSObjectProtocol+ibApply.swift // UIViewKit // -// Created by blz on 22/08/2025. +// Created by Blazej SLEBODA on 22/08/2025. // import Foundation diff --git a/Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift b/Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift index ddc0975..ad98e57 100644 --- a/Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift +++ b/Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift @@ -2,7 +2,7 @@ // IBPreviewSizeThatFits.swift // UIViewKit // -// Created by blz on 02/09/2025. +// Created by Blazej SLEBODA on 02/09/2025. // import UIKit diff --git a/Sources/UIViewKit/IBPreviews/IBRepresentables.swift b/Sources/UIViewKit/IBPreviews/IBRepresentables.swift index 8bcc722..a6cd5fe 100644 --- a/Sources/UIViewKit/IBPreviews/IBRepresentables.swift +++ b/Sources/UIViewKit/IBPreviews/IBRepresentables.swift @@ -2,7 +2,7 @@ // IBRepresentableViewController.swift // UIViewKit // -// Created by blz on 02/09/2025. +// Created by Blazej SLEBODA on 02/09/2025. // import UIKit diff --git a/Sources/UIViewKit/UIViewKit.swift b/Sources/UIViewKit/UIViewKit.swift index 54325ba..a38c719 100644 --- a/Sources/UIViewKit/UIViewKit.swift +++ b/Sources/UIViewKit/UIViewKit.swift @@ -2,8 +2,11 @@ // UIViewKit.swift // UIViewKit // -// Created by blz on 03/09/2025. +// Created by Blazej SLEBODA on 03/09/2025. // +/// Re-exports UIKit and SwiftUI so consumers can `import UIViewKit` only. +/// Note: These transitive imports are part of the public API surface. + @_exported import UIKit @_exported import SwiftUI diff --git a/Sources/UIViewKit/Views/IBContainerView.swift b/Sources/UIViewKit/Views/IBContainerView.swift index bfb7cf2..d534311 100644 --- a/Sources/UIViewKit/Views/IBContainerView.swift +++ b/Sources/UIViewKit/Views/IBContainerView.swift @@ -2,7 +2,7 @@ // ContainerView.swift // UIViewKit // -// Created by blz on 02/09/2025. +// Created by Blazej SLEBODA on 02/09/2025. // import UIKit diff --git a/Tests/UIViewKitTests/FoundationExtensionsTests.swift b/Tests/UIViewKitTests/FoundationExtensionsTests.swift index 4e55e26..af7b038 100644 --- a/Tests/UIViewKitTests/FoundationExtensionsTests.swift +++ b/Tests/UIViewKitTests/FoundationExtensionsTests.swift @@ -2,7 +2,7 @@ // FoundationExtensionsTests.swift // UIViewKit // -// Created by blz on 22/08/2025. +// Created by Blazej SLEBODA on 22/08/2025. // import Testing From c2c88cb7608f890173d86fe64fb8ca7b88f5b0df Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:09:17 +0200 Subject: [PATCH 23/24] Delete .h file Project is dedicated to Swift code bases only --- Sources/UIViewKit/UIViewDSL/UIViewDSL.h | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Sources/UIViewKit/UIViewDSL/UIViewDSL.h diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL.h b/Sources/UIViewKit/UIViewDSL/UIViewDSL.h deleted file mode 100644 index c843bb4..0000000 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// UIViewDSL.h -// UIViewDSL -// -// Created by MaxAir on 12/02/2024. -// - -#import - -//! Project version number for UIViewDSL. -FOUNDATION_EXPORT double UIViewDSLVersionNumber; - -//! Project version string for UIViewDSL. -FOUNDATION_EXPORT const unsigned char UIViewDSLVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - From 4a631f16b944c491f64eeec72efe3a974f7d5cd3 Mon Sep 17 00:00:00 2001 From: Blazej SLEBODA <5544365+Adobels@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:12:17 +0200 Subject: [PATCH 24/24] Add missing MainActor --- Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift index c4195d2..0ed4803 100644 --- a/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift +++ b/Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift @@ -7,6 +7,7 @@ import UIKit +@MainActor extension UIViewDSL { @discardableResult