Skip to content

hyperconnect/HypeUI

Repository files navigation

HypeUI

๐Ÿš€ HypeUI is a implementation of Apple's SwiftUI DSL style based on UIKit Want to enjoy SwiftUI syntax with UIKit? It's time to use HypeUI ๐Ÿ˜Š

Awesome Swift

Why to use HypeUI?
๐Ÿ“ฑ Support iOS 12+
โœจ HypeUI is compatible with UIKit based project using SwiftUI style syntax
๐Ÿ„โ€โ™‚๏ธ Reduce UI & Autolayout codes more than 30%
โ›ณ๏ธ Provide UI binding extension with RxSwift and RxCocoa
๐Ÿฆ„ Easy to use!
โœˆ๏ธ Improve readability and intuitiveness of complex layouts
๐Ÿคฉ Have a blast
โ›ท Customize reusable component, design system
โ„๏ธ Test with accessibility Identifier

Contents

Requirements

  • iOS 12.0+
  • XCode 13.0+
  • Swift 5.0+

Installation

Swift Package Manager

Swift Package Manager is a tool for managing the distribution of Swift code. Itโ€™s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.

Xcode 13+ is required to build HypeUI using Swift Package Manager.

To integrate HypeUI into your Xcode project using Swift Package Manager, add it to the dependencies value of your Package.swift:

dependencies: [
    .package(url: "https://github.com/hyperconnect/HypeUI", .upToNextMajor(from: "0.3.0"))
]

CocoaPod

The preferred installation method is with CocoaPods. Add the following to your Podfile:

pod 'HypeUI'

Supported Features

Supported Features
HStack โœ…
VStack โœ…
ZStack โœ…
Button โœ…
Text โœ…
Image โœ…
ScrollView โœ…
Behavior โœ…
Spacer โœ…
LinearGradient โœ…
AlignmentView โœ…
ViewBuildable โœ…
View Modifier โœ…
Text Modifier โœ…
Stack Modifier โœ…
ScrollView Modifier โœ…
Image Modifier โœ…
UIKit Extensions โœ…

View Modifier

name Description
setHContentHugging Adjusts the priority for a view to resist growing beyond its intrinsic size horizontally.
setVContentHugging Adjusts the priority for a view to resist growing beyond its intrinsic size vertically.
setHContentCompressionResistance Adjusts the priority for a view to resist shrinking below its intrinsic size horizontally.
setVContentCompressionResistance Adjusts the priority for a view to resist shrinking below its intrinsic size vertically.
makeRatio Sets the aspect ratio constraint for the view's size.
cornerRadius Applies a corner radius to the view to create rounded corners.
border Adds a border with specified color and width to the view.
background Sets the background color of the view.
makeContentMode Sets the content mode of the view.
frame Positions the view within a specified frame size.
padding Adds padding around specific edges of the view.
allowsHitTesting Enables or disables the view's interaction with touch events.
masksToBounds Clips the view's sublayers to its boundaries.
accessibilityIdentifier Assigns an identifier used to find this view in tests.
overlay Places specified views in front of the view.
background Layers the views that you specify behind this view.
center Centers the view within a new parent view.
tint Applies a tint color to the view.
opacity Sets the transparency level of the view.
scaleEffect Scales the view by specified factors along the x and y axes.
rotationEffect Rotates the view by a specified angle around a given anchor point.

Text Modifier

name Description
font Sets the font of the text.
foregroundColor Applies a foreground color to the text.
textAligned Sets the alignment of the text within its container.
lineLimit Specifies the maximum number of lines the text can span.
lineBreakMode Defines how text wraps when it reaches the edge of its container.
adjustFontSize Adjusts the font size of the text to fit its width.
minimumScaleFactor Sets the smallest multiplier for text size reduction to fit the width.
preferredMaxLayoutWidth Sets the preferred maximum width for the Text object and enables method chaining.
baselineAdjusted Applies a baseline adjustment to the Text object and enables method chaining.

Stack Modifier

name Description
distributed Modify stack's distribution layout.

ScrollView Modifier

name Description
bounces Modify scroll view bounces.
isPagingEnabled Modify scroll view paging enabled.
isScrollEnabled Modify scroll view enabled.

UIKit Extensions

name Description
shadow Applies a shadow to the view with specified properties
dropShadow Applies a simple drop shadow with default styling
removeShadow Removes any shadow from the view
debouncedAction Adds debounced action to control events with specified delay
keyboardType Sets the keyboard type for text input fields
autocorrectionDisabled Sets whether to disable autocorrection for text input
textContentType Sets the text content type for autofill suggestions
textFieldStyle Sets the text field's border style
submitLabel Sets the submit label for the return key
textInputAutocapitalization Sets the text capitalization style
secureField Sets whether the text field is a secure entry (password field)
textEditorEditable Sets whether the text view is editable
textEditorScrollable Sets whether the text view is scrollable
scrollContentBackground Hides the scroll content background
selection Sets the selected segment index for segmented control (SwiftUI-style)
pickerEnabled Sets whether the segmented control is enabled
selectedSegmentTintColor Sets the background color for the selected segment
segmentedControlBackgroundColor Sets the background color of the segmented control
momentary Sets whether the control momentarily highlights segments when touched
onSelectionChange Adds an action for value changed events

Usage

HStack

HStack(alinement: .center, spacing: 4) {
    Image(Asset.icStar.image)
        .frame(width: 12, height: 12)
    Text()
        .foregroundColor(UIColor.black)
        .font(UIFont.systemFont(ofSize: 14, weight: .regular))
    Spacer()
}

VStack

VStack(spacing: 8) {
    Text()
        .foregroundColor(UIColor.black)
        .font(UIFont.systemFont(ofSize: 14, weight: .regular))
    Spacer()
}

ZStack

ZStack {
    HStack(alinement: .center, spacing: 4) {
        Image(Asset.icStar.image)
            .frame(width: 12, height: 12)
        Text()
            .foregroundColor(UIColor.black)
            .font(UIFont.systemFont(ofSize: 14, weight: .regular))
        Spacer()
    }
    VStack {
        Text()
            .foregroundColor(UIColor.black)
            .font(UIFont.systemFont(ofSize: 14, weight: .regular))
        Spacer()
    }
}

Button

Button(action: { // DO SOMETHING ex) reactor action, closure }) {
    HStack(alignment: .center, spacing: 5.0) {
        Image("cart")
            .padding(.leading, 10.0)
        Text("Add to Cart")
            .foregroundColor(.black)
            .padding(.all, 10.0)
    }
}
.background(Color.gray)
.cornerRadius(5)

Text

Text("โœจ")
    .foregroundColor(UIColor.black)
    .font(UIFont.systemFont(ofSize: 14, weight: .regular))
    .textAligned(.center)
    .background(.white)
    .cornerRadius(16)

Image

Image(Resource.Profile.placeholderImage)
    .frame(width: 48, height: 48)
    .cornerRadius(24)

ScrollView

// MARK: Example
ScrollView(.vertical, showsIndicators: false) {
    VStack(alignment: .fill) {
        Image(image: Asset.imgPopupPrivateCall.image)
            .makeRatio(0.46106)
        Spacer()
            .frame(height: 24)
        VStack {
            viewModel.messages.map { message in
                HStack(alignment: .top, spacing: 8) {
                    Text("โ€ข")
                        .font(UIFont.systemFont(ofSize: 14, weight: .regular))
                        .foregroundColor(.Palette.gray04)
                        .frame(width: 6)
                    Text(message)
                        .font(UIFont.systemFont(ofSize: 14, weight: .regular))
                        .foregroundColor(.Palette.gray04)
                        .lineLimit(2)
                        .lineBreakMode(.byCharWrapping)
                    Spacer()
                        .frame(width: 5)
                }
                .padding(.vertical, 8)
            }
        }
        Spacer()
            .frame(height: 16)
    }
}

@Behavior - It's seems like SwiftUI's @State using DynamicLinkable ๐Ÿ˜Ž

@Behavior var isLive: Bool = false
@Behavior var username: String? = nil
@Behavior var profileImageURL: URL? = nil

// MARK Example
final class SearchHostHistoryViewCell: UICollectionViewCell {
    @Behavior var isLive: Bool = false
    @Behavior var username: String? = nil
    @Behavior var profileImageURL: URL? = nil

    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.addSubviewWithFit(
            ZStack {
                VStack(alignment: .center, spacing: 8) {
                    Image(Resource.Profile.placeholderImage)
                        .linked($profileImageURL.flatMapLatest { $0?.getImage(failover: Resource.Profile.placeholderImage) ?? .just(Resource.Profile.placeholderImage) }, keyPath: \.image)
                        .makeContentMode(.scaleAspectFill)
                        .frame(width: 48, height: 48)
                        .cornerRadius(24)
                        .background(.Palette.gray05)
                    Text("")
                        .linked($username, keyPath: \.text)
                        .textAligned(.center)
                        .foregroundColor(.darkModeSupporting(.Palette.gray01, .Palette.dkGray01))
                        .font(UIFont.systemFont(ofSize: 10, weight: .regular))
                        .frame(height: 12)
                }.padding(UIEdgeInsets(top: 12, left: 0, bottom: 4, right: 0))
                VStack(alignment: .center) {
                    Spacer()
                    Text("LIVE")
                        .foregroundColor(.Palette.white)
                        .font(UIFont.systemFont(ofSize: 8, weight: .bold))
                        .padding(UIEdgeInsets(top: 2, left: 4, bottom: 2, right: 4))
                        .background(.Palette.red)
                        .cornerRadius(4)
                        .linked($isLive.not(), keyPath: \.isHidden)
                    Spacer()
                        .frame(height: 20)
                }
            }
        )
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Spacer

Spacer()
    .frame(width: 10)
Spacer()
    .frame(height: 20)

LinearGradient

ProfileView()
    .background(
        LinearGradient(
            gradient: Gradient(
                stops: [
                    Stop(color: UIColor.black, location: 1.0),
                    Stop(color: UIColor.black, location: 0.2),
                    Stop(color: UIColor.black, location: 0.0)
                ]),
            startPoint: .top,
            endPoint: .bottom
        )
    )

ViewBuildable - Customize UI, Make reusable component and Design System by confirming ViewBuildable protocol.

struct ProfileView: ViewBuildable {
    @Behavior var country: String
    @Behavior var name: String

    func build() -> UIView {
        VStack {
            HStack(alignment: .center, spacing: 12) {
                Text("")
                    .linked($country, keyPath: \.text)
                    .font(UIFont.systemFont(ofSize: 20, weight: .regular))
                    .accessibilityIdentifier("country")
                Text("")
                    .linked($name, keyPath: \.text)
                    .font(UIFont.systemFont(ofSize: 20, weight: .regular))
                    .accessibilityIdentifier("name")
            }
        }
    }
}

UIKit Extensions

Shadow

// Default shadow
Text("Drop Shadow")
    .background(.systemBlue)
    .cornerRadius(8)
    .shadow()

// Custom shadow
Text("Custom Shadow")
    .background(.systemPurple)
    .cornerRadius(8)
    .shadow(color: .systemPurple, radius: 8, offset: CGSize(width: 0, height: 4), opacity: 0.4)

// Remove shadow
view.removeShadow()

Debounced Actions

// Default 0.3s delay
button.debouncedAction {
    print("Action executed!")
}

// Custom delay and events
button.debouncedAction(delay: 1.0, for: .touchUpInside) {
    print("Custom debounced action!")
}

// TextField debouncing
textField.debouncedAction(delay: 0.5, for: .editingChanged) {
    performSearch()
}

Text Input Controls

// TextField with SwiftUI-style modifiers
UITextField()
    .keyboardType(.emailAddress)
    .textFieldStyle(.roundedRect)
    .autocorrectionDisabled()
    .textContentType(.emailAddress)
    .submitLabel(.done)
    .secureField(false)

// TextEditor with SwiftUI-style modifiers  
UITextView()
    .keyboardType(.default)
    .textInputAutocapitalization(.sentences)
    .textEditorScrollable(true)
    .textEditorEditable(true)
    .scrollContentBackground(true)

// Segmented Control with SwiftUI-style modifiers
UISegmentedControl(items: ["Option 1", "Option 2", "Option 3"])
    .selection(0)
    .selectedSegmentTintColor(.systemBlue)
    .pickerEnabled(true)
    .onSelectionChange { index in
        print("Selected: \(index)")
    }

Important: Layer Frame Updates

When using borders or gradients with HypeUI, ensure proper layer sizing by calling updateHypeUILayers() in your view's layoutSubviews:

class MyCustomView: UIView {
    override func layoutSubviews() {
        super.layoutSubviews()
        updateHypeUILayers() // Updates border and gradient layer frames
    }
}

// Or in ViewBuildable components:
struct MyViewBuildable: ViewBuildable {
    func build() -> UIView {
        let containerView = UIView()
        
        // Apply borders and gradients
        containerView
            .gradient(colors: [.red, .blue])
            .border(edges: .all, color: .black, width: 2)
        
        // Container will automatically update layers when bounds change
        return containerView
    }
}

Main Contributors

cruz@hpcnt.com xeon@hpcnt.com owen.j@hpcnt.com dough@hpcnt.com

Dependencies

About

๐ŸŒบ HypeUI is a implementation of Apple's SwiftUI DSL style based on UIKit

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •