In Xcode 13.0+ go to File -> Swift Packages -> Add Package Dependency and enter there URL of this repo
https://github.com/MihaelIsaev/UIKitPlus
IMPORTANT!
Since version 2 there are a lot of advantages and fixes, and your project could look cleaner since there are no AppDelegate and SceneDelegate anymore, everything is under the hood like with SwiftUI, but it is very obvious and convenient to use any AppDelegate/SceneDelegate methods.
Check it out by creating a project with the new project template!
IMPORTANT!
To support iOS lower than 13 you have to set -weak_framework SwiftUI in Other Linker Flags in Build Settings.
Without that your app gonna crash on iOS lower than 13 because it will try to load SwiftUI without luck.
Project Template! 🍾
To simplify life with UIKitPlus you can download our template!
After that you will be able to go to File -> New -> Project and choose UIKitPlus app! 🚀
💡After project creation you have to install UIKitPlus manually either with Swift Package Manager or with CocoaPods
File Template
Together with project template you will get the file template 👍
Features
1. Delayed constraints
Declare all the constraints in advance before adding view to superview. Even by tags.
Button("Click me").width(300).centerInSuperview()
2. Declarativity
Build everything declarative way. Any view. Any control. Even layers, gestures, colors, fonts, etc.
UText("Hello world").color(.red).alignment(.center).font(.sfProMedium, 15)
// or
UText("Hello ".color(.red).font(.sfProMedium, 15), "world".color(.green).font(.sfProBold, 15)).alignment(.center)
// or even
"Hello world".color(.red).alignment(.center).font(.sfProMedium, 15)
3. Reactivity
Use @UState for any property, react on any thing, map states to different types, etc.
@UState var text = "Hello world"
UText($text)
@UState var number = 5
UText($number.map { "\($0)" })
@UState var bool = false
UText($bool.map { $0 ? "enabled" : "disabled" })
4. Purity
Everything is pretty clear. Clean short code without magic.
5. SwiftUI-like but still beloved UIKit
Declare subviews like in SwiftUI (but don’t forget that we’re still in UIKit and use autolayout)
body {
View1()
View2()
View3()
// btw it is NOT limited to 10
}
6. Reusable and extendable
Declare views or its styles in extensions. Subclass views. Use all the power of OOP.
7. All modern features
Diffable data-source (yes yes for iOS9+). Dynamic colors for light/dark mode. Stateable animations. Reactivity.
8. Everything and even more
Built-in ImageLoader, no need in huge 3rd party libs. Just set URL to Image. Fully customizable and overridable.
UImage(url: "")
UImage(url: "", defaultImage: UIImage(named: "emptyImage")) // set default image to show it while loading
UImage(url: "", loader: .defaultRelease) // release image before start loading
UImage(url: "", loader: .defaultImmediate) // immediate replace image after loading
UImage(url: "", loader: .defaultFade) // replace image with fade effect after loading
UImage(url: "", loader: ImageLoader()) // subclass from `ImageLoader` and set you custom loader here
Easy device model and type detection and ability to set values based on that.
Localization.default = .en // set any localization as default to use it with not covered languages
Localization.current = .en // override current locale
String(.en("Hello"), .fr("Bonjour"), .ru("Привет"))
Custom trait collections.
9. Live Preview
Live preview provided by SwiftUI (available only since macOS Catalina).
The only problem we have is that since names of views are the same in UIKitPlus and SwiftUI we should use aliases like UButton for Button or UView for View, so everything with U prefix. It is only necessary if you want to use live previews, otherwise there is no need to import SwiftUI, so no name conflicts.
Preview single item
💡 You can create as many preview structs as you need
/// 100pt
UView().width(100)
/// Stateable width
@UState var width: CGFloat = 100
UView().width($width)
/// Stateable but based on different type
@UState var expanded = false
UView().width($expanded.map { $0 ? 200 : 100 })
/// Different value for different devices
/// 80pt for iPhone5, 120pt for any iPad, 100pt for any other devices
UView().width(100 !! .iPhone5(80) !! .iPad(150))
height
/// 100pt
UView().height(100)
/// Stateable width
@UState var height: CGFloat = 100
UView().height($width)
/// Stateable but based on different type
@UState var expanded = false
UView().height($expanded.map { $0 ? 200 : 100 })
/// Different value for different devices
/// 80pt for iPhone5, 120pt for any iPad, 100pt for any other devices
UView().height(100 !! .iPhone5(80) !! .iPad(150))
/// all edges to superview 0pt
UView().edgesToSuperview()
/// all edges to superview 16pt
UView().edgesToSuperview(16)
/// horizontal edges: 16pt, vertical edges: 24pt
UView().edgesToSuperview(16, 24)
/// horizontal edges: 16pt
UView().edgesToSuperview(h: 16)
/// vertical edges: 24pt
UView().edgesToSuperview(v: 24)
/// each edge to different value to superview
UView().edgesToSuperview(top: 24, leading: 16, trailing: -16, bottom: -8)
top
/// 16pt to top of superview
UView().topToSuperview(16)
/// 16pt to safeArea top of superview
UView().topToSuperview(16, safeArea: true)
/// Stateable
@UState var top: CGFloat = 16
UView().topToSuperview($top)
/// Stateable but based on different type
@UState var expanded = false
UView().topToSuperview($expanded.map { $0 ? 0 : 16 })
leading
/// 16pt to leading of superview
UView().leadingToSuperview(16)
/// all the same as with topToSuperview
trailing
/// -16pt to trailing of superview
UView().trailingToSuperview(-16)
/// all the same as with topToSuperview
bottom
/// -16pt to bottom of superview
UView().leadingToSuperview(-16)
/// all the same as with topToSuperview
centerX
/// right in center of superview horizontally
UView().centerXInSuperview()
/// 16pt from horizontal center of superview
UView().centerXToSuperview(16)
/// all the same as with topToSuperview
centerY
/// right in center of superview vertically
UView().centerYInSuperview()
/// 16pt from vertical center of superview
UView().centerYToSuperview(16)
/// all the same as with topToSuperview
center
/// right in center of superview both horizontally and vertically
UView().centerInSuperview()
/// 16pt from horizontal center of superview, 8pt from vertical center of superview
UView().centerInSuperview(x: 16, y: 8)
/// all the same as with topToSuperview
width
/// equal width with superview
UView().widthToSuperview()
/// equal width with superview with low priority
UView().widthToSuperview(priority: .defaultLow)
/// half width of superview
UView().widthToSuperview(multipliedBy: 0.5)
/// half width of superview with low priority
UView().widthToSuperview(multipliedBy: 0.5, priority: .defaultLow)
/// all the same as with topToSuperview
height
/// equal height with superview
UView().heightToSuperview()
/// all the same as with widthToSuperview
Read and write view’s super constraints directly. And even animate them.
Any constraint value may be set as CGFloat or with Relation and even Multiplier
// just equal to 10
UView().leading(to: .trailing, of: anotherView, 10)
// greaterThanOrEqual to 10
UView().leading(to: .trailing, of: anotherView, >=10)
// lessThanOrEqual to 10
UView().leading(to: .trailing, of: anotherView, <=10)
// equal to 10 with 1.5 multiplier
UView().leading(to: .trailing, of: anotherView, 10 ~ 1.5)
// equal to 10 with 1.5 multiplier and 999 priority
UView().leading(to: .trailing, of: anotherView, 10 ~ 1.5 ! 999)
// equal to 10 with 1.5 multiplier and `.defaultLow` priority
UView().leading(to: .trailing, of: anotherView, 10 ~ 1.5 ! .defaultLow)
// equal to 10 with 999 priority
UView().leading(to: .trailing, of: anotherView, 10 ! 999)
More about constraints direct access
Ok, let’s imagine that you have a view which is sticked to its superview
let view = UView().edgesToSuperview()
now your view have top, leading, trailing and bottom constraints to its superview and e.g. you want to change top constraint so you could do it like this
view.top = 16
or
view.declarativeConstraints.top?.constant = 16
the same way works with all view’s constraints, so you can change them or even delete them just by setting them nil.
Another situation if you have a view which have a constrain to another relative view
let centerView = UView().background(.black).size(100).centerInSuperview()
let secondView = UView().background(.green).size(100).centerXInSuperview().top(to: .bottom, of: centerView, 16)
and for example you want to reach bottom constraint of centerView related to secondView, do it like this
// short way
centerView.outer[.bottom, secondView] = 32 // changes their vertical spacing from 16 to 32
// long way
centerView.declarativeConstraints.outer[.bottom, secondView]?.constant = 32 // changes their vertical spacing from 16 to 32
This is really bonus view! :D Almost every app now uses verification codes for login and now you can easily implement that code view with UIKitPlus! :)
// implemented. to be described more
UVisualEffectView(.darkBlur)
UVisualEffectView(.lightBlur)
UVisualEffectView(.extraLightBlur)
// iOS10+
UVisualEffectView(.prominent)
UVisualEffectView(.regular)
// iOS13+ (but can be used since iOS9+)
// automatic dynamic effect for light and dark modes
UVisualEffectView(.darkBlur, .lightBlur) // effect will be switched automatically. darkBlur is for light mode.
Create your own extension for your custom effects to use them easily like in example above
extension UIVisualEffect {
public static var darkBlur: UIVisualEffect { return UIBlurEffect(style: .dark) }
}
UWrapperView
It is simple View but with ability to initialize with inner view
and you could specify innerView`s padding right here
// to the same padding for all sides
UWrapperView {
UView()
}.padding(10)
// or to specific padding for each side
UWrapperView {
UView()
}.padding(top: 10, left: 5, right: 10, bottom: 5)
// or even like this
UWrapperView {
UView()
}.padding(top: 10, right: 10)
// set any localization as default
Localization.default = .en
// override current locale
Localization.current = .en
// create string relative to current language
let myString = String(
.en("Hello"),
.fr("Bonjour"),
.ru("Привет"),
.es("Hola"),
.zh_Hans("你好"),
.ja("こんにちは"))
print(myString)
By default current language is equal to Locale.current but you can change it by setting Localizer.current = .en.
Also localizer have default language in case if user’s language doesn’t match any in your string, and you could set it just by calling Localizer.default = .en.
Also you can use localizable strings directly in Button, Text, TextView, TextField and AttributedString
But how to use this awesome localization with 10+ languages in the app?
Just create a dedicated localization file (e.g. Localization.swift) like this
extension String {
static func transferTo(_ wallet: String) -> String {
String(.en("Transfer to #\(wallet)"),
.ru("Перевод на #\(wallet)"),
.zh("转移到 #\(wallet)"),
.ja("#\(wallet)に転送"),
.es("Transferir a #\(wallet)"),
.fr("Transférer au #\(wallet)"),
.sv("Överför till #\(wallet)"),
.de("Übertragen Sie auf #\(wallet)"),
.tr("\(wallet) numarasına aktar"),
.it("Trasferimento al n. \(wallet)"),
.cs("Převod na #\(wallet)"),
.he("\(wallet) העבר למספר"),
.ar("\(wallet)#نقل إلى"))
}
static var copyLink: String {
String(.en("Copy link to clipboard"),
.ru("Скопировать ссылку"),
.zh("复制链接到剪贴板"),
.ja("リンクをクリップボードにコピー"),
.es("Copiar enlace al portapapeles"),
.fr("Copier le lien dans le presse-papiers"),
.sv("Kopiera länk till urklipp"),
.de("Link in Zwischenablage kopieren"),
.tr("Bağlantıyı panoya kopyala"),
.it("Copia il link negli appunti"),
.cs("Zkopírujte odkaz do schránky"),
.he("העתק קישור ללוח"),
.ar("نسخ الرابط إلى الحافظة"))
}
static var copyLinkSucceeded: String {
String(.en("Link has been copied to clipboard"),
.ru("Ссылка успешно скопирована в буфер обмена"),
.zh("链接已复制到剪贴板"),
.ja("リンクがクリップボードにコピーされました"),
.es("El enlace ha sido copiado al portapapeles"),
.fr("Le lien a été copié dans le presse-papiers"),
.sv("Länken har kopierats till Urklipp"),
.de("Der Link wurde in die Zwischenablage kopiert"),
.tr("Bağlantı panoya kopyalandı"),
.it("Il link è stato copiato negli appunti"),
.cs("Odkaz byl zkopírován do schránky"),
.he("הקישור הועתק ללוח"),
.ar("تم نسخ الرابط إلى الحافظة"))
}
static var shareNumber: String {
String(.en("Share number"),
.ru("Поделиться номером"),
.zh("分享号码"),
.ja("共有番号"),
.es("Compartir número"),
.fr("Numéro de partage"),
.sv("Aktienummer"),
.de("Teilenummer"),
.tr("Numarayı paylaş"),
.it("Condividi il numero"),
.cs("Sdílejte číslo"),
.he("מספר שתף"),
.ar("رقم السهم"))
}
static var shareLink: String {
String(.en("Share link"),
.ru("Поделиться ссылкой"),
.zh("分享链接"),
.ja("共有リンク"),
.es("Compartir enlace"),
.fr("Lien de partage"),
.sv("Dela länk"),
.de("Einen Link teilen"),
.tr("Linki paylaş"),
.it("Condividi il link"),
.cs("Sdílet odkaz"),
.he("שתף קישור"),
.ar("مشاركة الرابط"))
}
}
And then use localized string all over the app this easy way
UText(.transferTo("123")) // Transfer to #123
UText(.copyLinkSucceeded) // Copy link to clipboard
UButton(.shareNumber) // Share number
UButton(.shareLink) // Share link
View Controller
// implemented. to be described
Status bar style
In any UViewController you can set statusBarStyle and all its values are iOS9+.
override var statusBarStyle: StatusBarStyle { .default }
override var statusBarStyle: StatusBarStyle { .dark }
override var statusBarStyle: StatusBarStyle { .light }
Colors
/// Simple color
UIColor.red
/// Automatic dynamic color: black for light mode, white for dark mode
UIColor.black / UIColor.white
/// color in hex, represented as int and supported by all color properties
0xFF0000
/// hex color converted to UIColor
0xFF0000.color
/// hex colors as dynamic UIColor
0x000.color / 0xfff.color
/// color with alpha
UIColor.white.alpha(0.5)
/// hex color with alpha
0xFFFFFF.color.alpha(0.5)
// implemented. to be described
/// helper to print all the fonts in console (debug only)
UIFont.printAll()
Add your custom fonts to the project and then declare them like this
import UIKitPlus
extension FontIdentifier {
public static var sfProBold = FontIdentifier("SFProDisplay-Bold")
public static var sfProRegular = FontIdentifier("SFProDisplay-Regular")
public static var sfProMedium = FontIdentifier("SFProDisplay-Medium")
}
let backgroudImage = UImage.welcomeBackground.edgesToSuperview()
With built-in ImageLoader
UImage(url: "")
UImage(url: "", defaultImage: UIImage(named: "emptyImage")) // set default image to show it while loading
UImage(url: "", loader: .defaultRelease) // release image before start loading
UImage(url: "", loader: .defaultImmediate) // immediate replace image after loading
UImage(url: "", loader: .defaultFade) // replace image with fade effect after loading
UImage(url: "", loader: ImageLoader()) // subclass from `ImageLoader` and set you custom loader here
InputView
// implemented. to be described
List
alias is UList
// implemented. to be described
also describe auto-DIFF with Identable models
import UIKitPlus
class MyViewController: ViewController {
lazy var view1 = UView()
override func buildUI() {
super.buildUI()
body {
view1.background(.black).size(100).centerInSuperview()
UView().background(.red).size(30, 20).centerXInSuperview().top(to: .bottom, of: view1, 16)
}
}
}
Example 2
import UIKitPlus
// Just feel how easy you could build & declare your views
// with all needed constraints, properties and actions
// even before adding them to superview!
class LoginViewController: ViewController {
@UState var email = ""
@UState var password = ""
override func buildUI() {
super.buildUI()
view.backgroundColor = .black
body {
UButton.back.onTapGesture { print("back tapped") }
UText.welcome.text("Welcome").centerXInSuperview().topToSuperview(62, safeArea: true)
UVStack {
UTextField.welcome.text($email).placeholder("Email").keyboard(.emailAddress).content(.emailAddress)
UTextField.welcome.text($password).placeholder("Password").content(.password).secure()
UView().height(10) // just to add extra space
UButton.bigBottomGreen.title("Sign In").onTapGesture(signIn)
}.edgesToSuperview(top: 120, leading: 16, trailing: -16)
}
}
func signIn() {
// do an API call to your server with awesome CodyFire lib 😉
}
}
And you just need a few extensions to make it work
// PRO-TIP:
// To avoid mess declare reusable views in extensions like this
extension FontIdentifier {
static var sfProRegular = FontIdentifier("SFProDisplay-Regular")
static var sfProMedium = FontIdentifier("SFProDisplay-Medium")
}
extension UText {
static var title: UText { UText().color(.white).font(.sfProMedium, 18) }
}
extension UTextField {
static var welcome: UTextField {
UTextField()
.height(40)
.background(.clear)
.color(.black)
.tint(.mainGreen)
.border(.bottom, 1, .gray)
.font(.sfProRegular, 16)
}
}
extension UButton {
static var back: UButton { UButton("backIcon").topToSuperview(64).leadingToSuperview(24) }
static var bigBottomGreen: UButton {
UButton()
.color(.white)
.font(.sfProMedium, 15)
.background(.green)
.height(50)
.circle()
.shadow(.gray, opacity: 1, offset: .init(width: 0, height: -1), radius: 10)
}
}
// PRO-TIP2:
// I'd suggest you to use extensions for everything: fonts, images, labels, buttons, colors, etc.
🚀❤️ YOU WILL LOVE UIKIT MORE THAN EVER ❤️🚀
Nothing is impossible!
Build awesome responsive UIs even simpler than with SwiftUI cause you already know everything.
With. Live. Preview. iOS9+.
EXAMPLES
OUR COMMUNITY IN DISCORD
Requirements
Xcode 13.0+
Swift 5.5+
Good mood
Installation
With CocoaPods
Add the following line to your Podfile:
With Swift Package Manager
In Xcode 13.0+ go to
File -> Swift Packages -> Add Package Dependency
and enter there URL of this repoIMPORTANT!
Since version 2 there are a lot of advantages and fixes, and your project could look cleaner since there are no AppDelegate and SceneDelegate anymore, everything is under the hood like with SwiftUI, but it is very obvious and convenient to use any AppDelegate/SceneDelegate methods.
Check it out by creating a project with the new project template!
IMPORTANT!
To support iOS lower than 13 you have to set
-weak_framework SwiftUI
inOther Linker Flags
inBuild Settings
.Without that your app gonna crash on iOS lower than 13 because it will try to load SwiftUI without luck.
Project Template! 🍾
To simplify life with UIKitPlus you can download our template!
For that run the following commands in console
After that you will be able to go to
File -> New -> Project
and chooseUIKitPlus
app! 🚀File Template
Together with project template you will get the file template 👍
Features
1. Delayed constraints
Declare all the constraints in advance before adding view to superview. Even by tags.
2. Declarativity
Build everything declarative way. Any view. Any control. Even layers, gestures, colors, fonts, etc.
3. Reactivity
Use
@UState
for any property, react on any thing, map states to different types, etc.4. Purity
Everything is pretty clear. Clean short code without magic.
5. SwiftUI-like but still beloved UIKit
Declare subviews like in SwiftUI (but don’t forget that we’re still in UIKit and use autolayout)
6. Reusable and extendable
Declare views or its styles in extensions. Subclass views. Use all the power of OOP.
7. All modern features
Diffable data-source (yes yes for iOS9+). Dynamic colors for light/dark mode. Stateable animations. Reactivity.
8. Everything and even more
Built-in
ImageLoader
, no need in huge 3rd party libs. Just set URL toImage
. Fully customizable and overridable.Easy device model and type detection and ability to set values based on that.
Localizable strings
Custom trait collections.
9. Live Preview
Live preview provided by SwiftUI (available only since macOS Catalina).
Preview single item
ViewController
exampleView
examplePreview group 🔥
It is just convenient way to create multiple previews inside one struct
Limitations:
rtl
andlanguage
properties can be set only to group, not to previews directlyUsage
Even no need to import
UIKit
at all!Constraints
Solo
aspectRatio
width
height
size
Read and write view’s solo constraints directly. And even animate them.
Super
edges
top
leading
trailing
bottom
centerX
centerY
center
width
height
Read and write view’s super constraints directly. And even animate them.
Relative
top
leading
trailing
bottom
left
right
centerX
centerY
center
width
height
equal
Relative constraints by tags 🔥
Really often we have to create some views with constraints related to each other 😃
The classic way is to create a variable with view somewhere outside, like this
then we used it with other views to make relative constraints
But if it’s not necessary to declare view outside the you can use tag! And easily rely to it from other views!
Even order doesn’t matter 🤗
You even can add view later and all related views will immediately stick to it once it’s added 🚀
Extra
Any constraint value may be set as
CGFloat
or withRelation
and evenMultiplier
More about constraints direct access
Ok, let’s imagine that you have a view which is sticked to its superview
now your view have top, leading, trailing and bottom constraints to its superview and e.g. you want to change
top
constraint so you could do it like thisor
the same way works with all view’s constraints, so you can change them or even delete them just by setting them
nil
.Another situation if you have a view which have a constrain to another relative view
and for example you want to reach bottom constraint of
centerView
related tosecondView
, do it like thisRoot View Controller 🍀
Detailed instruction
View
View may be created with empty initializer
or you can put subviews into it right while initialization
or you can wrap some view using
inline
keyword so that inner view will stick all edges to superviewalso you can add subviews to that superview by calling
.body { ... }
method. even multiple times.VerificationCodeView
// implemented. to be described more
This is really bonus view! :D Almost every app now uses verification codes for login and now you can easily implement that code view with UIKitPlus! :)
VisualEffectView
Create your own extension for your custom effects to use them easily like in example above
UWrapperView
It is simple
View
but with ability to initialize with inner viewand you could specify innerView`s padding right here
LayerView
// implemented. to be described
Impact Feedback
My favourite feature.
Localization 🇮🇸🇩🇪🇯🇵🇲🇽
By default current language is equal to
Locale.current
but you can change it by settingLocalizer.current = .en
. Also localizer havedefault
language in case if user’s language doesn’t match any in your string, and you could set it just by callingLocalizer.default = .en
.Also you can use localizable strings directly in Button, Text, TextView, TextField and AttributedString
But how to use this awesome localization with 10+ languages in the app?
Just create a dedicated localization file (e.g.
Localization.swift
) like thisAnd then use localized string all over the app this easy way
View Controller
// implemented. to be described
Status bar style
In any
UViewController
you can setstatusBarStyle
and all its values are iOS9+.Colors
Declare custom colors like this
and then use them just like
Fonts
Add your custom fonts to the project and then declare them like this
and then use them just like
Gestures
Detailed instruction
States
Attributed Strings
Animations
// implemented. to be described
Activity Indicator
// implemented. to be described
Bar Button Item
// implemented. to be described
Button
// to be described more
background and background for highlighted state
title color for different states
set some font from declared identifiers or with system fonts
add image
You can handle tap action easily
or like this
Declare custom buttons like this
and then use them like this
Collection
ControlView
// implemented. to be described
DatePicker
// implemented. to be described
DynamicPickerView
// implemented. to be described
StackView
// implemented. to be described
VStack
// implemented. to be described more
The same asStackView
but with predefined axis and ability to easily add arranged subviewsVScrollStack
HStack
// implemented. to be described more
The same asStackView
but with predefined axis and ability to easily add arranged subviewsHScrollStack
HSpace
VSpace
Space
HUD
// implemented. to be described
Image
// to be described more
Declare asset images like this
and then use them like this
With built-in
ImageLoader
InputView
// implemented. to be described
List
TableView
// implemented. to be described
PickerView
// implemented. to be described
RefreshControl
// implemented. to be described
ScrollView
// implemented. to be described more
SegmentedControl
// implemented. to be described more
SliderView
// implemented. to be described
Stepper
// implemented. to be described
TextField
set some font from declared identifiers or with system fonts
set text color
set text alignment
placeholder
secure
remove any text from field easily
set keyboard and content type
listen if user typing or not
set delegate
or get needed events declarative way
Text (aka UILabel)
// to be described more
It either may be initialized withString
or unlimited amount ofAttributedString
sset some font from declared identifiers or with system fonts
set text color
set text alignment
set amount of lines
Declare custom attributed labels like this
and then use them like this
TextView
// implemented. to be described
Toggle
// implemented. to be described
Properties
All the properties are available to be set declaratively and can be binded to
UState
.A lot of layer properties are available directly and have convenient initializers.
Alpha
Background
Borders
To set border on all sides
To set border on specific side
To remove border from specific side
Bounds
Compression Resistance
Corners
To set radius to all corners
To set custom radius for specific corner
To make your view’s corners round automatically by smaller side
Hidden
Hugging Priority
Itself
Layout Margin
Focus to next responder or resign
Opacity
Rasterize
To rasterize layer, e.g. for better shadow performance
Shadow
Shake
You can shake any view just by calling
And you could customize shake effect
or even create an extension
Tag
Tint
User Interaction
Examples
Example app is here
Example 1
Example 2
And you just need a few extensions to make it work