Skip to content

Releases: MihaelIsaev/UIKitPlus

🎉Release 2.0.0

22 Sep 20:10
Compare
Choose a tag to compare

All the changes are visible in the readme, new project template, and in updated examples! Enjoy! 🚀

Relative constraints with tags 🔥

18 Apr 01:54
Compare
Choose a tag to compare

Screenshot 2020-04-18 at 05 47 57

Really often we have to create some views with constraints related to each other 😃

Before

Before that release we had to create a variable with view somewhere outside, like this

let someView = UView()

then we used it with other views to make relative constraints

UView {
    someView.size(200).background(.red).centerInSuperview()
    UView().size(100).background(.cyan).centerXInSuperview().top(to: someView)
    UView().size(100).background(.purple).centerXInSuperview().bottom(to: someView)
    UView().size(100).background(.yellow).centerYInSuperview().right(to: someView)
    UView().size(100).background(.green).centerYInSuperview().left(to: someView)
}

Now

But since now we can easily rely just to view's tag

UView {
    UView().size(200).background(.red).centerInSuperview().tag(7)
    UView().size(100).background(.cyan).centerXInSuperview().top(to: 7)
    UView().size(100).background(.purple).centerXInSuperview().bottom(to: 7)
    UView().size(100).background(.yellow).centerYInSuperview().right(to: 7)
    UView().size(100).background(.green).centerYInSuperview().left(to: 7)
}

Even order doesn't matter 🤗

UView {
    UView().size(100).background(.cyan).centerXInSuperview().top(to: 7)
    UView().size(100).background(.purple).centerXInSuperview().bottom(to: 7)
    UView().size(100).background(.yellow).centerYInSuperview().right(to: 7)
    UView().size(100).background(.green).centerYInSuperview().left(to: 7)
    UView().size(200).background(.red).centerInSuperview().tag(7)
}

You even can add view later and all related views will immediately stick to it once it's added 🚀

let v = UView {
    UView().size(100).background(.cyan).centerXInSuperview().top(to: 7)
    UView().size(100).background(.purple).centerXInSuperview().bottom(to: 7)
    UView().size(100).background(.yellow).centerYInSuperview().right(to: 7)
    UView().size(100).background(.green).centerYInSuperview().left(to: 7)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    UIView.animate(withDuration: 1) {
        v.body {
            UView().size(200).background(.red).centerInSuperview().tag(7)
        }
    }
}

Gestures 🎉

17 Apr 21:38
Compare
Choose a tag to compare

Tested, Fixed and Improved all the gestures

We support all available gestures

TapGestureRecognizer

TapGestureRecognizer(taps: 1, touches: 1)

or

TapGestureRecognizer()
    .numberOfTapsRequired(1)
    .numberOfTouchesRequired(1)

💡numberOfTapsRequired and numberOfTouchesRequired also accept @State

also you can conveniently call onTapGesture at any view

.onTapGesture {}
.onTapGesture { view in }
.onTapGesture { view, state in }
.onTapGesture { view, state, recognizer in }
.onTapGesture(taps: 1, touches: 1) { view, state in }
.onTapGesture(taps: 1, touches: 1) { view, state, recognizer in }
.onTapGesture(taps: 1, touches: 1, $someState)
.onTapGesture(taps: 1, touches: 1, on: .ended) { }
.onTapGesture(taps: 1, touches: 1, on: .ended) { view in }

LongPressGestureRecognizer

LongPressGestureRecognizer(taps: 0, touches: 1, minDuration: nil, allowableMovement: nil)

or

LongPressGestureRecognizer(taps: 0, touches: 1)
    .minimumPressDuration(500)
    .allowableMovement(10)

💡minimumPressDuration and allowableMovement also accept @State

also you can conveniently call onLongPressGesture at any view

.onLongPressGesture { view in }
.onLongPressGesture { view, state in }
.onLongPressGesture { view, state, recognizer in }
.onLongPressGesture(taps: 1, touches: 1) { view, state in }
.onLongPressGesture(taps: 1, touches: 1) { view, state, recognizer in }
.onLongPressGesture(taps: 1, touches: 1, $someState)
.onLongPressGesture(taps: 1, touches: 1, on: .ended) { }
.onLongPressGesture(taps: 1, touches: 1, on: .ended) { view in }

PinchGestureRecognizer

PinchGestureRecognizer(scale: 10)

or

PinchGestureRecognizer().scale(10)

💡scale also accept @State

also you can conveniently call onPinchGesture at any view

// there are a lot of convenient combinations of this method
.onPinchGesture { view, state, recognizer in
    print("changed scale: \(recognizer.scale) velocity: \(recognizer.velocity)")
    view.size(200 * r.scale)
}

RotationGestureRecognizer

RotationGestureRecognizer(rotation: 2)

or

RotationGestureRecognizer().rotation(2)

💡rotation also accept @State

also you can conveniently call onRotationGesture at any view

// there are a lot of convenient combinations of this method
.onRotationGesture { view, state, recognizer in
    if state == .changed {
        view.transform = CGAffineTransform(rotationAngle: recognizer.rotation)
    }
}

SwipeGestureRecognizer

SwipeGestureRecognizer(direction: .left, touches: nil)

or

SwipeGestureRecognizer(direction: .left)
    .numberOfTouchesRequired(1)

💡numberOfTouchesRequired also accept @State

also you can conveniently call onSwipeGesture at any view

// there are a lot of convenient combinations of this method
.onSwipeGesture(direction: .left) { view, state, recognizer in }

PanGestureRecognizer

PanGestureRecognizer(minTouches: 1, maxTouches: 2)

or

PanGestureRecognizer()
    .minimumNumberOfTouches(1)
    .maximumNumberOfTouches(1)

💡minimumNumberOfTouches and maximumNumberOfTouches also accept @State

also you can conveniently call onPanGesture at any view

// there are a lot of convenient combinations of this method
.onPanGesture { view, state, recognizer in }

ScreenEdgePanGestureRecognizer

ScreenEdgePanGestureRecognizer(edges: .all)

and additional .edges method gives you an ability to change edges property later

.edges(.left)

💡edges also accept @State

also you can conveniently call onScreenEdgePanGesture at any view

// there are a lot of convenient combinations of this method
.onScreenEdgePanGesture(edges: .left) { view, state, recognizer in }

HoverGestureRecognizer

HoverGestureRecognizer()

you can conveniently call onHoverGesture or hovered at any view

.hovered {
    print("hovered: \($0)")
}

or

.onHoverGesture { view, state, recognizer in
        switch state {
        case .began: 
            print("began")
            view.backgroundColor = .magenta
        case .changed: 
            print("changed") 
            view.backgroundColor = .orange
        case .ended: 
            print("ended") 
            view.backgroundColor = .green
        default: print("default")
        }
}

State tracking

Any gesture recognizer has tracking methods

There are universal trackState method which contains state and optionally recognizer

.trackState { state, recognizer in
    // do switch/case here
}

💡trackState can accept @State

And additionally you have an ability to listen for exact state easily 👍

.onPossible { recognizer in }
.onBegan { recognizer in }
.onChanged { recognizer in }
.onEnded { recognizer in }
.onCancelled { recognizer in }
.onFailed { recognizer in }

💡recognizer is optional

Delegate

Any gesture recognizer has its delegate methods available directly

You can set delegate simply like this

.delegate(...)

Additionally you can use these convenient methods below and even combine them with the classic delegate

.shouldBegin { recognizer in
    return true
}
.shouldReceivePress { recognizer, press in
    return true
}
.shouldReceiveTouch { recognizer, touch in
    return true
}
.shouldRequireFailureOfOtherGestureRecognizer { currentRecognizer, otherRecognizer in
    return false
}
.shouldBeRequiredToFailByOtherGestureRecognizer { currentRecognizer, otherRecognizer in
    return false
}
.shouldRecognizeSimultaneouslyWithOtherGestureRecognizer { currentRecognizer, otherRecognizer in
    return true
}

How to add a gesture to view

UView().gesture(myGesture)

or

UView().gestures(myGesture1, myGesture2)

or even

UView().gestures {
    myGesture1
    myGesture2
    // unlimited amount
}

Multiple gestures at the same time (simple example for pinch + rotate)

UView().size(200).background(.red).centerInSuperview().gestures { v in
    PinchGestureRecognizer().shouldRecognizeSimultaneouslyWithOtherGestureRecognizer { og in
        print("PinchGestureRecognizer shouldRecognizeSimultaneouslyWith: \(og.tag)")
        return true
    }.trackState { s, r in
        if s == .changed {
            v.size(200 * r.scale)
        }
    }.tag(3)
    RotationGestureRecognizer().shouldRecognizeSimultaneouslyWithOtherGestureRecognizer { og in
        print("PinchGestureRecognizer shouldRecognizeSimultaneouslyWith: \(og.tag)")
        return true
    }.trackState { s, r in
        if s == .changed {
            v.transform = CGAffineTransform(rotationAngle: r.rotation)
        }
    }.tag(4)
}

View's touch methods which available without gesture recognizers

.touchesBegan { view, touch, event in }
.touchesMoved { view, touch, event in }
.touchesEnded { view, touch, event in }
.touchesCancelled { view, touch, event in }

💡 All the methods above has its convenient shorter variations

Declarative ViewController

17 Apr 20:05
Compare
Choose a tag to compare

Sometimes you may need to declare view controller quickly without creating a separate file for it

ViewController {
    UView()
    UView()
    UView()
    // add any amount of subviews
}
.background(.gray)
.statusBarStyle(.dark)
.title("Hello world")
.navigationController { nav in }
.navigationItem { navItem in }
.onViewDidLoad { vc in }
.onViewDidLayoutSubviews { vc in }
.onViewWillAppear { vc, animated in }
.onViewWillAppearFirstTime { vc, animated in }
.onViewDidAppear { vc, animated in }
.onViewDidAppearFirstTime { vc, animated in }
.onViewWillDisappear { vc, animated in }
.onViewDidDisappear { vc, animated in }
.touchesBegan { vc, touches, event in }
.touchesMoved { vc, touches, event in }
.touchesCancelled { vc, touches, event in }
.touchesEnded { vc, touches, event in }

Preview group 🔥

08 Apr 15:18
Compare
Choose a tag to compare

It is convenient way to create multiple previews inside one struct

Limitations:

  • only 10 previews inside group
  • rtl and language properties can be set only to group, not to previews directly
#if canImport(SwiftUI)
import SwiftUI
@available(iOS 13.0, *)
struct MyPreviewGroup_Preview: PreviewProvider, DeclarativePreviewGroup {
    static var previewGroup: PreviewGroup {
        PreviewGroup { // 1 to 10 previews inside
            Preview {
                MainViewController()
            }
            .colorScheme(.dark)
            .device(.iPhoneX)
            Preview {
                MainViewController()
            }
            .colorScheme(.light)
            .device(.iPhoneX)
            Preview {
                // in this group title will be shown in `fr` language
                UButton(String(.en("Hello"), .fr("Bonjour"), .ru("Привет")))
                    .circle()
                    .background(.blackHole / .white)
                    .color(.white / .black)
                    .height(54)
                    .edgesToSuperview(h: 8)
                    .centerYInSuperview()
            }
            .colorScheme(.dark)
            .layout(.fixed(width: 300, height: 64))
        }
        .language(.fr) // limited to group
        .rtl(true) // limited to group
    }
}
#endif

photo_2020-04-08 19 54 32

Ready to beat SwiftUI

27 Mar 18:37
Compare
Choose a tag to compare

Hurray, I made UIKitPlus even more powerful than before! It is updated to Swift 5.2 and Xcode 11.4!

For those who doesn't know what is UIKitPlus

It is complex production-ready solution for easy building amazing UI on UIKit.
This is a UIKit-wrapper which makes is declarative, allows to add subviews in SwiftUI style, and gives the most beautiful way to declare constraints! So no more need in SnapKit or its clones! Even more, constraints may be set in advance anywhere, so before you add view to superview! 🚀

You can start using it today even for one or two views. Just try it and you will never want to go back! 🤷

I refactored and updated EXAMPLE PROJECT so now LivePreview works in every file just like in SwiftUI!

In the library's readme you will find detailed examples for everything, bu here I want to highlight some awesome bonus features:

  • easily declare dynamic colors for dark/light modes
let titleColor: UIColor = .black / .white // second color is for dark mode (works automatically ofc)
  • root view controller
    This view controller you could use as enter point to the app and inside it declare routes to other view controllers and its transitions. It gives an ability to easily switch between controllers and support deep links.
    Documentation is here

  • localize your strings easily

String(.en("Hello"),
       .fr("Bonjour"),
       .ru("Привет"),
       .es("Hola"),
       .zh_Hans("你好"),
       .ja("こんにちは"))
  • build attributed strings easily
"Hello".foreground(.darkGrey).font(.articoRegular, 15)
  • and even concatenate them easily
"Hello ".foreground(.green).font(.articoRegular, 15) + "world".foreground(.red).font(.articoRegular, 15)
static var phone: TextField {
    TextField()
        .content(.telephoneNumber)
        .autocapitalization(.none)
        .autocorrection(.no)
        .keyboard(.phonePad)
        .font(.articoRegular, 15)
        .placeholder("(555) 123-4567".foreground(.darkGrey).font(.articoRegular, 15))
        .color(.blackHole)
        .height(18)
        .formatCharacters { textView, range, text in
            // here we use `AnyFormatKit`
            let phoneFormatter = DefaultTextInputFormatter(textPattern: "(###) ###-####")
            let result = phoneFormatter.formatInput(currentText: textView.text ?? "", range: range, replacementString: text)
            textView.text = result.formattedText
    }
}

and even more cool, beautiful and convenience features ❤️ because I'm writing this lib for myself and just would love to share it with you 😃

If you will use it please don't hesitate to ask me any questions! I'm ready to help with anything!

You have a lot of ways to get help:

Implement TextView

09 Feb 12:42
Compare
Choose a tag to compare
@State var text = ""

TextView($text)
    .tag(0)
    .background(.purple)
    .color(.white)
    .tint(.red)
    .placeholder("Placeholder title")
    .corners(4)
    .inputAccessoryView { SomeAccessoryView() }
    .height(88)

Implement VSpace, HSpace, Space

09 Feb 12:38
Compare
Choose a tag to compare
VStack {
    View1()
    VSpace(16) // adds 16pt empty view
    View2()
}
HStack {
    View1()
    HSpace(16) // adds 16pt empty view
    View2()
}
HStack {
    View1().width(100)
    View2().width(32)
    Space() // adds flexible empty view
}

Brand new VScrollStack and HScrollStack ❤️

10 Nov 03:02
Compare
Choose a tag to compare

Use VScrollStack and HScrollStack as simple as VStack and HStack but with scrolling!

It contains methods from both StackView and ScrollView.

body {
    VScrollStack {
        Text("Text on top")        
        View().height(400).color(.red)
        Text("Text 400px from top")
        View().height(400).color(.cyan)
        Text("Text ~800px from top")
        View().height(400).color(.purple)
        Text("Text at the bottom")
    }
    .edgesToSuperview()
    .spacing(16) // stackview method
    .hideAllIndicators() // scrollview method
}

New portion of useful things and fixes

07 Nov 10:34
Compare
Choose a tag to compare
  • DeclarativeProtocol: implement tag method
  • Image: implement more url methods
  • PickerView: implement textColor methods
  • Button: fix reactive title logic
  • TextField: implement shouldReturnToNextResponder
  • ViewController: improve keyboard notification handlers by retrieving animation curve info to flawlessly show/hide keyboard with $keyboardHeight