Skip to content

Add UIKit component extensions and concept example app#8

Open
cruisediary wants to merge 33 commits intohyperconnect:mainfrom
cruisediary:feature/uikit-extensions
Open

Add UIKit component extensions and concept example app#8
cruisediary wants to merge 33 commits intohyperconnect:mainfrom
cruisediary:feature/uikit-extensions

Conversation

@cruisediary
Copy link
Contributor

@cruisediary cruisediary commented Mar 22, 2026

Description

Overview

Adds SwiftUI-style DSL extensions for common UIKit components and redesigns the Example app into a Concept Gallery with six interactive demos.

Tech Spec

New Extensions

Component Key Modifiers
UISwitch isOn, onTintColor, onChange
UISlider value, minimumValue, maximumValue, minimumTrackTintColor, onChange
UIStepper value, minimumValue, maximumValue, stepValue, onChange
UIProgressView progress, progressTintColor, trackTintColor
UIActivityIndicatorView hidesWhenStopped, color, style
UIPageControl numberOfPages, currentPage, pageIndicatorTintColor, onChange
UIImageView makeContentMode, highlightedImage, animationImages
UIView hidden, disabled, tag, zIndex, fixedSize, scaleEffect, rotationEffect
UIStackView spacing, alignment, layoutMargins

Reactive Binding

  • @Behavior property wrapper — state management backed by BehaviorRelay<T>
  • .linked(_:keyPath:) — two-way binding between UIControl and BehaviorRelay
  • Gradient.init(colors:) convenience initializer added

Example App

Restructured from a single view to a ConceptList → detail navigation flow with six concept screens:

  • Music Player — horizontal paging hero (UIScrollView, isPagingEnabled) with per-track images, track sync via UIScrollViewDelegate,
    real-time elapsed time binding
  • Social Profile — follow/unfollow toggle, thin stat column dividers via ZStack overlay, 2×3 photo grid
  • SettingsUISwitch / UISlider / UIStepper / UIProgressView composition with reactive label bindings
  • Travel CarouselUIPageControl synced to scroll position in real time via UIScrollViewDelegate.scrollViewDidScroll
  • Component Gallery — design system reference covering Typography, Color, Buttons, Gradients, and Surfaces
  • Animation Showcase — 2-column grid of tap-to-trigger animations: Fade, Scale, Rotate, Pulse, and Shake

Note

Medium Risk
Expands the public API surface with new UIKit modifier extensions and event-closure storage via associated objects, plus bumps dependency versions; regressions would primarily affect UI behavior and bindings across the library.

Overview
Adds SwiftUI-style DSL extensions for more UIKit controls. Introduces chainable modifiers for UISwitch, UISlider, UIStepper, UIProgressView, UIActivityIndicatorView, and UIPageControl (including onChange callbacks), plus new UIView modifiers (hidden, disabled, tag, zIndex, fixedSize) and extra UIStackView/UIScrollView configuration helpers.

Enhances existing primitives and documentation/tests. Adds Gradient(colors:) convenience init, expands Image modifiers (highlighting/animation/symbol config), updates README to document the new APIs, and adds/updates unit tests accordingly.

Reworks the Example app into a concept gallery. Switches the root controller to a navigation-driven list and adds multiple demo view controllers (music player, profile, settings, travel carousel, component gallery, animation showcase) along with a small remote-image helper; also updates SPM/CocoaPods dependency constraints and the example’s SwiftPM resolution.

Written by Cursor Bugbot for commit f6cd202. This will update automatically on new commits. Configure here.

…spacing, alignment, layoutMargins modifiers to UIStackView
…te, scroll indicator, contentOffset modifiers to UIScrollView
…d accuracy-based assertions for layout tests
Sets up programmatic navigation with ConceptListViewController as root,
and adds UIImageView+Remote for Lorem Picsum async image loading.
Full-screen player with horizontal paging hero (one image per track),
page-synced track info via UIScrollViewDelegate, playback controls,
real-time elapsed time, shuffle mode, and volume slider.
Profile page with hero image, avatar, follow/unfollow button,
equal-width stat columns (ZStack overlay for thin dividers),
bio text, notification switch, and 2x3 photo grid.
Grouped settings UI with Dark Mode toggle, Brightness slider (full-width,
with live percentage label), Push Notifications toggle, Font Size stepper,
Storage progress bar, and iCloud Sync button with activity indicator.
Horizontal destination carousel with hero images, paging cards,
page control that syncs to scroll position via UIScrollViewDelegate,
star ratings, and category filter chips.
Design-system-style reference page with hero banner, Typography scale,
Color System palette chips, Button variants (primary/secondary/destructive/
ghost/pill/gradient), Gradient swatches, and Surfaces/Layout examples.
2-column grid with Fade, Scale, Rotate, and Pulse animation cards
(tap-to-trigger), plus a full-width Shake card. Cards use fill-aligned
VStack so the preview area renders at full width.
@cruisediary cruisediary force-pushed the feature/uikit-extensions branch from de8a661 to 177c17e Compare March 22, 2026 08:59
- Remove previous target before adding in onChange to prevent handler
  firing N times when called repeatedly on the same control
- Change OBJC_ASSOCIATION_COPY_NONATOMIC to OBJC_ASSOCIATION_RETAIN_NONATOMIC
  for closure storage; Swift closures do not conform to NSCopying
- Schedule playback timer with RunLoop .common mode so progress advances
  during scroll interactions (was pausing in .tracking run loop mode)
Content hugging was missing, allowing views to expand beyond their
intrinsic size. Both compression resistance and hugging are now set
to .required, matching SwiftUI fixedSize() semantics in both directions.
guard let scrollView = self?.carouselScrollView, let self = self else { return }
let offset = CGFloat(page) * (self.cardWidth + 16)
scrollView.setContentOffset(CGPoint(x: offset, y: 0), animated: true)
}
Copy link

Choose a reason for hiding this comment

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

Carousel page offset ignores horizontal content padding

Low Severity

The onChange handler computes the scroll offset as page * (cardWidth + 16), and scrollViewDidScroll computes the page index using the same pageWidth. However, the content HStack has .padding(.horizontal, 24), adding 24pt before the first card. This means the computed offsets don't align with the actual card positions in the content, causing cards to appear slightly misaligned when the user taps a page indicator dot.

Additional Locations (1)
Fix in Cursor Fix in Web

Page control is already updated reactively via .linked($currentTrack,
keyPath: \.currentPage); the stored weak reference was never read.
…ndler visibility

- Exclude current track from shuffle selection in nextTrack()
- Show follower count as integer below 100K to make ±1 changes visible
- Mark @objc handler methods as internal to hide from public API
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

self.progress = 0
let x = CGFloat(page) * UIScreen.main.bounds.width
self.pagingScrollView?.setContentOffset(CGPoint(x: x, y: 0), animated: true)
}
Copy link

Choose a reason for hiding this comment

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

Track change order inconsistency creates latent state bug

Low Severity

The pageControl.onChange handler sets self.currentTrack = page before self.progress = 0, while all other track-change paths (nextTrack, previousTrack, scrollViewDidEndDecelerating) reset progress first. Since the $progress reactive map closure reads self.currentTrack synchronously, this ordering difference means the elapsed-time label briefly computes against the new track's duration for the old progress value. Currently masked because progress is always set to 0 (making the product zero regardless), but any future change to preserve partial progress across tracks would surface an incorrect time display specifically from the page-control path.

Additional Locations (2)
Fix in Cursor Fix in Web

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