Skip to content

Add ContentView, Improve size changing in FreeForm Previews, add shortcut for constraints in center#14

Merged
Adobels merged 24 commits intomainfrom
develop
Sep 3, 2025
Merged

Add ContentView, Improve size changing in FreeForm Previews, add shortcut for constraints in center#14
Adobels merged 24 commits intomainfrom
develop

Conversation

@Adobels
Copy link
Owner

@Adobels Adobels commented Sep 2, 2025

Summary by CodeRabbit

  • New Features

    • Added single-anchor centering support in the constraints builder.
    • Introduced a container view to embed child view controllers.
    • Result builder now supports flattening arrays of views.
    • Stack view initializer now accepts an optional frame.
    • Re-exported UIKit and SwiftUI for simpler imports.
  • Breaking Changes

    • Removed SwiftUI preview wrappers (free-form, full-screen, size-that-fits).
    • Renamed stack factories to IBVerticalStack and IBHorizontalStack.
    • UIViewDSL constrained to UIView types; outlet helpers now require reference types.
    • Adjusted DSL extension scopes and actor annotations.
  • Tests

    • Added coverage for creating subviews via loops.

… 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
Fix containerView is not available
@Adobels Adobels self-assigned this Sep 2, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 2, 2025

Warning

Rate limit exceeded

@Adobels has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 23 minutes and 38 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 089b5b6 and 4a631f1.

📒 Files selected for processing (9)
  • Sources/UIViewKit/FoundationExtensions/NSObjectProtocol+ibApply.swift (1 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift (1 hunks)
  • Sources/UIViewKit/IBPreviews/IBRepresentables.swift (1 hunks)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift (2 hunks)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL.h (0 hunks)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL.swift (1 hunks)
  • Sources/UIViewKit/UIViewKit.swift (1 hunks)
  • Sources/UIViewKit/Views/IBContainerView.swift (1 hunks)
  • Tests/UIViewKitTests/FoundationExtensionsTests.swift (1 hunks)

Walkthrough

Adds a new center anchor in constraints, introduces IBContainerView for embedding child view controllers, renames stack factory functions, updates UIStackView initializer to include frame, adjusts UIViewDSL protocol and extensions, adds a result-builder buildArray, re-exports UIKit/SwiftUI, adds a test, and removes multiple SwiftUI preview bridge types.

Changes

Cohort / File(s) Summary of changes
Constraints builder
Sources/UIViewKit/IBConstraints/IBConstraints.swift
Added ViewAnchor.center handling to create centerX and centerY constraints against target guides/views.
SwiftUI preview bridges removal
Sources/UIViewKit/IBPreviews/IBPreview.swift, Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift, Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift, Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift, Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift, Sources/UIViewKit/IBPreviews/IBPreview+SizeThatFitsView.swift
Deleted IBPreview and multiple public SwiftUI wrappers (FreeFormView, FreeFormViewController, FullScreenView, FullScreenViewController, SizeThatFitsView) and related container logic.
UIViewDSL protocol/extension alignment
Sources/UIViewKit/UIViewDSL/UIViewDSL.swift, Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift, Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift, Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift
Constrained UIViewDSL to Self: UIView. Removed where Self: UIView constraints from extensions; dropped @MainActor on IBSubviews extension. Tightened ibOutlet generics to Owner: AnyObject.
Result builder enhancement
Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift
Added public static func buildArray(_ components: [[UIView]]) -> [UIView] to flatten nested view arrays.
UIStackView initializer update
Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift
Changed convenience init to include frame: CGRect = .zero; now calls self.init(frame:). Parameter order updated to frame, axis, ....
New container view
Sources/UIViewKit/Views/IBContainerView.swift
Added IBContainerView to embed a child UIViewController into nearest ancestor VC via responder chain; supports direct and factory-based embedding, triggers on didMoveToWindow.
Stack factory renames/orientation
Sources/UIViewKit/Views/IBHorizontalStack.swift, Sources/UIViewKit/Views/IBVerticalStack.swift
Renamed factories to IBHorizontalStack(...) and IBVerticalStack(...); corresponding axis set to horizontal/vertical respectively.
Module re-exports
Sources/UIViewKit/UIViewKit.swift
Added @_exported import UIKit and @_exported import SwiftUI.
Tests
Tests/UIViewKitTests/IBSubviewsTests.swift
Added testForLoop verifying ibSubviews handles for-generated views (expects 3 subviews).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor UserApp
  participant Container as IBContainerView
  participant Responder as Responder Chain
  participant ParentVC as Nearest UIViewController
  participant ChildVC as UIViewController (to embed)

  rect rgba(230,245,255,0.6)
    note over Container: Setup phase
    UserApp->>Container: ibEmbed(_ vc) or ibEmbed(maker:)
    Container-->>Container: store controllerCreator
  end

  rect rgba(235,255,235,0.6)
    note over Container: Lifecycle
    Container->>Container: didMoveToWindow()
    alt controllerCreator exists
      Container->>Container: make ChildVC
      Container->>Responder: nearestViewController()
      Responder-->>Container: ParentVC
      Container->>ParentVC: ibEmbed(ChildVC, in: Container)
      note right of ParentVC: Adds as child, attaches view to Container
    else
      note over Container: No-op
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

In stacks I hop from left to right,
New centers snap my layout tight.
A container nest for VCs to stay,
While preview bridges hop away.
With flattened views and exports bright—
I thump approval: ship tonight! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (10)
Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift (1)

46-80: Consider mirroring buildArray for constraints builder

Loops that emit constraints will currently fail to compile without buildArray on IBLayoutConstraintBuilder. Add a symmetric helper.

Apply:

 public enum IBLayoutConstraintBuilder {
 
     public static func buildBlock(_ components: [NSLayoutConstraint]...) -> [NSLayoutConstraint] {
         components.flatMap { $0 }
     }
 
+    public static func buildArray(_ components: [[NSLayoutConstraint]]) -> [NSLayoutConstraint] {
+        components.flatMap { $0 }
+    }
+
     public static func buildBlock(_ components: NSLayoutConstraint...) -> [NSLayoutConstraint] {
         components
     }
Sources/UIViewKit/IBConstraints/IBConstraints.swift (2)

42-45: Center anchor implementation looks good; consider offset support later

The zero-offset .center is useful. If you foresee non-zero offsets, a follow-up API (e.g., .centerX(_:) + .centerY(_:) combo, or a new .centerWithOffset(CGPoint)) could reduce boilerplate.


72-72: Add convenience static for .center to match other anchors

Parity with existing static var helpers improves ergonomics.

Apply:

         case trailing(CGFloat)
         case all
         static public var left: ViewAnchor { return .left(.zero) }
         static public var right: ViewAnchor { return .right(.zero) }
         static public var top: ViewAnchor { return .top(.zero) }
         static public var bottom: ViewAnchor { return .bottom(.zero) }
+        static public var center: ViewAnchor { return .center }
         static public var centerX: ViewAnchor { return .centerX(.zero) }
         static public var centerY: ViewAnchor { return .centerY(.zero) }
         static public var leading: ViewAnchor { return .leading(.zero) }
         static public var trailing: ViewAnchor { return .trailing(.zero) }
Sources/UIViewKit/Views/IBVerticalStack.swift (1)

10-13: Use the existing convenience init to reduce duplication

Leverage UIStackView(axis:spacing:alignment:distribution:) for brevity and consistency with IBHorizontalStack.

Apply:

-public func IBVerticalStack(spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) -> UIStackView {
-    let stackView = UIStackView()
-    stackView.axis = .vertical
-    if let alignment {
-        stackView.alignment = alignment
-    }
-    if let distribution {
-        stackView.distribution = distribution
-    }
-    if let spacing {
-        stackView.spacing = spacing
-    }
-    return stackView
-}
+public func IBVerticalStack(spacing: CGFloat? = nil,
+                            alignment: UIStackView.Alignment? = nil,
+                            distribution: UIStackView.Distribution? = nil) -> UIStackView {
+    UIStackView(axis: .vertical, spacing: spacing, alignment: alignment, distribution: distribution)
+}
Sources/UIViewKit/Views/IBHorizontalStack.swift (1)

10-13: Mirror the convenience init usage here as well

Keeps both factories uniform and concise.

Apply:

-public func IBHorizontalStack(spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) -> UIStackView {
-    let stackView = UIStackView()
-    stackView.axis = .horizontal
-    if let alignment {
-        stackView.alignment = alignment
-    }
-    if let distribution {
-        stackView.distribution = distribution
-    }
-    if let spacing {
-        stackView.spacing = spacing
-    }
-    return stackView
-}
+public func IBHorizontalStack(spacing: CGFloat? = nil,
+                              alignment: UIStackView.Alignment? = nil,
+                              distribution: UIStackView.Distribution? = nil) -> UIStackView {
+    UIStackView(axis: .horizontal, spacing: spacing, alignment: alignment, distribution: distribution)
+}
Sources/UIViewKit/IBPreviews/IBRepresentables.swift (1)

42-44: Nit: remove stray space before init parameter label for consistency

Small formatting consistency fix.

-    public init (_ viewMaker: @escaping () -> UIView) {
+    public init(_ viewMaker: @escaping () -> UIView) {
Sources/UIViewKit/Views/IBContainerView.swift (1)

40-43: Pin child view to container with constraints so it resizes with IBContainerView

The current ibEmbed implementation in UIViewController+Extensions uses frames; without autoresizing masks or constraints, the child view won’t follow container size changes.

Add edge constraints after embedding:

 private func embed(_ viewControllerToEmbed: UIViewController) {
     guard let parent = nearestViewController() else { return }
     parent.ibEmbed(viewControllerToEmbed, self)
+    // Ensure child view resizes with the container
+    let childView = viewControllerToEmbed.view!
+    childView.translatesAutoresizingMaskIntoConstraints = false
+    NSLayoutConstraint.activate([
+        childView.topAnchor.constraint(equalTo: topAnchor),
+        childView.leadingAnchor.constraint(equalTo: leadingAnchor),
+        childView.trailingAnchor.constraint(equalTo: trailingAnchor),
+        childView.bottomAnchor.constraint(equalTo: bottomAnchor),
+    ])
 }

If you prefer, consider updating UIViewController.ibEmbed to set constraints instead of frames for a single-source fix.

Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift (2)

40-49: Use the provided closure parameter instead of capturing outer view to improve clarity

Minor readability fix and avoids accidental capture if this code gets moved.

-        if let viewMaker = viewMaker {
-            let viewToPreview = viewMaker()
-            view.ibSubviews {
-                viewToPreview.ibAttributes {
-                    $0.ibConstraints(to: view, guide: .view, anchors: .center)
-                }
-            }
+        if let viewMaker = viewMaker {
+            let viewToPreview = viewMaker()
+            view.ibSubviews { superview in
+                viewToPreview.ibAttributes {
+                    $0.ibConstraints(to: superview, guide: .view, anchors: .center)
+                }
+            }
             return
         } else if let viewControllerMaker = viewControllerMaker {

60-63: Provide a diagnostic message instead of a bare fatalError

Crashing is fine for a preview-only type, but a message helps during misuse.

-        } else {
-            fatalError()
-        }
+        } else {
+            preconditionFailure("IBPreviewSizeThatFits requires either a view or a viewController maker.")
+        }
Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift (1)

115-121: Initial width/height constants rely on view.frame inside loadView

Inside loadView the root view’s frame can be .zero; prefer deferring initial sizing to viewDidLayoutSubviews or using safe area/bounds to avoid starting at size 0x0 on first layout.

Example approach:

  • Initialize with a reasonable default (e.g., 375x667).
  • Or set constants in viewDidLayoutSubviews when view.bounds.size is known.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bdb9ea8 and ea15b4e.

📒 Files selected for processing (15)
  • Sources/UIViewKit/IBConstraints/IBConstraints.swift (2 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift (0 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift (0 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift (0 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift (0 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreview+SizeThatFitsView.swift (0 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreview.swift (0 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift (1 hunks)
  • Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift (1 hunks)
  • Sources/UIViewKit/IBPreviews/IBRepresentables.swift (1 hunks)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift (1 hunks)
  • Sources/UIViewKit/Views/IBContainerView.swift (1 hunks)
  • Sources/UIViewKit/Views/IBHorizontalStack.swift (1 hunks)
  • Sources/UIViewKit/Views/IBVerticalStack.swift (1 hunks)
  • Tests/UIViewKitTests/IBSubviewsTests.swift (1 hunks)
💤 Files with no reviewable changes (6)
  • Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift
  • Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift
  • Sources/UIViewKit/IBPreviews/IBPreview.swift
  • Sources/UIViewKit/IBPreviews/IBPreview+SizeThatFitsView.swift
  • Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift
  • Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift
🧰 Additional context used
🧬 Code graph analysis (9)
Tests/UIViewKitTests/IBSubviewsTests.swift (1)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift (2)
  • ibSubviews (13-17)
  • ibSubviews (19-26)
Sources/UIViewKit/IBConstraints/IBConstraints.swift (1)
Sources/UIViewKit/UIKitExtensions/UIView+Extensions.swift (2)
  • ibConstraints (10-15)
  • ibConstraints (12-14)
Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift (1)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift (3)
  • Self (10-42)
  • ibSubviews (13-17)
  • callAsFunction (28-32)
Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift (5)
Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift (2)
  • loadView (40-63)
  • loadView (78-133)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift (1)
  • ibAttributes (13-18)
Sources/UIViewKit/UIKitExtensions/UIView+Extensions.swift (1)
  • ibConstraints (12-14)
Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift (2)
  • iOS (11-41)
  • iOS (13-40)
Sources/UIViewKit/IBPreviews/IBPreview+SizeThatFitsView.swift (2)
  • iOS (10-45)
  • iOS (12-44)
Sources/UIViewKit/IBPreviews/IBRepresentables.swift (4)
Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift (3)
  • iOS (11-34)
  • iOS (13-33)
  • updateUIViewController (32-32)
Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift (3)
  • iOS (12-30)
  • iOS (10-31)
  • updateUIViewController (29-29)
Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift (2)
  • iOS (11-44)
  • iOS (13-43)
Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift (2)
  • iOS (11-41)
  • iOS (13-40)
Sources/UIViewKit/Views/IBContainerView.swift (1)
Sources/UIViewKit/UIKitExtensions/UIViewController+Extensions.swift (3)
  • ibEmbed (22-27)
  • ibSetView (10-45)
  • ibUnembed (39-44)
Sources/UIViewKit/Views/IBHorizontalStack.swift (2)
Sources/UIViewKit/Views/HorizontalStack.swift (1)
  • HorizontalStack (10-23)
Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift (1)
  • axis (10-25)
Sources/UIViewKit/Views/IBVerticalStack.swift (3)
Sources/UIViewKit/Views/HorizontalStack.swift (1)
  • HorizontalStack (10-23)
Sources/UIViewKit/Views/VerticalStack.swift (1)
  • VerticalStack (10-23)
Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift (1)
  • axis (10-25)
Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift (5)
Sources/UIViewKit/IBPreviews/IBPreviewSizeThatFits.swift (1)
  • loadView (40-63)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift (1)
  • ibAttributes (13-18)
Sources/UIViewKit/UIKitExtensions/UIView+Extensions.swift (1)
  • ibConstraints (12-14)
Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift (2)
  • iOS (11-44)
  • iOS (13-43)
Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift (4)
  • iOS (11-34)
  • iOS (13-33)
  • childViewController (36-70)
  • childViewController (38-69)
🔇 Additional comments (4)
Sources/UIViewKit/UIViewDSL/UIViewDSL+ResultBuilders.swift (1)

41-43: buildArray for subview builder: LGTM

This correctly unlocks for-loop support in @IBSubviewsBuilder and flattens nested arrays.

Tests/UIViewKitTests/IBSubviewsTests.swift (1)

77-84: Loop-based subview creation test: LGTM

This verifies the result-builder buildArray path and guards against regressions.

Sources/UIViewKit/Views/IBHorizontalStack.swift (1)

10-13: All old HorizontalStack/VerticalStack call sites removed
No occurrences of the legacy factory functions were found, so no shims are required.

Sources/UIViewKit/IBPreviews/IBRepresentables.swift (1)

8-12: Remove unnecessary SwiftUI availability guards
The package’s Package.swift specifies a minimum deployment target of iOS 13, so importing SwiftUI and using UIViewControllerRepresentable/UIViewRepresentable is always valid—no #if canImport(SwiftUI) or @available(iOS 13.0, *) needed.

Likely an incorrect or invalid review comment.

Comment on lines +148 to +154
public override func touchesMoved(_ touches: Set<UITouch>, 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
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: calling super.touchesBegan inside touchesMoved

This breaks UIKit’s touch handling chain and can cause unexpected behaviors.

-    public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
-        super.touchesBegan(touches, with: event)
+    public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
+        super.touchesMoved(touches, with: event)
         guard let touch = touches.first else { return }
         let loc = touch.location(in: view)
         heightConstraint.constant = loc.y
         widthConstraint.constant = loc.x
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public override func touchesMoved(_ touches: Set<UITouch>, 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
}
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
guard let touch = touches.first else { return }
let loc = touch.location(in: view)
heightConstraint.constant = loc.y
widthConstraint.constant = loc.x
}
🤖 Prompt for AI Agents
In Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift around lines 148 to 154,
the touchesMoved override incorrectly calls super.touchesBegan which breaks
UIKit’s touch chain; change the call to super.touchesMoved(touches, with: event)
and keep the existing touch handling (guard, location, constraint updates) so
the superclass receives the correct moved event.

Comment on lines +169 to +184
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
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: hit-testing uses frame.contains with a point in the view’s own coordinates

You’re calling location(in: viewToSnap), which returns a point in the view’s local coordinates; compare against bounds, not frame. Also, consider not canceling touches so the content remains interactive.

-    func tapGesture() -> UIGestureRecognizer {
-        UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
-    }
+    func tapGesture() -> UIGestureRecognizer {
+        let g = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
+        g.cancelsTouchesInView = false
+        return g
+    }
@@
-            let loc = tapGesture.location(in: viewToSnap)
-            if viewToSnap.frame.contains(loc) {
+            let loc = tapGesture.location(in: viewToSnap)
+            if viewToSnap.bounds.contains(loc) {
                 controller.widthConstraint.constant = viewToSnap.frame.width
                 controller.heightConstraint.constant = viewToSnap.frame.height
                 return
             }
🤖 Prompt for AI Agents
In Sources/UIViewKit/IBPreviews/IBPreviewFreeForm.swift around lines 169–184,
the tap handler does hit-testing with viewToSnap.frame.contains(loc) using a
point returned by location(in: viewToSnap) (which is in the view’s local
coordinates); change the hit test to use viewToSnap.bounds.contains(loc)
instead, and modify tapGesture() to create a UITapGestureRecognizer instance,
set its target/action, and set recognizer.cancelsTouchesInView = false so the
gesture doesn’t block other touch handling, then return that recognizer.

@Adobels Adobels force-pushed the develop branch 2 times, most recently from 877179f to 6823123 Compare September 3, 2025 09:22
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift (1)

12-24: Optional: mark initializer @mainactor (and consider a sugar overload).

UI work should be confined to the main thread; annotating helps concurrency correctness. Also, keeping an axis-first overload can aid readability.

Apply within this range:

-    public convenience init(frame: CGRect = .zero, axis: NSLayoutConstraint.Axis, spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) {
+    @MainActor public convenience init(frame: CGRect = .zero, axis: NSLayoutConstraint.Axis, spacing: CGFloat? = nil, alignment: UIStackView.Alignment? = nil, distribution: UIStackView.Distribution? = nil) {

Additionally (outside this range), consider adding:

@MainActor
public convenience init(axis: NSLayoutConstraint.Axis,
                        spacing: CGFloat? = nil,
                        alignment: UIStackView.Alignment? = nil,
                        distribution: UIStackView.Distribution? = nil) {
    self.init(frame: .zero, axis: axis, spacing: spacing, alignment: alignment, distribution: distribution)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 877179f and b0d7b57.

📒 Files selected for processing (1)
  • Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift (2)
Tests/UIViewKitTests/UIStackViewExtensionsTests.swift (4)
  • testVertical (11-46)
  • testVerticalWithArguments (23-29)
  • testHorizontalWithArguments (39-45)
  • testVertical (15-21)
Tests/UIViewKitTests/StackViewTests.swift (2)
  • testVerticalStackView (24-31)
  • testHorizontalStackView (15-22)
🔇 Additional comments (1)
Sources/UIViewKit/UIKitExtensions/UIStackView+Extensions.swift (1)

12-24: LGTM: frame-aware initializer is correct and source-compatible.

Uses self.init(frame:) appropriately; defaults preserved when optionals are nil (axis-first calls only appear in tests).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift (2)

10-10: Main-actor isolate these UIView helpers

These APIs touch UI-owned references; isolating the extension to the main actor prevents accidental background usage and aligns with the rest of the DSL.

-import UIKit
-
-extension UIViewDSL {
+import UIKit
+
+@MainActor
+extension UIViewDSL {

37-46: Tighten Owner to AnyObject for ReferenceWritableKeyPath

ReferenceWritableKeyPath requires a class root; constraining Owner clarifies intent and avoids surprising inference.

-    public func ibOutlet<Owner: Any>(_ owner: Owner?, _ property: ReferenceWritableKeyPath<Owner, Self>) -> Self {
+    public func ibOutlet<Owner: AnyObject>(_ owner: Owner?, _ property: ReferenceWritableKeyPath<Owner, Self>) -> Self {
         owner?[keyPath: property] = self
         return self
     }
 
     @discardableResult
-    public func ibOutlet<Owner: Any>(_ owner: Owner?, _ property: ReferenceWritableKeyPath<Owner, Self?>) -> Self {
+    public func ibOutlet<Owner: AnyObject>(_ owner: Owner?, _ property: ReferenceWritableKeyPath<Owner, Self?>) -> Self {
         owner?[keyPath: property] = self
         return self
     }
Sources/UIViewKit/UIViewDSL/UIViewDSL.swift (1)

10-10: Consider annotating the protocol itself with @mainactor

Optional, but it propagates UI-thread guarantees to default impls in protocol extensions.

-public protocol UIViewDSL where Self: UIView { }
+@MainActor
+public protocol UIViewDSL where Self: UIView { }
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift (2)

20-23: Avoid force casts in content wrappers

Use a safe cast + assertion to prevent crashes if misuse ever slips in.

-        let contentWrapper: (UIView) -> [UIView] = { arg1 in
-            content(arg1 as! Self) // swiftlint:disable:this force_cast
-        }
+        let contentWrapper: (UIView) -> [UIView] = { owner in
+            guard let typedOwner = owner as? Self else {
+                assertionFailure("ibSubviews owner type mismatch: expected \(Self.self), got \(type(of: owner))")
+                return []
+            }
+            return content(typedOwner)
+        }
-        let contentWrapper: (UIView) -> [UIView] = { arg1 in
-            content(arg1 as! Self) // swiftlint:disable:this force_cast
-        }
+        let contentWrapper: (UIView) -> [UIView] = { owner in
+            guard let typedOwner = owner as? Self else {
+                assertionFailure("ibSubviews owner type mismatch: expected \(Self.self), got \(type(of: owner))")
+                return []
+            }
+            return content(typedOwner)
+        }

Also applies to: 35-38


28-32: DRY: have callAsFunction forward to ibSubviews

Keeps behavior identical and reduces duplication.

     @discardableResult
     public func callAsFunction(@IBSubviewsBuilder _ content: () -> [UIView]) -> Self {
-        UIViewDSLEngine.shared.addSubviews(content, to: self)
-        return self
+        return ibSubviews(content)
     }
     @discardableResult
     public func callAsFunction(@IBSubviewsBuilder _ content: (Self) -> [UIView]) -> Self {
-        let contentWrapper: (UIView) -> [UIView] = { arg1 in
-            content(arg1 as! Self) // swiftlint:disable:this force_cast
-        }
-        UIViewDSLEngine.shared.addSubviews(contentWrapper, to: self)
-        return self
+        return ibSubviews(content)
     }

Also applies to: 14-17

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7cc5d7f and 47e644f.

📒 Files selected for processing (4)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift (1 hunks)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift (1 hunks)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift (1 hunks)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift
🧰 Additional context used
🧬 Code graph analysis (3)
Sources/UIViewKit/UIViewDSL/UIViewDSL.swift (2)
Sources/UIViewKit/UIViewDSL/AutoresizingStrategy/UIViewDSL+EngineConstraintsProtocol.swift (1)
  • addConstraints (10-19)
Sources/UIViewKit/UIViewDSL/UIViewDSL+Engine.swift (2)
  • shared (10-66)
  • addConstraints (37-42)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift (1)
Sources/UIViewKit/UIViewDSL/UIViewDSL+NSLayoutConstraint.swift (1)
  • ibOutlet (29-33)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift (1)
Sources/UIViewKit/UIViewDSL/UIViewDSL+Engine.swift (3)
  • shared (10-66)
  • addSubviews (68-85)
  • addSubviews (70-84)
🔇 Additional comments (2)
Sources/UIViewKit/UIViewDSL/UIViewDSL.swift (1)

10-12: Verified: only UIView conforms to UIViewDSL
No other explicit conformances found.

Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift (1)

10-11: Extension isolation and header change look good

Unconstraining the extension is fine given the protocol’s Self: UIView bound, and @mainactor keeps UI ops safe.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
Sources/UIViewKit/UIViewKit.swift (2)

8-9: Guard re-exports and avoid hard dependency on platform-only frameworks.

Using @_exported is fine here, but it’s an underscored attribute and you’re re-exporting heavy, platform-scoped modules. Wrap with canImport to keep the package flexible across platforms/toolchains.

Apply:

-@_exported import UIKit
-@_exported import SwiftUI
+#if canImport(UIKit)
+@_exported import UIKit
+#endif
+#if canImport(SwiftUI)
+@_exported import SwiftUI
+#endif

1-7: Optional: replace the file banner with a concise module doc comment.

Keep headers minimal and explain why this file exists.

-//
-//  UIViewKit.swift
-//  UIViewKit
-//
-//  Created by blz 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.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 47e644f and 4efb036.

📒 Files selected for processing (1)
  • Sources/UIViewKit/UIViewKit.swift (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Sources/UIViewKit/UIViewKit.swift (6)
Sources/UIViewKit/IBPreviews/IBPreview+FullScreenView.swift (2)
  • iOS (11-41)
  • iOS (13-40)
Sources/UIViewKit/UIViewDSL/UIViewDSL.swift (2)
  • extension UIView: UIViewDSL { } (12-12)
  • public protocol UIViewDSL { } (10-10)
Sources/UIViewKit/IBPreviews/IBPreview+FullScreenViewController.swift (2)
  • iOS (10-31)
  • iOS (12-30)
Sources/UIViewKit/IBPreviews/IBPreview+FreeFormViewController.swift (1)
  • iOS (11-34)
Sources/UIViewKit/IBPreviews/IBPreview+FreeFormView.swift (2)
  • iOS (13-43)
  • iOS (11-44)
Sources/UIViewKit/UIKitExtensions/UIViewController+Extensions.swift (1)
  • ibSetView (10-45)
🔇 Additional comments (1)
Sources/UIViewKit/UIViewKit.swift (1)

8-9: Confirmed SwiftUI compatibility and API surface
Package.swift specifies iOS 13+ (lines 8–10), matching SwiftUI’s availability. Note that @_exported import UIKit/SwiftUI expands your public API—removing these later is a semver-breaking change.

Any is working well when owner is AnyObject but when owner is a struct then async calls to struct variables makes the Any unusable
Add explicit @mainactor to UIViewDSL, though it seems unnecessary since the protocol’s Self constraint on UIView is already @MainActor-protected by UIView
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift (1)

10-10: Confirm unconstrained extension; consider adding @mainactor for consistency with other UIViewDSL APIs

Dropping where Self: UIView broadens availability. If intentional, fine—but to keep UI APIs consistently main-thread-only (as in IBAttributes/IBSubviews), consider annotating this extension with @MainActor.

-extension UIViewDSL {
+@MainActor
+extension UIViewDSL {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4efb036 and 089b5b6.

📒 Files selected for processing (1)
  • Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift (4)
Sources/UIViewKit/UIViewDSL/UIViewDSL+NSLayoutConstraint.swift (4)
  • ibOutlet (29-33)
  • ibOutlet (35-39)
  • ibOutlet (41-45)
  • ibOutlet (47-51)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBAttributes.swift (1)
  • Self (10-19)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBSubviews.swift (1)
  • Self (10-42)
Sources/UIViewKit/UIViewDSL/UIViewDSL.swift (2)
  • extension UIView: UIViewDSL { } (12-12)
  • public protocol UIViewDSL { } (10-10)
🔇 Additional comments (2)
Sources/UIViewKit/UIViewDSL/UIViewDSL+IBOutlet.swift (2)

37-40: Good call: constrain Owner to AnyObject

Using Owner: AnyObject matches ReferenceWritableKeyPath’s class-root requirement and prevents accidental struct owners. Please mirror this in the NSLayoutConstraint overloads for consistency.

Suggested follow-up in Sources/UIViewKit/UIViewDSL/UIViewDSL+NSLayoutConstraint.swift:

-    public func ibOutlet<Owner: Any>(_ owner: Owner?, _ property: ReferenceWritableKeyPath<Owner, NSLayoutConstraint>) -> Self {
+    public func ibOutlet<Owner: AnyObject>(_ owner: Owner?, _ property: ReferenceWritableKeyPath<Owner, NSLayoutConstraint>) -> Self {

43-46: Likewise here: AnyObject is correct

Same rationale as above; also mirror the optional variant in the NSLayoutConstraint helpers for symmetry.

Follow-up:

-    public func ibOutlet<Owner: Any>(_ owner: Owner?, _ property: ReferenceWritableKeyPath<Owner, NSLayoutConstraint?>) -> Self {
+    public func ibOutlet<Owner: AnyObject>(_ owner: Owner?, _ property: ReferenceWritableKeyPath<Owner, NSLayoutConstraint?>) -> Self {

Repository owner deleted a comment from coderabbitai bot Sep 3, 2025
@Adobels Adobels merged commit 8192637 into main Sep 3, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant