Localizer 1.0.2

Localizer 1.0.2

Maintained by Mihael Isaev.



Localizer 1.0.2

  • By
  • MihaelIsaev

MIT License Swift 4.2 Cocoapod

This project is in active development state till 1.0.0

Build UI in SwiftUI-like way right now in Xcode10+ and for iOS9 and higher! 😺

// NOTE:
// For now it's written for Swift 4.2-5.0
// but closer to 5.1 release it will support 5.1 features like `@State` and `@Binding`
// and became look more like SwiftUI cause `functionBuilder` became available.

Support this lib by giving a ⭐️!

The Main Features

You can build your view anywhere with all the constraints (even to other views), and then once you add it into a superview all the constraints will be activated.

Reusing views is pretty easy. Just declare them in extensions!

Really short intro

// black 100x100 view in center of future superview
lazy var view1 = View().background(.black).size(100).centerInSuperview()
// red 30x20 view in horizontal center of future superview and with vertical spacing to view1
lazy var view2 = View().background(.red)
                       .size(30, 20)
                       .centerXInSuperview()
//                      yes! you can declare constraints before adding to superivew
                       .top(to: .bottom, of: view1, 16)
// view with view1 and view2 as subviews
let awesomeView = View.subviews { [view1, view2] }

func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(awesomeView)
    // and yes! you can reach and change declared constraints easily!
    UIView.animate(duration: 0.5) {
        view2.centerX = 30
        view2.outer[.top, view1] = 16
        awesomeView.layoutIfNeeded()
    }
}

Long intro

import UIKit
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 {
    lazy var backButton = Button.back.tapAction { print("back tapped") }

    lazy var titleLabel = Label.welcome.text("Welcome").centerXInSuperview().topToSuperview(62)

    lazy var contentView = View.subviews { [fieldsView] }
                               .edgesToSuperview(top: 120, leading: 16, trailing: -16, bottom: 0)
                               .background(.white)
                               .corners(20, .topLeft, .topRight)

    lazy var fieldsView = VStackView { [emailField, passwordField, signInButton] }
                              .edgesToSuperview(top: 10, leading: 8, trailing: -8)

    // NOTE: WrapperView needed just to add padding since we're using these views inside VStackView
    lazy var emailField = WrapperView {
        TextField().welcome.placeholder("Email").keyboard(.emailAddress).content(.emailAddress)
    }.padding(x: 10)

    lazy var passwordField = WrapperView {
        TextField.welcome.placeholder("Password").content(.password).secure()
    }.padding(x: 10)

    lazy var signInButton = WrapperView {
        Button.bigBottomGreen.title("Sign In").tapAction(signIn)
    }.padding(top: 10, left: 16, right: 16)

    override func loadView() {
        super.loadView()
        view.backgroundColor = .black
        view.addSubview(backButton, titleLabel, contentView)
    }

    func signIn() {
        guard let email = emailField.innerView.text,
              let password = passwordField.innerView.text else { return }
        // do an API call to your server with CodyFire 😉
    }
}

And that's it! Yeah! You just need 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 Label {
    static var title: Label { return Label().color(.white).font(.sfProMedium, 18) }
}
extension TextField {
    static var welcome: TextField {
        return TextField().height(40)
                          .background(.clear)
                          .color(.black)
                          .tint(.mainGreen)
                          .border(.bottom, 1, .gray)
                          .font(.sfProRegular, 16)
    }
}
extension Button {
    static var back: Button { return Button("backIcon").topToSuperview(64).leadingToSuperview(24) }
    static var bigBottomGreen: Button {
        return Button().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.

Btw, play with it in Playground with our Example project 🎮

// - you need CocoaPods installed on your Mac
// - clone or download this repo
// - go to Example folder in terminal and execute `pod install`
// - go to Example folder in Finder and open Example.xcworkspace
// - Find Example playground at the left top corner in Xcode and start playing

Wondered?❤️ Try it yourself!😃 Now!🚀

Installation

With CocoaPods

Add the following line to your Podfile:

pod 'UIKit-Plus', '~> 0.14.1'

With Swift Package Manager

In Xcode11 go to File -> Swift Packages -> Add Package Dependency and enter there URL of this repo

https://github.com/MihaelIsaev/UIKitPlus

With Carthage

Not supported yet. Feel free to send PR for that.

Usage

import UIKit
import UIKitPlus

Cheatsheet

UIKitPlus UIKit
View UIView
WrapperView UIView
ScrollView UIScrollView
CollectionView UICollectionView
TableView UITableView
Image UIImageView
Button UIButton
Label UILabel
TextField UITextField
SegmentedControl UISegmentedControl
VisualEffectView UIVisualEffectView
StackView UIStackView
HStackView UIStackView
VStackView UIStackView
VerificationCodeView
AttributedString (aka AttrStr) NSAttributedString
ViewController UIViewController
NavigationController UINavigationController
FormController
🛠 DatePicker UIDatePicker
🛠 Stepper UIStepper
🛠 Slider UISlider
🛠 Toggle UISwitch

View

It is just a simple view with ability to customize it declarative way

View().background(.red).shadow().edgesToSuperview()

Also you can initialize it with predefined subviews

View.subviews {
    let avatar = Image("some").size(100)
//                             stick to top, leading and trailing of superview
                              .edgesToSuperview(top: 0, leading: 0, trailing: 0)

//                                 stick top to bottom of avatar view with 16pt
    let name = Label("John Smith").top(to: .bottom, of: avatar, 8)
//                                 stick to leading, trailing and bottom of superview
                                  .edgesToSuperview(leading: 0, trailing: 0, bottom: 0)
    return [avatar, name]
}

WrapperView

It is simple View but with ability to initialize with inner view

WrapperView {
  View().background(.red).shadow()
}.background(.green).shadow()

and you could specify innerView`s padding right here

// to the same padding for all sides
WrapperView {
  View()
}.padding(10)
// or to specific padding for each side
WrapperView {
  View()
}.padding(top: 10, left: 5, right: 10, bottom: 5)
// or even like this
WrapperView {
  View()
}.padding(top: 10, right: 10)

ScrollView

Nothing interesting yet, but you could specify some settings in declarative manner

ScrollView().paging(true).scrolling(false).hideIndicator(.horizontal)
ScrollView().paging(true).scrolling(false).hideAllIndicators()
ScrollView().contentInset(.zero)
ScrollView().contentInset(top: 10, left: 5, right: 5, bottom: 10)
ScrollView().contentInset(top: 10, bottom: 10)
ScrollView().scrollIndicatorInsets(.zero)
ScrollView().scrollIndicatorInsets(top: 10, left: 5, right: 5, bottom: 10)
ScrollView().scrollIndicatorInsets(top: 10, bottom: 10)

CollectionView

Nothing interesting yet

CollectionView().paging(true)

TableView

Nothing interesting yet

Image

Image("someImage").mode(.scaleAspectFill).clipsToBounds(true)

Button

Button()
Button("Tap me")
Button().title("Tap me") // useful if you declared Button from extension like below
Button.mySuperButton.title("Tap me")

background and background for highlighted state

Button("Tap me").background(.white).backgroundHighlighted(.darkGray)

title color for different states

Button("Tap me").color(.black).color(.lightGray, .disabled)

set some font from declared identifiers or with system fonts

Button("Tap me").font(v: .systemFont(ofSize: 15))
Button("Tap me").font(.sfProBold, 15)

add image

Button("Tap me").image(UIImage(named: "cat"))
Button("Tap me").image("cat")

You can handle tap action easily

Button("Tap me").tapAction { print("button tapped") }
Button("Tap me").tapAction { button in
    print("button tapped")
}

or like this

func tapped() { print("button tapped") }
Button("Tap me").tapAction(tapped)

func tapped(_ button: Button) { print("button tapped") }
Button("Tap me").tapAction(tapped)

Label

It either may be initialized with String or unlimited amount of AttributedStrings

Label("hello 👋 ")
Label().text("hello") // useful if declare label in extension like below
Label.mySuperLabel.text("hello")
Label(AttributedString("hello").foreground(.red), AttributedString("world").foreground(.green))

set some font from declared identifiers or with system fonts

Label("hello").font(v: .systemFont(ofSize: 15))
Label("hello").font(.sfProBold, 15)

set text color

Label("hello").color(.red)

set text alignment

Label("hello").alignment(.center)

set amount of lines

Label("hello").lines(1)
Label("hello\nworld").lines(0)
Label("hello\nworld").lines(2)
Label("hello\nworld").multiline()

TextField

TextField()
TextField("some text")
TextField().text("some text")
TextField.mySuperDuperTextField.text("some text")

set some font from declared identifiers or with system fonts

TextField().font(v: .systemFont(ofSize: 15))
TextField().font(.sfProBold, 15)

set text color

TextField().color(.red)

set text alignment

TextField().alignment(.center)

placeholder

TextField().placeholder("email")
// or use AttributedString to make it colored
TextField().placeholder(AttributedString("email").foreground(.green))

secure

TextField().secure()

remove any text from field easily

TextField().cleanup()

set keyboard and content type

TextField().keyboard(.emailAddress).content(.emailAddress)

set delegate

TextField().delegate(self)

or get needed events declarative way

TextField().shouldBeginEditing { tf in return true }
           .didBeginEditing { tf in }
           .shouldEndEditing { tf in return true }
           .didEndEditing { tf in }
           .shouldChangeCharacters { tf, range, replacement in return true }
           .shouldClear { tf in return true }
           .shouldReturn { tf in return true }
           .editingDidBegin { tf in }
           .editingChanged { tf in }
           .editingDidEnd { tf in }

SegmentedControl

SegmentedControl("One", "Two").select(1).changed { print("segment changed to \($0)") }

VisualEffectView

VisualEffectView(.darkBlur)
VisualEffectView(.lightBlur)
VisualEffectView(.extraLightBlur)
// iOS10+
VisualEffectView(.prominent)
VisualEffectView(.regular)

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) }
}

StackView

StackView().axis(.vertical)
           .alignment(.fill)
           .distribution(.fillEqually)
           .spacing(16)

HStackView

The same as StackView but with predefined axis and ability to easily add arranged subviews

HStackView (
  Label("hello world").background(.green),
  Label("hello world").background(.red)
).spacing(10)

VStackView

The same as StackView but with predefined axis and ability to easily add arranged subviews

VStackView (
  Label("hello world").background(.green),
  Label("hello world").background(.red)
).spacing(10)

VerificationCodeView

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! :)

VerificationCodeField().digitWidth(64)
                       .digitsMargin(25)
                       .digitBorder(.bottom, 1, 0xC6CBD3)
                       .digitColor(0x171A1D)
                       .font(.sfProRegular, 32)
                       .entered(verify)

func verify(_ code: String) {
  print("entered code: " + code)
}

Any view

Background

View().background(.red)
View().background(0xff0000)

Tint

View().tint(.red)
View().tint(0xff0000)

Corners

To set radius to all corners

View().corners(10)

To set custom radius for specific corner

View().corners(10, .topLeft, topRight)
View().corners(10, .topLeft, .bottomRight)
View().corners(10, .topLeft, topRight, .bottomLeft, .bottomRight)

To make you view's corners round automatically by smaller side

View().circle()

Borders

To set border on all sides

View().border(1, .black)
View().border(1, 0x000)

To set border on specific side

View().border(.top, 1, .black)
View().border(.left, 1, .black)
View().border(.right, 1, .black)
View().border(.bottom, 1, .black)

To remove border from specific side

.removeBorder(.top)

Alpha

View().alpha(0)

Opacity

View().opacity(0)

Hidden

View().hidden() // true by default
View().hidden(true)
View().hidden(false)

Rasterize

To rasterize layer, e.g. for better shadow performance

View().rasterize() // true by default
View().rasterize(true)
View().rasterize(false)

Shadow

View().shadow() // by default it's black, opacity 1, zero offset, radius 10
View().shadow(.gray, opacity: 0.8, offset: .zero, radius: 5)
View().shadow(0x000000, opacity: 0.8, offset: .zero, radius: 5)

Shake

You can shake any view just by calling

View().shake()

And you could customize shake effect

View().shake(values: [-20, 20, -20, 20, -10, 10, -5, 5, 0],
             duration: 0.6,
             axis: .horizontal,
             timing: .easeInEaseOut)
View().shake(-20, 20, -20, 20, -10, 10, -5, 5, 0,
             duration: 0.6,
             axis: .horizontal,
             timing: .easeInEaseOut)

or even create an extension

import UIKitPlus

extension DeclarativeProtocol {
  func myShake() {
      View().shake(-20, 20, -20, 20, -10, 10, -5, 5, 0,
                   duration: 0.6,
                   axis: .horizontal,
                   timing: .easeInEaseOut)
  }
}

AttributedString

You could create attributed strings in declarative way easily

AttributedString("hello").background(.gray)
                         .foreground(.red)
                         .font(.sfProBold, 15)
                         .paragraphStyle(.default)
                         .ligature(1)
                         .kern(1)
                         .strikethroughStyle(1)
                         .underlineStyle(.patternDash)
                         .strokeColor(.purple)
                         .strokeWidth(1)
                         .shadow()
                   // or .shadow(offset: .zero, blur: 1, color: .lightGray)
                         .textEffect("someEffect")
                         .attachment(someAttachment)
                         .link("http://github.com")
                         .baselineOffset(1)
                         .underlineColor(.cyan)
                         .strikethroughColor(.magenta)
                         .obliqueness(1)
                         .expansion(1)
                         .glyphForm(.horizontal)
                         .writingDirection(.rightToLeft)

Constraints

Size

You can set view size by passing width and height values like this

View().size(100, 200)

or square size by passing single value

View().size(100)

or view's size can be equal to other view size so when you change size of one view other view will change its size as well

let view1 = View().size(100, 200)
let view2 = View().equalSize(to: view1)

Of course you can specify just width or just height or both but by separate methods

View().width(100)
View().height(200)
View().width(100).height(200)
Superview

Your view can stick to its superview by any side or even by all sides

// this way it will stick with 0 constant value
View().edgesToSuperview()
// this way it will stick with 10 constant value for all sides
View().edgesToSuperview(10)
// also you could specify some values manually, but all the rest values are 0 by default
View().edgesToSuperview(top: 16, leading: 16, trailing: -16)
View().edgesToSuperview(trailing: -16, bottom: -16)

you could stick your view to only one side of superview like this

// empty argument means 0 constant value
View()topToSuperview()
View()topToSuperview(16)
View()leadingToSuperview()
View()leadingToSuperview(16)
View()trailingToSuperview()
View()trailingToSuperview(16)
View()bottomToSuperview()
View()bottomToSuperview(16)
View()centerXToSuperview()
View()centerXToSuperview(16)
View()centerYToSuperview()
View()centerYToSuperview(16)
View()widthToSuperview()
View()heightToSuperview()
Relative

Any side of your view could also stick to any side of other view

// Sides to superview
View().top(to: .bottom, of: someView, 16) // stick view's top to someView`s bottom with 16pt (by default 0pt)
View().leading(to: .trailing, of: someView)
View().trailing(to: .leading, of: someView)
View().bottom(to: .top, of: someView)
// Center to superview
View().centerX(to: .centerX, of: someView)
View().centerY(to: .centerY, of: someView)
// Dimension Superview
View().width(to: .width, of: someView)
View().height(to: .height, of: someView)

or this way

// Sides to superview
View().edge(.top, toSuperview: someView, .bottom)
View().edge(.leading, toSuperview: someView, .trailing)
View().edge(.trailing, toSuperview: someView, .leading)
View().edge(.bottom, toSuperview: someView, .top)
// Center to superview
View().edge(.centerX, toSuperview: someView, .centerX)
View().edge(.centerY, toSuperview: someView, .centerY)
// Dimension Superview
View().edge(.width, toSuperview: someView, .width)
View().edge(.height, toSuperview: someView, .height)

To build constraints between two relative views

// Sides to another views
View().spacing(.leading, to: relativeView, toSide: .trailing, 16) // last parameter is optional, 0 by default
View().spacing(.trailing, to: relativeView, toSide: .leading)
View().spacing(.top, to: relativeView, toSide: .bottom)
View().spacing(.bottom, to: relativeView, toSide: .top)
// Center to another relative views
View().center(.x, to: relativeView, toSide: .x)
View().center(.y, to: relativeView, toSide: .y)
// Dimension Relative
View().dimension(.width, to: relativeView, toSide: .width)
View().dimension(.height, to: relativeView, toSide: .height)
Center

Your view could be in center of its superview

View().centerInSuperview() // exact center
View().centerInSuperview(10) // exact center +10 by x-axis, and +10 by y-axis
View().centerInSuperview(x: 5, y: 10) // exact center +5 by x-axis, and +10 by y-axis

also it may be in center of another view

View().center(to: anotherView) // exact center
View().center(to: anotherView, 10) // exact center +10 by x-axis, and +10 by y-axis
View().center(to: anotherView, x: 5, y: 10) // exact center +5 by x-axis, and +10 by y-axis
Constraints direct access

Ok, let's imagine that you have a view which is sticked to its superview

let view = View().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 = View().background(.black).size(100).centerInSuperview()
let secondView = View().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
Layout Margin
// to all sides
View().layoutMargin(10)
// optional sides
View().layoutMargin(top: 10)
View().layoutMargin(left: 10, bottom: 5)
View().layoutMargin(top: 10, right: 5)
// vertical and horizontal
View().layoutMargin(x: 10, y: 5) // top: 5, left: 10, right: 10, bottom: 5
View().layoutMargin(x: 10) // left: 10, right: 10
View().layoutMargin(y: 5) // top: 5, bottom: 5
SafeArea

You could get safeArea safely at any view without #available(iOS 11.0, *) check like this

someView.safeArea.topAnchor
Constraint values

Any constraint value may be set as CGFloat or with Relation and even Multiplier

// just equal to 10
View().leading(to: .trailing, of: anotherView, 10)

// greaterThanOrEqual to 10
View().leading(to: .trailing, of: anotherView, >=10)

// lessThanOrEqual to 10
View().leading(to: .trailing, of: anotherView, <=10)

// equal to 10 with 1.5 multiplier
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5)

// equal to 10 with 1.5 multiplier and 999 priority
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5 ! 999)

// equal to 10 with 1.5 multiplier and `.defaultLow` priority
View().leading(to: .trailing, of: anotherView, 10 ~ 1.5 ! .defaultLow)

// equal to 10 with 999 priority
View().leading(to: .trailing, of: anotherView, 10 ! 999)

Colors

With UIKitPlus you're able to use hex colors just by typing them with 0x prefix like 0x000 for black or 0xfff for white.

Extensions

Fonts

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")
}

and then use them just like

Button().font(.sfProMedium, 15)

Colors

Declare custom colors like this

import UIKit
import UIKitPlus

extension UIColor {
    static var mainBlack: UIColor { return .black  }
    static var otherGreen: UIColor { return 0x3D7227.color  } // 61 114 39
}

and then use them just like

Label("Hello world").color(.otherGreen).background(.mainBlack)

Buttons

Declare custom buttons like this

import UIKitPlus

extension Button {
    static var bigBottomWhite: Button {
        return Button().color(.darkGray).color(.black, .highlighted).font(.sfProMedium, 15).background(.white).backgroundHighlighted(.lightGray).circle()
    }
    static var bigBottomGreen: Button {
        return Button().color(.white).font(.sfProMedium, 15).background(.mainGreen).circle()
    }
}

and then use them like this

Button.bigBottomWhite.size(300, 50).bottomToSuperview(20).centerInSuperview()

Labels

Declare custom attributed labels like this

import UIKitPlus

extension Label {
    static var welcomeLogo: Label {
        return .init(AttributedString("My").foreground(.white).font(.sfProBold, 26),
                     AttributedString("App").font(.sfProBold, 26))
    }
}

and then use them like this

let logo = Label.welcomeLogo.centerInSuperview()

Images

Declare asset images like this

import UIKitPlus

extension Image {
    static var welcomeBackground: Image { return Image("WelcomeBackground") }
}

and then use them like this

let backgroudImage = Image.welcomeBackground.edgesToSuperview()

Subviews

Add subviews in one line

view.addSubview(view1, view2, view3)