Documentation

Show Content

Displaying content in DSKit is easy, all you have to do is just call one function with content you want to display or want to update.


show(content: [YOUR_SECTION])

Content is composed of two data structures DSSection and DSViewModel

DSSection - is used to describe how content will be displayed on the screen, in one word: Layout DSViewModel - is used to describe your content on the screen, labels, texts, images, actions, and so on.

You can display your view models in Lists, Grids, and Galleries.

To easily transform an array of DSViewModel's in a section just call .list() .grid() .gallery() method on your view models array.

Every time your need to update content on the screen, add, delete, change position, just change your content structure and call show(content: [YOUR_SECTION]) DSKit will automatically update the changes, remove non-existent content, or switch position, and it will be animated.

Preview on device


let texts = ["Petrichor","Sumptuous","Angst","Aesthete","Nadir"]

let viewModels = texts.map { (text) -> DSViewModel in
    DSLabelVM(.body, text: text)
}

show(content: viewModels.list())

As for the center content of the screen you can easily show content on the bottom of the screen, all you have to do is just call


showBottom(content: [YOUR_SECTION])

everything related to the show content from the show content section is also valid here

Preview on device


let texts = ["Petrichor","Sumptuous","Angst","Aesthete","Nadir"]

let viewModels = texts.map { (text) -> DSViewModel in
    DSLabelVM(.body, text: text)
}

showBottom(content: viewModels.list())

As for the center content of the screen you can easily show content on the top of the screen, all you have to do is just call


showTop(content: [YOUR_SECTION])

everything related to the show content from the show content section is also valid here

Preview on device


let texts = ["Petrichor","Sumptuous","Angst","Aesthete","Nadir"]

let viewModels = texts.map { (text) -> DSViewModel in
    DSLabelVM(.body, text: text)
}

show(content: viewModels.list())

Here is an example of how to show and update content in all positions of the screen

Preview on device


var showBottom = DSButtonVM(title: "Show Bottom")
showBottom.didTap { [unowned self] (_ :DSButtonVM) in
    self.showBottom()
}

var hideBottom = DSButtonVM(title: "Hide Bottom")
hideBottom.didTap { [unowned self] (_ :DSButtonVM) in
    self.hideBottom()
}

var showTop = DSButtonVM(title: "Show Top")
showTop.didTap { [unowned self] (_ :DSButtonVM) in
    self.showTop()
}

var hideTop = DSButtonVM(title: "Hide Top")
hideTop.didTap { [unowned self] (_ :DSButtonVM) in
    self.hideTop()
}

var showCenter = DSButtonVM(title: "Show Center")
showCenter.didTap { [unowned self] (_ :DSButtonVM) in
    self.showCenter = true
    self.showDemo()
}

var hideCenter = DSButtonVM(title: "Hide Center")
hideCenter.didTap { [unowned self] (_ :DSButtonVM) in
    self.showCenter = false
    self.showDemo()
}

// Texts
let texts = ["Petrichor","Sumptuous","Angst","Aesthete","Nadir","Petrichor"]
let textViewModels = texts.map { (text) -> DSViewModel in
    DSLabelVM(.body, text: text)
}

var sections = [DSSection]()

let buttonsSection = [showBottom,
                      hideBottom,
                      showTop,
                      hideTop,
                      showCenter,
                      hideCenter].grid(identifier: "TopButtons")

sections.append(buttonsSection)

let textsSection = textViewModels.list(grouped: true, identifier: "List")
sections.append(textsSection)

if self.showCenter {
    let imageSection = DSImageVM(named: "barbershop", height: .absolute(300)).list(identifier: "BarbeshopImage")
    sections.append(imageSection)
}

show(content: sections)

Lists

Preview on device


let texts = ["Petrichor","Sumptuous","Angst","Aesthete","Nadir"]

let viewModels = texts.map { (text) -> DSViewModel in
    DSLabelVM(.body, text: text)
}

show(content: viewModels.list())

Preview on device


let texts = ["Miraculous","Lassitude","Gossamer","Bungalow","Scintilla"]

let viewModels: [DSViewModel] = texts.map { (text) -> DSLabelVM in
    DSLabelVM(.body, text: text)
}

show(content: viewModels.list(separator: true))

Preview on device


let texts = ["Aurora","Inure","Mellifluous","Euphoria","Serendipity"]

let viewModels: [DSViewModel] = texts.map { (text) -> DSLabelVM in
    DSLabelVM(.body, text: text)
}

show(content: viewModels.list(grouped: true))

Preview on device


let texts = ["Cherish", "Demure", "Elixir", "Eternity", "Felicity"]

let viewModels: [DSViewModel] = texts.map { (text) -> DSLabelVM in
    DSLabelVM(.body, text: text)
}

show(content: viewModels.list(separator: true, grouped: true))

Preview on device


let texts = ["Languor","Love","Solitude","Epiphany","Quintessential"]

let viewModels: [DSViewModel] = texts.map { (text) -> DSLabelVM in
    DSLabelVM(.body, text: text)
}

let section = viewModels.list(separator: true, grouped: true)
let footerText = "Have you ever met someone who embodies all of the characteristics of the city they're from or the career path they've chosen?"
section.header = DSLabelVM(.headline, text: "Header")
section.footer = DSLabelVM(.caption2, text: footerText)
show(content: section)

Preview on device


// Image view models
var imageViewModels = ["barbershop", "beautysaloon"].map { (name) -> DSViewModel in

    var image = DSImageVM(image: UIImage(named: name), height: .absolute(100))
    let label = DSLabelVM(.subheadline, text: name)
    image.supplementaryItems = [label.asSupplementary(position: .leftTop)]
    return image
}

// Action view models
let actionViewModels = ["flowerstore", "grocerystore"].map { (name) -> DSViewModel in

    // Action
    var action = DSActionVM(title: name)
    action.topImage(image: UIImage(named: name), height: .equalTo(100), contentMode: .scaleAspectFill)

    // Label
    let label = DSLabelVM(.headlineWithSize(12), text: name.capitalized)

    // Button
    var button = DSButtonVM(sfSymbol: "heart.fill",
                            type: .blur(effect: .dark, color: .white))

    button.width  = .absolute(40)
    button.height  = .absolute(40)

    // Set supplementary items
    action.supplementaryItems = [label.asSupplementary(position: .leftTop, background: .lightBlur),
                                 button.asSupplementary(position: .rightTop, background: .clear)]
    return action
}

imageViewModels.append(contentsOf: actionViewModels)
show(content: imageViewModels)

Preview on device


// Image view models
var imageViewModels = ["barbershop", "beautysaloon"].map { (name) -> DSViewModel in

    // Image view
    var image = DSImageVM(image: UIImage(named: name), height: .absolute(50))

    // Picker
    let picker = DSQuantityPickerVM()
    picker.height = .absolute(25)
    picker.width = .absolute(100)

    // Set picker view as right side view
    image.rightSideView = DSSideView(view: picker)

    return image
}

// Action view models
let actionViewModels = ["flowerstore", "grocerystore"].map { (name) -> DSViewModel in

    // Action
    var action = DSActionVM(title: name)
    action.topImage(image: UIImage(named: name), height: .equalTo(100), contentMode: .scaleAspectFill)

    // Picker
    let picker = DSQuantityPickerVM()
    picker.width = .absolute(100)
    picker.height = .absolute(30)
    picker.rightButton(title: "Add",
                       sfSymbolName: "cart.fill.badge.plus",
                       style: .custom(size: 18, weight: .medium)) {

        print("Add to cart")
    }

    // Bottom side view
    action.bottomSideView = DSSideView(view: picker)
    return action
}

imageViewModels.append(contentsOf: actionViewModels)
show(content: imageViewModels)

Grids

Preview on device


let viewModels = [1,2,3,4,5].map { (index) -> DSViewModel in
    var viewModel = DSImageVM(image: UIImage(named: "picture-\(index)"))
    viewModel.height = .absolute(150)
    return viewModel
}

show(content: viewModels.grid())

Preview on device


let viewModels = [1,2,3,4,5].map { (index) -> DSViewModel in
    var viewModel = DSImageVM(image: UIImage(named: "picture-\(index)"))
    viewModel.height = .absolute(150)
    return viewModel
}

let section = viewModels.grid()
section.header = DSLabelVM(.headline, text: "Header")
section.footer = DSLabelVM(.headline, text: "Footer")

show(content: section)

Preview on device


let viewModels = [1,2,3,4,5].map { (index) -> DSViewModel in
    var viewModel = DSImageVM(image: UIImage(named: "picture-\(index)"))
    viewModel.height = .absolute(100)
    return viewModel

}

show(content: viewModels.grid(columns: 3, grouped: true))

Preview on device


// Map array of number in to DSViewModel array
let viewModels = [1,2,3,4,5,6,7,8].map { (index) -> DSViewModel in
    var viewModel = DSImageVM(image: UIImage(named: "picture-\(index)"))
    viewModel.height = .absolute(60)
    return viewModel
}

// Transform viewModels array in to a grid section
let section = viewModels.grid(columns: 5, grouped: true)

// Set header and footer with text view models
section.header = DSLabelVM(.headline, text: "Header")
section.footer = DSLabelVM(.headline, text: "Footer")

// Show content on screen
show(content: section)

Preview on device


let viewModels = ["barbershop", "beautysaloon", "flowerstore", "grocerystore"].map { (name) -> DSViewModel in

    // Image
    var image = DSImageVM(named: name, height: .absolute(200))

    // Label
    let label = DSLabelVM(.subheadline, text: name.capitalized)

    let labelSupplementaryView = DSSupplementaryView(view: label, position: .leftBottom)

    // Button
    var button = DSButtonVM(title: "Like",
                            icon: UIImage(systemName: "heart.fill"),
                            type: .blur(effect: .dark, color: .white))

    button.width = .absolute(80)
    button.height = .absolute(40)

    let buttonSupplementaryView = DSSupplementaryView(view: button,
                                                      position: .rightTop,
                                                      background: .clear)

    image.supplementaryItems = [labelSupplementaryView, buttonSupplementaryView]
    return image
}

show(content: viewModels.grid())

Text

In essence, typography is the art of arranging letters and text in a way that makes the copy legible, clear, and visually appealing to the reader. Typography involves font style, appearance, and structure, which aims to elicit certain emotions and convey specific messages.

Preview on device


let title1 = DSLabelVM(.title1, text: "Title 1")
let title2 = DSLabelVM(.title2, text: "Title 2")
let title3 = DSLabelVM(.title3, text: "Title 3")
let headline = DSLabelVM(.headline, text: "Headline")
let subheadline = DSLabelVM(.subheadline, text: "Subheadline")
let body = DSLabelVM(.body, text: "Body")
let callout = DSLabelVM(.callout, text: "Callout")
let caption1 = DSLabelVM(.caption1, text: "Caption 1")
let caption2 = DSLabelVM(.caption2, text: "Caption 2")
let footnote = DSLabelVM(.footnote, text: "Footnote")
show(content: [title1, title2, title3, headline, subheadline, body, callout, caption1, caption2, footnote])

Preview on device


let text1 = DSLabelVM(.headline, text: "An astronomical term that's been coopted for colloquial usage")
let text2 = DSLabelVM(.subheadline, text: "Suffering from a lack of energy? Describe your tiredness—whether it's in your body, your mind.")
let text3 = DSLabelVM(.caption1, text: "Not to be confused with those furry crepuscular rodents, scintilla means a spark or a trace of something.")
show(content: [text1, text2, text3].list())

Preview on device


let text1 = DSLabelVM(.headline, text: "An astronomical term that's been coopted for colloquial usage")
let text2 = DSLabelVM(.subheadline, text: "Suffering from a lack of energy? Describe your tiredness—whether it's in your body, your mind.")
let text3 = DSLabelVM(.caption1, text: "Not to be confused with those furry crepuscular rodents, scintilla means a spark or a trace of something.")
show(content: [text1, text2, text3].grid())

Preview on device


let text1 = DSLabelVM(.headline, text: "An astronomical term that's been coopted for colloquial usage")
var text2 = DSLabelVM(.subheadline, text: "Suffering from a lack of energy? Describe your tiredness—whether it's in your body, your mind.")
text2.style.displayStyle = .grouped(inSection: false)
let text3 = DSLabelVM(.caption1, text: "Not to be confused with those furry crepuscular rodents, scintilla means a spark or a trace of something.")

show(content: [text1, text2, text3].gallery())

Preview on device


let composer = DSTextComposer()
composer.add(type: .title1, text: "Nadir")
composer.add(type: .subheadline, text: "An astronomical term that's been coopted for colloquial usage, nadir means the lowest point, as in the \"nadir of her popularity.\" Its opposite term, zenith, has a similar appeal.")
composer.add(price: DSPrice(amount: "100.0", currency: "$"))
composer.add(price: DSPrice(amount: "50.0", currency: "$"), size: .large)
composer.add(price: DSPrice(amount: "50.0", currency: "$"), size: .extraLarge)

composer.add(sfSymbol: "staroflife.circle.fill", style: .small, tint: .custom(UIColor.systemYellow))
composer.add(sfSymbol: "staroflife.circle.fill", style: .medium, tint: .custom(UIColor.systemYellow))
composer.add(sfSymbol: "staroflife.circle.fill", style: .large, tint: .custom(UIColor.systemYellow))

composer.add(sfSymbol: "moon.stars.fill", style: .custom(size: 60, weight: .light), tint: .custom(UIColor.systemYellow))

composer.add(sfSymbol: "flame.fill", style: .medium, tint: .custom(UIColor.systemRed))
composer.add(sfSymbol: "star.fill", style: .large)
composer.add(sfSymbols: ["tray.fill", "star.square", "newspaper.fill", "person.crop.circle.fill.badge.minus", "smoke.fill"], style: .large, tint: .custom(UIColor.systemTeal))

composer.add(rating: 3, maximumValue: 5, positiveSymbol: "star.fill", negativeSymbol: "star", tint: .custom(UIColor.systemYellow))

show(content: composer.textViewModel())

Preview on device


let nadir = DSTextComposer()
nadir.add(type: .title1, text: "Nadir")
nadir.add(type: .subheadline, text: "An astronomical term that's been coopted for colloquial usage.")

let lassitude = DSTextComposer()
lassitude.add(type: .headline, text: "Lassitude")
lassitude.add(type: .subheadline, text: "Suffering from a lack of energy? Describe your tiredness—whether it's in your body, your mind.")

var nadirAction = nadir.actionViewModel()
nadirAction.rightArrow()

var lassitudeAction = lassitude.actionViewModel()
lassitudeAction.rightImage(named: "flowerstore")
show(content: nadirAction, lassitudeAction)

Preview on device


var activeText = DSActiveTextVM(.body, text: "An astronomical term that's been coopted for colloquial #usage, nadir means the lowest point, as in the \"nadir of her popularity.\" Its @opposite term, noreply@dskit.com zenith, has a similar appeal.")

activeText.links = ["astronomical": "https://dskit.app"]

activeText.didTapOnHashTag = { tag in
    self.show(message: "Did tap on hashtag: #\(tag)")
}

activeText.didTapOnUrl = { url in
    self.show(message: "Did tap on url:\(url.absoluteString)")
}

activeText.didTapOnEmail = { email in
    self.show(message: "Did tap on email: \(email)")
}

activeText.didTapOnMention = { mention in
    self.show(message: "Did tap on mention: @\(mention)")
}

show(content: activeText)

Buttons

Preview on device


// Default button style
var defaultButton = DSButtonVM(title: "Default")
defaultButton.didTap { [unowned self] (button: DSButtonVM) in
    self.show(message: "Did dap on Default button", timeOut: 1)
}

// Light button style
var lightButton = DSButtonVM(title: "Light", type: .light)
lightButton.didTap { [unowned self] (button: DSButtonVM) in
    self.show(message: "Did dap on light button", timeOut: 1)
}

// Link button style
var linkButton = DSButtonVM(title: "Link", type: .link)
linkButton.didTap { [unowned self] (button: DSButtonVM) in
    self.show(message: "Did dap on link button")
}

// Buttons with icons
let defaultWithIcon = DSButtonVM(title: "Default", icon: UIImage(systemName: "message.fill"))
var lightButtonWithIcon = DSButtonVM(title: "Light", icon: UIImage(systemName: "figure.wave"), type: .light)
lightButtonWithIcon.imagePosition = .right

// Link with icon
let linkButtonWithIcon = DSButtonVM(title: "Link", icon: UIImage(systemName: "flame.fill"), type: .link)

// Default left
let defaultButtonLeftAlignment = DSButtonVM(title: "Default left", textAlignment: .left)

// Light right
let lightButtonRightAlignment = DSButtonVM(title: "Light right", type: .light, textAlignment: .right)

// Link left
let linkButtonLeft = DSButtonVM(title: "Link left", type: .link, textAlignment: .left)

// Link right
let linkButtonRight = DSButtonVM(title: "Link right", type: .link, textAlignment: .right)

// Default button - right image
var defaultButtonLeftWithRightImage = DSButtonVM(title: "Left text right image",
                                                 icon: UIImage(systemName: "message.fill"),
                                                 textAlignment: .center)

defaultButtonLeftWithRightImage.imagePosition = .right

// Default button - left image
var defaultButtonRightWithLeftImage = DSButtonVM(title: "Left image right text",
                                                 icon: UIImage(systemName: "message.fill"),
                                                 textAlignment: .right)

defaultButtonRightWithLeftImage.imagePosition = .left

// Default button - right margin image
var defaultButtonLeftWithRightMarginImage = DSButtonVM(title: "Left text right margin image",
                                                       icon: UIImage(systemName: "message.fill"),
                                                       textAlignment: .left)

defaultButtonLeftWithRightMarginImage.imagePosition = .rightMargin

// Default button - left margin image
var defaultButtonRightWithLeftMarginImage = DSButtonVM(title: "Left image right text",
                                                       icon: UIImage(systemName: "message.fill"),
                                                       textAlignment: .right)

defaultButtonRightWithLeftMarginImage.imagePosition = .leftMargin

show(content: defaultButton,
     lightButton,
     linkButton,
     defaultWithIcon,
     lightButtonWithIcon,
     linkButtonWithIcon,
     defaultButtonLeftAlignment,
     lightButtonRightAlignment,
     linkButtonLeft,
     linkButtonRight,
     defaultButtonLeftWithRightImage,
     defaultButtonRightWithLeftImage,
     defaultButtonLeftWithRightMarginImage,
     defaultButtonRightWithLeftMarginImage)

Preview on device


// Texts
let texts = ["Petrichor", "Sumptuous", "Angst", "Aesthete", "Nadir"]

// Map texts into an array of view models
let viewModels = texts.map { (text) -> DSViewModel in

    var button = DSButtonVM(title: text)

    // Handle did tap on button
    button.didTap { [unowned self] (button: DSButtonVM) in
        self.show(message: text)
    }
    return button
}

// Show
show(content: viewModels.grid())

Preview on device


// Texts
let texts = ["Petrichor", "Sumptuous", "Angst", "Aesthete", "Nadir"]

// Map texts into an array of view models
let viewModels = texts.map { (text) -> DSViewModel in

    var button = DSButtonVM(title: text)

    // Handle button tap on button
    button.didTap { [unowned self] (button: DSButtonVM) in
        self.show(message: text)
    }

    return button
}

// Show
show(content: viewModels.list())

Preview on device


// Texts
let texts = ["Petrichor", "Sumptuous", "Angst", "Aesthete", "Nadir"]

// Map texts into an array of view models
let viewModels = texts.map { (text) -> DSViewModel in

    var button = DSButtonVM(title: text)

    // Handle button tap on button
    button.didTap { [unowned self] (button: DSButtonVM) in
        self.show(message: text)
    }
    return button
}

// Show
show(content: viewModels.gallery())

Images

Preview on device


// Barber Shop
let barberShop = DSImageVM(named: "barbershop",
                           height: .absolute(100),
                           displayStyle: .circle)

// Beauty Saloon
let beautySaloon = DSImageVM(named: "beautysaloon",
                             height: .absolute(100),
                             displayStyle: .themeCornerRadius)

// Flower Saloon
let flowerStore = DSImageVM(named: "flowerstore",
                            height: .absolute(100),
                            displayStyle: .default,
                            contentMode: .scaleAspectFit)

// Show
show(content: barberShop, beautySaloon, flowerStore)

Preview on device


// Images
let imageNames = ["barbershop", "beautysaloon", "flowerstore", "grocerystore"]

// Map texts into an array  of view models
let viewModels = imageNames.map { (name) -> DSViewModel in
    DSImageVM(image: UIImage(named: name), height: .absolute(100))
}

// Show
show(content: viewModels.list())

Preview on device


// Images
let imageNames = ["barbershop", "beautysaloon", "flowerstore", "grocerystore"]

// Map texts into an array  of view models
let viewModels = imageNames.map { (name) -> DSViewModel in
    DSImageVM(image: UIImage(named: name), height: .absolute(100))
}

// Show
show(content: viewModels.grid())

Preview on device


// Images
let imageNames = ["barbershop", "beautysaloon", "flowerstore", "grocerystore"]

// Map texts into an array  of view models
let viewModels = imageNames.map { (name) -> DSViewModel in
    DSImageVM(image: UIImage(named: name), height: .absolute(250))
}

// Show
show(content: viewModels.gallery())

Preview on device


// Images
let imageNames = ["barbershop", "beautysaloon", "flowerstore", "grocerystore"]

// MARK: - Handle tap on image

// Map texts into an array  of view models
let viewModels = imageNames.map { (name) -> DSViewModel in

    var image = DSImageVM(image: UIImage(named: name), height: .absolute(250))

    image.didTap = { (_: DSViewModel) in
        self.show(message: "Did tap on image \(name)")
    }

    return image
}

// MARK: - Handle tap on array of images

// Map texts into an array  of view models
var viewModels2 = imageNames.map { (name) -> DSViewModel in
    DSImageVM(image: UIImage(named: name), height: .absolute(100))
}

viewModels2 = viewModels2.didTap { (imageViewModel: DSImageVM) in
    self.show(message: "Did tap on image")
}

// Show
show(content: viewModels.gallery(), viewModels2.list())

Text Fields

Display a list of text fields

Preview on device


// Text fields
let fullName = DSTextFieldVM(placeholder: "Full Name")
let email = DSTextFieldVM(placeholder: "Email")
let password = DSTextFieldVM(placeholder: "Password")

// Show
show(content: fullName, email, password)

Display a list of text fields with initial valid values

Preview on device


// Full name
let fullName = DSTextFieldVM.name(text: "Borinschi Ivan",placeholder: "Full Name")
fullName.leftSFSymbolName = "person.crop.circle.fill"

// Email
let email = DSTextFieldVM.email(text: "imodeveloperlab@gmail.com", placeholder: "Email")
email.leftSFSymbolName = "envelope.fill"

// Password
let password = DSTextFieldVM.password(text: "qqqqqqqq",placeholder: "Password")
password.leftSFSymbolName = "lock.shield.fill"

// Section
let section = [fullName, email, password].list()

// Show
show(content: section)

Display a list of text fields with initial invalid values

Preview on device


// Full name
let fullName = DSTextFieldVM.name(text: "12345",placeholder: "Full Name")
fullName.leftSFSymbolName = "person.crop.circle.fill"

// Email
let email = DSTextFieldVM.email(text: "Ivan", placeholder: "Email")
email.leftSFSymbolName = "envelope.fill"

// Password
let password = DSTextFieldVM.password(text: "qqq",placeholder: "Password")
password.leftSFSymbolName = "lock.shield.fill"

// Section
let section = [fullName, email, password].list()

// Header
section.headlineHeader("User form")

// Footer
section.subheadlineFooter("Form description")

// Show
show(content: section)

If built-in text field validation is not enough for your project requirements you can easily add custom validation to any text field in your form. Using handleValidation closure you are in control, if you implement handleValidation closure, all built-in validations will not affect the validation, you are in control for minimum or the maximum number of characters and other validation patterns.

Preview on device


// Custom text field for full name
let fullName = DSTextFieldVM(text: "Borinschi Ivan", placeholder: "Full Name")

// Handle text field validation
fullName.handleValidation = { textFieldText in

    // Check if text is not nil
    guard let text = textFieldText else {
        return false
    }

    if text.contains("123") {

        // Set textfield as invalid state
        return false

    } else {

        // Set textfield as valid state
        return false
    }
}

// Handle textfield update, this closure is called every time
// user type a character in textfield
fullName.didUpdate = { textField in
    print(textField.text ?? "No value")
}

// Show
show(content: fullName)

Customize your error message displayed directly in the text field if the user types an invalid value, you can add a message or an example of what kind of value he should type.

Preview on device


// Full name
let fullName = DSTextFieldVM.name(text: "12345",placeholder: "Full Name")
fullName.errorPlaceHolderText = "Full name should not contain numbers"

// Email
let email = DSTextFieldVM.email(text: "Ivan", placeholder: "Email")
email.errorPlaceHolderText = "Email example@example.com"

// Password
let password = DSTextFieldVM.password(text: "qqq",placeholder: "Password")
email.errorPlaceHolderText = "Minimum length should be 8"

// Section
let section = [fullName, email, password].list()

// Show
show(content: section)

DSKit contains built-in validation, you can easily add any validation you need for your form using these three properties:

  1. validateMinimumLength
  2. validateMaximumLength
  3. validationPattern

Regular expressions snippet:


let patternNumbers = "^[0-9]*$"
let patternLetters = "^[a-zA-Z]*$"
let patternNameUmlaute = "^[\\ü\\ö\\ä\\Ä\\Ü\\Ö\\ß\\u0600-\\u06FFa-zA-Z\\s\\'\\-]*$"
let patternName = "^[\\u0600-\\u06FFa-zA-Z\\s\\'\\-]*$"
let patternLettersAndSpaces = "^[\\u0600-\\u06FFa-zA-Z\\s]*$"
let patternLettersAndNumbers = "^[a-zA-Z0-9]*$"
let patternLettersNumbersAndSpaces = "^[\\u0600-\\u06FFa-zA-Z0-9\\s]*$"
let patternAddressUmlaute = "^[\\ü\\ö\\ä\\Ä\\Ü\\Ö\\ß\\u0600-\\u06FFa-zA-Z0-9\\s\\'\\-]*$"
let patternAddress = "^[\\u0600-\\u06FFa-zA-Z0-9\\s\\'\\-]*$"
let patternEmail = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let patternPhoneNumber = "^\\s*(?:\\+?(\\d{1,3}))?([-. (]*(\\d{3})[-. )]*)?((\\d{3})[-. ]*(\\d{2,4})(?:[-.x ]*(\\d+))?)\\s*$"
let patternPhoneNumber2 = "(\\+?( |-|\\.)?\\d{1,3}( |-|\\.)?)?(\\(?\\d{1,5}\\)?|\\d{1,5})( |-|\\.)?(\\d{1,4}( |-|\\.)?\\d{3,4})"
let patternMin8Max20CharsAtLeastOneDigitAtLeastOneLetter = "^(?=.*\\d)((?=.*[a-z])|(?=.*[A-Z])).*$"
let patternCityNameUmlaute = "^[\\ü\\ö\\ä\\Ä\\Ü\\Ö\\ß\\u0600-\\u06FFa-zA-Z\\s\\'\\-]*$"
let patternCityName = "^[\\u0600-\\u06FFa-zA-Z\\s\\'\\-]*$"

Preview on device


// Custom text field for full name
let fullName = DSTextFieldVM(text: "Borinschi Ivan", placeholder: "Full Name")

// Minimum 4 characters for textfield to be valid
fullName.validateMinimumLength = 4

// Maximum 50 characters for textfield to be valid
fullName.validateMaximumLength = 50

// Validation regular expression
fullName.validationPattern = "^[a-zA-Z]*$"

// Handle textfield update, this closure is called every time user type a character in textfield
fullName.didUpdate = { textField in
    print(textField.text)
}

// Show
show(content: fullName)

DSKit contains built-in text field configurations, just pick the one you need and add it to your form, each configuration comes with a specific regular expression validation, icon, minimum and maximum number of required characters for validation.

We can use a shortcut text field and add some custom elements after initialization, for example changing the minimum of characters for a text-filed to be valid.

Preview on device


// Email
let email = DSTextFieldVM.email(placeholder: "Email")
email.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Phone
let phone = DSTextFieldVM.phone(placeholder: "Phone")
phone.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Secure Code
let secureCode = DSTextFieldVM.secureCode(placeholder: "Secure Code")
secureCode.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Password
let password = DSTextFieldVM.password(placeholder: "Password")
password.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// New Password
let newPassword = DSTextFieldVM.newPassword(placeholder: "New Password")
newPassword.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Name
let name = DSTextFieldVM.name(placeholder: "Name")
name.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Given Name
let givenName = DSTextFieldVM.givenName(placeholder: "Given Name")
givenName.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Family Name
let familyName = DSTextFieldVM.familyName(placeholder: "Family Name")
familyName.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Number
let number = DSTextFieldVM.number(placeholder: "Number")
number.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Address
let address = DSTextFieldVM.address(placeholder: "Address")
number.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Street Address Line 1
let streetAddressLine1 = DSTextFieldVM.streetAddressLine1(placeholder: "Street Address Line 1")
streetAddressLine1.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Street Address Line 2
let streetAddressLine2 = DSTextFieldVM.streetAddressLine2(placeholder: "Street Address Line 2")
streetAddressLine2.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Address State
let addressState = DSTextFieldVM.addressState(placeholder: "Address State")
addressState.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Address City
let addressCity = DSTextFieldVM.addressCity(placeholder: "Address City")
addressCity.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Address City And State
let addressCityAndState = DSTextFieldVM.addressCityAndState(placeholder: "Address City And State")
addressCityAndState.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Search
let search = DSTextFieldVM.search(placeholder: "Search")
search.didUpdate = { tf in
    print(tf.text ?? "No value")
}

// Show
show(content: [email,
               phone,
               secureCode,
               password,
               newPassword,
               name,
               givenName,
               familyName,
               number,
               address,
               streetAddressLine1,
               streetAddressLine2,
               addressState,
               addressCity,
               addressCityAndState,
               search])

Display a list of text fields grouped in section

Preview on device


// Text fields
let fullName = DSTextFieldVM(placeholder: "Full Name")
let email = DSTextFieldVM(placeholder: "Email")
let password = DSTextFieldVM(placeholder: "Password")

// Text fields section
let section = [fullName, email, password].list(grouped: true)

// Header
section.headlineHeader("User form")

// Footer
section.subheadlineFooter("Form description")

// Show
show(content: section)

Customize your text fields with specific icons using apple SF Symbols library https://developer.apple.com/sf-symbols/

Preview on device


// Full name
let fullName = DSTextFieldVM.name(placeholder: "Full Name")

// Set left sf symbol https://developer.apple.com/sf-symbols/
fullName.leftSFSymbolName = "person.crop.circle.fill"

// Email
let email = DSTextFieldVM.email(placeholder: "Email")

// Set left sf symbol https://developer.apple.com/sf-symbols/
email.leftSFSymbolName = "envelope.fill"

// Password
let password = DSTextFieldVM.password(placeholder: "Password")

// Set left sf symbol https://developer.apple.com/sf-symbols/
password.leftSFSymbolName = "lock.shield.fill"

// Section
let section = [fullName, email, password].list(grouped: true)

// Header
section.headlineHeader("User form")

// Footer
section.subheadlineFooter("Form description")

// Show
show(content: section)

Display a list of text fields in a grid layout

Preview on device


// Text fields
let fullName = DSTextFieldVM(placeholder: "Full Name")
let email = DSTextFieldVM(placeholder: "Email")
let password = DSTextFieldVM(placeholder: "Password")

// Show
show(content: [fullName, email, password].grid())

Display a list of text fields in a grouped grid layout

Preview on device


// Text fields
let fullName = DSTextFieldVM(placeholder: "Full Name")
let email = DSTextFieldVM(placeholder: "Email")
let password = DSTextFieldVM(placeholder: "Password")

// Show
show(content: [fullName, email, password].grid(grouped: true))

Actions

Preview on device


// Data
let data = ["Petrichor": "leaf.fill", "Sumptuous": "safari.fill", "Angst": "paintbrush.pointed.fill"]
let sortedData = data.sorted(by: { $0 < $1 })

// Map data into an array  of actions
let actions = sortedData.map { text -> DSViewModel in
    var action = DSActionVM(title: text.key)
    action.leftIcon(sfSymbolName: text.value)
    action.height = .absolute(30)
    return action
}

// Show content
show(content: actions.list(grouped: true), actions.list(separator: true, grouped: true))

Preview on device


// Texts
let texts = ["Petrichor", "Sumptuous", "Angst", "Aesthete", "Nadir"]

// Map texts into an array of view models
let viewModels = texts.map { (text) -> DSViewModel in
    DSActionVM(title: text)
}

// Show
show(content: viewModels.list())

Preview on device


// Texts
let texts = ["Petrichor", "Sumptuous", "Angst", "Aesthete", "Nadir"]

// Map texts into an array of view models
let viewModels = texts.map { (text) -> DSViewModel in
    DSActionVM(title: text)
}

// Show
show(content: viewModels.grid())

Preview on device


// Texts
let texts = ["Petrichor", "Sumptuous", "Angst", "Aesthete", "Nadir"]

// Map texts into an array of view models
let viewModels = texts.map { (text) -> DSViewModel in
    DSActionVM(title: text)
}

// Show
show(content: viewModels.gallery())

Preview on device


// Data
let data = ["Petrichor": "leaf.fill", "Sumptuous": "safari.fill", "Angst": "paintbrush.pointed.fill"]
let sortedData = data.sorted(by: { $0 < $1 })

// Map data into an array of view models
let actions = sortedData.map { text -> DSViewModel in
    var action = DSActionVM(title: text.key)
    action.leftIcon(sfSymbolName: text.value)
    return action
}

// Show
show(content: actions)

Preview on device


// Barber shop
let barbershop = DSTextComposer()
barbershop.add(type: .headline, text: "Barber Shop")
barbershop.add(type: .subheadline, text: "Subheadline text")
var barbershopAction = DSActionVM(composer: barbershop)

// Set top image
barbershopAction.topImage(image: UIImage(named: "barbershop"), height: .equalTo(50))

// Beauty saloon
var beautySaloonAction = DSActionVM(title: "Beauty Saloon")

// Set top image
beautySaloonAction.topImage(image: UIImage(named: "beautysaloon"), height: .unknown)
beautySaloonAction.height = .absolute(150)

// Beauty saloon
var flowerStoreAction = DSActionVM(title: "Flower Store", subtitle: "Best flower store in town")

// Set top image
flowerStoreAction.topImage(image: UIImage(named: "flowerstore"), height: .equalTo(200))

// Right button
flowerStoreAction.rightButton(title: "More details", sfSymbolName: "leaf.fill", style: .small) { [unowned self] in
    self.show(message: "Did tap on more details")
}

// Show
show(content: barbershopAction, beautySaloonAction, flowerStoreAction)

Preview on device


// Data
let data = ["Barbershop": "barbershop", "Beauty Saloon": "beautysaloon", "Flower Store": "flowerstore"]
let sortedData = data.sorted(by: { $0 < $1 })

// Map data into an array of view models
let actions = sortedData.map { text -> DSViewModel in

    // Action
    var action = DSActionVM(title: text.key)
    let image = UIImage(named: text.value)

    // Set left round image
    action.leftRoundImage(image: image)
    return action
}

// Show
show(content: actions)

Preview on device


// Data
let data = ["Barbershop": "barbershop", "Beauty Saloon": "beautysaloon", "Flower Store": "flowerstore"]
let sortedData = data.sorted(by: { $0 < $1 })

// Default image size
var widthHeight = 40

// Map data into an array of view models
let actions = sortedData.map { text -> DSViewModel in

    // Action with title and subtitle
    var action = DSActionVM(title: text.key, subtitle: "Image name: \(text.value)")

    // Image
    let image = UIImage(named: text.value)

    // Set left round image
    action.leftRoundImage(image: image, size: CGSize(width: widthHeight, height: widthHeight))

    // Increase image size for each view model
    widthHeight += 15

    return action
}

// Show
show(content: actions)

Preview on device


// Barbershop
var barberShop = DSActionVM(title: "Barbershop", subtitle: "themeCornerRadius, scaleAspectFill")

// Left image
barberShop.leftImage(image: UIImage(named: "barbershop"),
                     style: .themeCornerRadius,
                     size: .size(CGSize(width: 50, height: 50)),
                     contentMode: .scaleAspectFill)

// Beauty Saloon
var beautysaloon = DSActionVM(title: "Beauty Saloon", subtitle: "circle, scaleAspectFill")

// Left image
beautysaloon.leftImage(image: UIImage(named: "beautysaloon"),
                       style: .circle,
                       size: .size(CGSize(width: 30, height: 30)),
                       contentMode: .scaleAspectFill)

// Flowers Store
var flowerstore = DSActionVM(title: "Flower Store", subtitle: "default, scaleAspectFit")

// Left image
flowerstore.leftImage(image: UIImage(named: "flowerstore"),
                      style: .default,
                      size: .size(CGSize(width: 70, height: 70)),
                      contentMode: .scaleAspectFit)

// Show
show(content: barberShop, beautysaloon, flowerstore)

Preview on device


// Data
let data = ["Barbershop": "barbershop", "Beauty Saloon": "beautysaloon", "Flower Store": "flowerstore"]
let sortedData = data.sorted(by: { $0 < $1 })

// Map data into an array of view models
let actions = sortedData.map { text -> DSViewModel in

    // Action
    var action = DSActionVM(title: text.key)
    let image = UIImage(named: text.value)

    // Set right round image
    action.rightRoundImage(image: image)
    return action
}

// Show
show(content: actions)

Preview on device


// Data
let data = ["Petrichor": "leaf.fill", "Sumptuous": "safari.fill", "Angst": "paintbrush.pointed.fill"]
let sortedData = data.sorted(by: { $0 < $1 })

// Map data into an array of view models
let actions = sortedData.map { text -> DSViewModel in

    // Action
    var action = DSActionVM(title: text.key)

    // Right icon
    action.rightIcon(sfSymbolName: text.value)
    return action
}

// Show
show(content: actions)

Preview on device


// Data
let data = ["Barbershop": "barbershop", "Beauty Saloon": "beautysaloon", "Flower Store": "flowerstore"]
let sortedData = data.sorted(by: { $0 < $1 })

// Default image size
var widthHeight = 40

// Map data into an array of view models
let actions = sortedData.map { text -> DSViewModel in

    // Action with title and subtitle
    var action = DSActionVM(title: text.key, subtitle: "Image name: \(text.value)")

    // Image
    let image = UIImage(named: text.value)

    // Set right round image
    action.rightRoundImage(image: image, size: CGSize(width: widthHeight, height: widthHeight))

    // Increase image size for each view model
    widthHeight += 15

    return action
}

// Show
show(content: actions)

Preview on device


// Barber Shop
var barberShop = DSActionVM(title: "Barbershop", subtitle: "themeCornerRadius, scaleAspectFill")

// Right image
barberShop.rightImage(image: UIImage(named: "barbershop"),
                      style: .themeCornerRadius,
                      size: .size(CGSize(width: 50, height: 50)),
                      contentMode: .scaleAspectFill)

// Barber Saloon
var beautySaloon = DSActionVM(title: "Beauty Saloon", subtitle: "circle, scaleAspectFill")

// Right image
beautySaloon.rightImage(image: UIImage(named: "beautysaloon"),
                        style: .circle,
                        size: .size(CGSize(width: 30, height: 30)),
                        contentMode: .scaleAspectFill)

// Flower Store
var flowerStore = DSActionVM(title: "Flower Store", subtitle: "default, scaleAspectFit")

// Right image
flowerStore.rightImage(image: UIImage(named: "flowerstore"),
                       style: .default,
                       size: .size(CGSize(width: 70, height: 70)),
                       contentMode: .scaleAspectFit)

// Show
show(content: barberShop, beautySaloon, flowerStore)

Preview on device


// Action 1
var action = DSActionVM(title: "Action", subtitle: "Button")
action.rightButton(sfSymbolName: "cart.fill.badge.plus") { [unowned self] in
    self.show(message: "Did tap on button")
}

// Action 2
var action2 = DSActionVM(title: "Action", subtitle: "Small button")
action2.rightButton(title: "Small", sfSymbolName: "trash.fill", style: .small) { [unowned self] in
    self.show(message: "Did tap on small button")
}

// Action 3
var action3 = DSActionVM(title: "Action", subtitle: "Medium button")
action3.rightButton(title: "Medium", sfSymbolName: "trash.fill", style: .medium) { [unowned self] in
    self.show(message: "Did tap on medium button")
}

// Action 4
var action4 = DSActionVM(title: "Action", subtitle: "Large button")
action4.rightButton(title: "Large", sfSymbolName: "trash.fill", style: .large) { [unowned self] in
    self.show(message: "Did tap on large button")
}

// Show
show(content: action, action2, action3, action4)

Pages

Preview on device


// Image
let image = DSImageVM(named: "glasses", height: .absolute(250))

// Compose text
let composer = DSTextComposer()
let spacing = appearance.interItemSpacing
composer.add(type: .headlineWithSize(40), text: "Discover your best fashion glasses", spacing: spacing, maximumLineHeight: 38)
composer.add(type: .subheadline, text: "Get all brands at one place. Fill the bag\nwith full joy", spacing: spacing)
composer.add(type: .headline, text: "Only ")
composer.add(price: DSPrice(amount: "99.0", regularAmount: "129.00", currency: "$"), size: .extraLarge, newLine: false)

// Space
let space = DSSpaceVM()

// Page with view models
let page = DSPageVM(viewModels: [image, space, composer.textViewModel()])
var pageControl = DSPageControlVM(type: .pages([page]))
pageControl.height = .absolute(UIDevice.current.contentAreaHeigh - 80)

// Show page
self.show(content: pageControl)

Other

Preview on device


// Text composer
let text = DSTextComposer()
text.add(type: .headline, text: "325 Broadway, Bayonne, NJ 07002")
text.add(type: .subheadline, text: "15 minutes to drive")

// Location
let location = CLLocationCoordinate2D(latitude: 40.764382, longitude: -73.973045)

// Map
let map = DSMapVM(text: text, coordinate: location)

// Show
show(content: map)

Preview on device


// Text composer
let composer = DSTextComposer(alignment: .left)
composer.add(type: .text(font: .headlineWithSize(18), color: .white), text: "John Doe")
composer.add(type: .text(font: .headlineWithSize(24), color: .white), text: "0006 5236 1689 9521")
composer.add(type: .text(font: .subheadline, color: .white), text: "Exp: 24/23")

// Credit Card
let creditCard = DSCardVM(composer: composer,
                          textPosition: .bottom,
                          backgroundImage: .image(image: UIImage(named: "visa-card-bg")))

// Text composer 2
let composer2 = DSTextComposer(alignment: .right)
composer2.add(type: .text(font: .headlineWithSize(18), color: .white), text: "Bonus Card")
composer2.add(type: .text(font: .headlineWithSize(24), color: .white), text: "0006 5236 1689 9521")
composer2.add(type: .text(font: .subheadline, color: .white), text: "Discount: 10%, Points 0, Cash Back 5%")
composer2.add(price: DSPrice(amount: "50", currency: "$", discountBadge: "BONUS"), size: .large)

// Bonus Card
var bonusCard = DSCardVM(composer: composer2, textPosition: .bottom,
                         backgroundImage: .imageURL(url: URL(string: "https://images.pexels.com/photos/291762/pexels-photo-291762.jpeg?cs=srgb&dl=pexels-freestocksorg-291762.jpg&fm=jpg")))

// Bonus Card gradient
bonusCard.gradientTopColor = UIColor.black.withAlphaComponent(0.1)
bonusCard.gradientBottomColor = DSColor.color(light: UIColor.black.withAlphaComponent(1.0), dark: UIColor.black.withAlphaComponent(0.7))

// Show
show(content: creditCard, bonusCard)

Preview on device


// Segments
let segments = ["Petrichor", "Sumptuous", "Angst"]

// Segment controll
let segmentControl = DSSegmentVM(segments: ["Petrichor","Sumptuous","Angst"])

// Handle tap on segment
segmentControl.didTapOnSegment = { segment in

    let text = DSLabelVM(.body, text: segment.title)

    // Update
    self.show(content: segmentControl, text)
}

// Text
let text = DSLabelVM(.body, text: segments.first ?? "No value")

// Show
show(content: segmentControl, text)

Preview on device


// Numbers
let numbers = [1, 2, 3, 4]

// Map numbers into an array of view models
let viewModels = numbers.map { (index) -> DSViewModel in

    var viewModel = DSImageVM(image: UIImage(named: "picture-\(index)"))
    viewModel.height = .absolute(200)
    return viewModel
}

// Page controll
let pageControl = DSPageControlVM(type: .viewModels(viewModels))

// Sections
let section = pageControl.list().zeroLeftRightInset()
let section2 = pageControl.list().zeroLeftRightInset()

// Show
show(content: section, section2)

Style

Main DSKit idea is to configure the app appearance once and to not care anymore about spaces colors and typography, if you need just that please read the article about appearance in DSKit

But DSKit offers more than that, you can add small adjustments to specific view models in your UI if you need that.

Using style property on your view model you can adjust borderStyle , colorStyle, cornerStyle,shadowStyle

Explore the example bellow:

Preview on device


// Button view model with custom colors
var button = DSButtonVM(title: "Button")

// Get a copy current appearance colors from secondaryView colors
var colors = DSAppearance.shared.main.secondaryView

// Change colors
colors.button.background = UIColor.green
colors.button.title = UIColor.blue

// Set color style to the button
button.style.colorStyle = .custom(colors)

// Image View Model with custom border
var imageViewModel = DSImageVM(named: "barbershop", height: .absolute(100), displayStyle: .themeCornerRadius)

imageViewModel.style.borderStyle = .custom(width: 5, color: .blue)

// Action with custom corner radius
var action = DSActionVM(title: "Action")
action.style.cornerStyle = .custom(3)

// Action with default display style
var action2 = DSActionVM(title: "Action")
action2.style.displayStyle = .default

// Action with custom shadow
var action3 = DSActionVM(title: "Action")
action3.style.shadowStyle = .small

show(content: [button, imageViewModel, action, action2, action3].list())