The markdown parsing is broken/disabled for release notes. Sorry about that, I'm chasing the source of a crash that's been bringing this website down for the last couple of days.
๐Release 2.0.0
2 years ago
All the changes are visible in the readme, new project template, and in updated examples! Enjoy! ๐
Relative constraints with tags ๐ฅ
3 years ago
<p align="center">
<img width="375" alt="Screenshot 2020-04-18 at 05 47 57" src="https://user-images.githubusercontent.com/1272610/79625178-2bba4200-8138-11ea-959b-f7487a45cee5.png">
</p>
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
```swift
let someView = UView()
```
then we used it with other views to make relative constraints
```swift
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
```swift
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 ๐ค
```swift
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 ๐
```swift
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 ๐
3 years ago
## Tested, Fixed and Improved all the gestures
We support all available gestures
### TapGestureRecognizer
```swift
TapGestureRecognizer(taps: 1, touches: 1)
```
or
```swift
TapGestureRecognizer()
.numberOfTapsRequired(1)
.numberOfTouchesRequired(1)
```
> ๐ก`numberOfTapsRequired` and `numberOfTouchesRequired` also accept `@State`
also you can conveniently call `onTapGesture` at any view
```swift
.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
```swift
LongPressGestureRecognizer(taps: 0, touches: 1, minDuration: nil, allowableMovement: nil)
```
or
```swift
LongPressGestureRecognizer(taps: 0, touches: 1)
.minimumPressDuration(500)
.allowableMovement(10)
```
> ๐ก`minimumPressDuration ` and `allowableMovement ` also accept `@State`
also you can conveniently call `onLongPressGesture ` at any view
```swift
.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
```swift
PinchGestureRecognizer(scale: 10)
```
or
```swift
PinchGestureRecognizer().scale(10)
```
> ๐ก`scale ` also accept `@State`
also you can conveniently call `onPinchGesture` at any view
```swift
// 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
```swift
RotationGestureRecognizer(rotation: 2)
```
or
```swift
RotationGestureRecognizer().rotation(2)
```
> ๐ก`rotation ` also accept `@State`
also you can conveniently call `onRotationGesture` at any view
```swift
// 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
```swift
SwipeGestureRecognizer(direction: .left, touches: nil)
```
or
```swift
SwipeGestureRecognizer(direction: .left)
.numberOfTouchesRequired(1)
```
> ๐ก`numberOfTouchesRequired ` also accept `@State`
also you can conveniently call `onSwipeGesture` at any view
```swift
// there are a lot of convenient combinations of this method
.onSwipeGesture(direction: .left) { view, state, recognizer in }
```
### PanGestureRecognizer
```swift
PanGestureRecognizer(minTouches: 1, maxTouches: 2)
```
or
```swift
PanGestureRecognizer()
.minimumNumberOfTouches(1)
.maximumNumberOfTouches(1)
```
> ๐ก`minimumNumberOfTouches ` and `maximumNumberOfTouches ` also accept `@State`
also you can conveniently call `onPanGesture` at any view
```swift
// there are a lot of convenient combinations of this method
.onPanGesture { view, state, recognizer in }
```
### ScreenEdgePanGestureRecognizer
```swift
ScreenEdgePanGestureRecognizer(edges: .all)
```
and additional `.edges` method gives you an ability to change edges property later
```swift
.edges(.left)
```
> ๐ก`edges` also accept `@State`
also you can conveniently call `onScreenEdgePanGesture` at any view
```swift
// there are a lot of convenient combinations of this method
.onScreenEdgePanGesture(edges: .left) { view, state, recognizer in }
```
### HoverGestureRecognizer
```swift
HoverGestureRecognizer()
```
you can conveniently call `onHoverGesture` or `hovered` at any view
```swift
.hovered {
print("hovered: \($0)")
}
```
or
```swift
.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`
```swift
.trackState { state, recognizer in
// do switch/case here
}
```
> ๐ก`trackState` can accept `@State`
And additionally you have an ability to listen for exact state easily ๐
```swift
.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
```swift
.delegate(...)
```
Additionally you can use these convenient methods below and even combine them with the classic delegate
```swift
.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
```swift
UView().gesture(myGesture)
```
or
```swift
UView().gestures(myGesture1, myGesture2)
```
or even
```swift
UView().gestures {
myGesture1
myGesture2
// unlimited amount
}
```
### Multiple gestures at the same time (simple example for pinch + rotate)
```swift
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
```swift
.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
3 years ago
Sometimes you may need to declare view controller quickly without creating a separate file for it
```swift
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 ๐ฅ
3 years ago
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
```swift
#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
```

Ready to beat SwiftUI
3 years ago
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](https://github.com/MihaelIsaev/UIKitPlusExample) so now **LivePreview** works in every file just like in SwiftUI!
In the [library's readme](https://github.com/MihaelIsaev/UIKitPlus) 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
```swift
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](https://github.com/MihaelIsaev/UIKitPlus/blob/master/Readme/RootViewController.md)
- localize your strings easily
```swift
String(.en("Hello"),
.fr("Bonjour"),
.ru("ะัะธะฒะตั"),
.es("Hola"),
.zh_Hans("ไฝ ๅฅฝ"),
.ja("ใใใซใกใฏ"))
```
- build attributed strings easily
```swift
"Hello".foreground(.darkGrey).font(.articoRegular, 15)
```
- and even concatenate them easily
```swift
"Hello ".foreground(.green).font(.articoRegular, 15) + "world".foreground(.red).font(.articoRegular, 15)
```
- format textfields easily! example with [AnyFormatKit](https://github.com/luximetr/AnyFormatKit) lib
```swift
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:
- file an issue here
- write me in Discord directly to `iMike#3049`
- write me in Discord in #ios channel on [Vapor Discord server](https://discordapp.com/invite/vapor)
- write me in Discord in #uikitplus channel in [Swift Stream Discord server](https://discord.gg/q5wCPYv)
Implement TextView
3 years ago
```swift
@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
3 years ago
```swift
VStack {
View1()
VSpace(16) // adds 16pt empty view
View2()
}
```
```swift
HStack {
View1()
HSpace(16) // adds 16pt empty view
View2()
}
```
```swift
HStack {
View1().width(100)
View2().width(32)
Space() // adds flexible empty view
}
```
Brand new VScrollStack and HScrollStack โค๏ธ
3 years ago
Use `VScrollStack` and `HScrollStack` as simple as `VStack` and `HStack` but with scrolling!
It contains methods from both `StackView` and `ScrollView`.
```swift
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
3 years ago
- 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