Skip to content

Package of useful controls: pickers, presenting queue, textfield thats supports validation, showing the errors etc.

License

Notifications You must be signed in to change notification settings

Flawion/KOControls

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KOControls

KOControls is a package of useful controls. It helps you to create a better user experience without a lot of effort.

Right now it contains only the few features but it will be getting the new stuff, depending on the users needs.

Features

Requirements

  • iOS 10+
  • Xcode 10.0+
  • Swift 4.2+

Versions

  • Swift 4.2: from 1.0 to 1.0.3
  • Swift 5.0: from 1.1 to newest

Installation

KOControls doesn't contains any external dependencies. If you want to stay updated install KOControls by Cocoapods.

CocoaPods

Add below entry to the target in Podfile

pod 'KOControls', '~> 1.2.4'

For example

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target 'Target Name' do
pod 'KOControls', '~> 1.2.4'
end

Install the pods by running

pod install

Manually

You can use KOControls manually and customize how you like. One of the simplest way to do that.

  1. Download repository.
  2. Copy the KOControls.xcodeproj and folder Sources to the your project directory.
  3. In project explorer click "Add Files to 'Your project'" -> choose KOControls.xcodeproj. Xcode will add automatically KOControls as a sub-project.
  4. In project settings -> Target -> Add embeded library -> choose 'Your project' -> KOControls.xcodeproj -> Products -> KOControls.framework.
  5. And thats all! If you don't want to build KOControl manually every time when you change something. Go to the scheme settings of your target to the build section and add KOControls build target.

Usage

You need to add following import to the top of the file.

import KOControls

KOPresentationQueuesService

You can add viewController to queue of presenting, to avoid a situation when there can be multiple viewControllers to present at the same time, and only one will be presented. The simplest way to add the viewController to queue of presenting is to use the overridden function present for the presenting viewController.

let itemIdInQueue = present(viewControllerToPresent, inQueueWithIndex: messageQueueIndex)

The most detail one, lets you to set presenting viewController. But be careful because modalPresentationStyles that aren't presenting on current context (like custom or fullscreen) will be presented at fullscreen context outside the queue.

let itemIdInQueue = KOPresentationQueuesService.shared.presentInQueue(customDialog, onViewController: presentingContainerViewController, queueIndex: messageQueueIndex, animated: true, animationCompletion: nil)

To remove item from the queue, you need id of item in queue.

KOPresentationQueuesService.shared.removeFromQueue(withIndex: messageQueueIndex, itemWithId: itemIdInQueue)

Or index of item in queue.

KOPresentationQueuesService.shared.removeFromQueue(withIndex: messageQueueIndex, itemWithIndex: indexOfItemInQueue)

Another functions let you to remove multiple items from the queue.

//delete all items from queue that would be presented at 'presentingViewController'
KOPresentationQueuesService.shared.removeAllItemsFromQueue(withIndex: messageQueueIndex, forPresentingViewController: presentingViewController)

//just deletes queue with all of items
KOPresentationQueuesService.shared.deleteQueue(withIndex: messageQueueIndex)

The current presented item isn't in the queue, but you can check is something is presented for the queue or delete presented item if you want.

if KOPresentationQueuesService.shared.itemPresentedForQueue(withIndex: messageQueueIndex) != nil{
    //do something...
}

//dismisses current presented viewController if is
KOPresentationQueuesService.shared.removeCurrentVisibleItemForQueue(withIndex: messageQueueIndex, animated: true, animationCompletion: nil)

If you want to do something when queue was change (new items were add / remove, queue created / deleted), you can use queueChangedEvent.

KOPresentationQueuesService.shared.queueChangedEvent = {
    queueIndex in
    //it will print count of queue items
    print(KOPresentationQueuesService.shared.itemsCountForQueue(withIndex: queueIndex) ?? 0)
}

KOTextField

Text field supports showing and validating an error.

You always have to set the error description before show it. To show an error manually you need to change the default validation.mode to manual and just change the flag error.isShowing to true.

errorField.errorInfo.description = "Error description text"
errorField.validation.mode = .manual
errorField.error.isShowing = true

To don't worry about setting the flag manually, you can use auto validation feature. The default validation mode is validateOnLostFocus. So in example if you want to show error when the email isn't correct you need to only add the predefinied validator.

emailField.validation.add(validator: KORegexTextValidator.mailValidator(failureText: "Email is incorrect"))

You can always adjust the border of the field to the state of: normal, error, focus; by setting border.settings.

emailField.border.settings = KOControlBorderSettings(color: UIColor.lightGray.cgColor, errorColor: UIColor.red.cgColor, focusedColor: UIColor.blue.cgColor, errorFocusedColor : UIColor.red.cgColor,  width: 1, focusedWidth: 2)

Field can be validated with the multiple validators based on function or regex.

passwordField.border.settings = KOControlBorderSettings(color: UIColor.lightGray.cgColor, errorColor: UIColor.red.cgColor, focusedColor: UIColor.blue.cgColor, errorFocusedColor : UIColor.red.cgColor,  width: 1, focusedWidth: 2)
passwordField.validation.validateMode = .validateOnTextChanged

//simple function based validator
passwordField.validation.failureTextPrefix = "Password should contain:\n"
passwordField.validation.add(validator: KOFunctionTextValidator(function: { password -> Bool in
    return password.count >= 8 && password.count <= 20
}, failureText: "from 8 to 20 chars"))
passwordField.validation.add(validator: KOFunctionTextValidator(function: { password -> Bool in
    return password.rangeOfCharacter(from: .decimalDigits) != nil
}, failureText: "one digit"))
passwordField.validation.add(validator: KOFunctionTextValidator(function: { password -> Bool in
    return password.rangeOfCharacter(from: .uppercaseLetters) != nil
}, failureText: "one uppercase letter"))

Regex based validator.

passwordField.validation.add(validator: KORegexTextValidator(regexPattern: "^(?=.*[a-z]{1,}.*)(?=.*[A-Z]{1,}.*)(?=.*[0-9]{1,}.*)(?=.*[^a-zA-Z0-9]{1,}.*).{8,20}$", failureText: "Password should contain from 8 to 20 chars, one digit, letter, uppercase letter and special char."))

Error info in default is showing in the field's superview, but you can change this by setting manually showErrorInfoInView. If you want to show error info always or manually when there is an error you can do this by change showErrorInfoMode. In manually mode you show or hide error info by flag errorInfo.isShowing.

Showing error info can be customized by changing errorInfo.view and its show / hide animation.

//changes animations
passwordField.errorInfo.hideAnimation = KOAnimationGroup(animations:[
    KOTranslationAnimation(toValue: CGPoint(x: -200, y: 20)),
    KOFadeOutAnimation()
])
passwordField.errorInfo.showAnimation = KOAnimationGroup(animations: [
    KOTranslationAnimation(toValue: CGPoint.zero, fromValue: CGPoint(x: -200, y: 20)),
    KOFadeInAnimation(fromValue: 0)
], dampingRatio: 0.6)

//adds additional icon
passwordField.errorInfo.view.imageWidthConst.constant = 25
passwordField.errorInfo.view.imageView.image = UIImage(named:"ico_account")
passwordField.errorInfo.view.imageViewEdgesConstraintsInsets.insets =  UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
passwordField.errorInfo.view.imageView.contentMode = .scaleAspectFit

//other adjustments
passwordField.errorInfo.view.descriptionLabel.textColor = UIColor.black
passwordField.errorInfo.view.contentView.backgroundColor = UIColor.white
passwordField.errorInfo.view.layer.shadowColor = UIColor.black.cgColor
passwordField.errorInfo.view.layer.shadowOffset = CGSize(width: 0, height: -2)
passwordField.errorInfo.view.layer.shadowRadius = 5
passwordField.errorInfo.view.layer.shadowOpacity = 0.7
passwordField.errorInfo.view.markerColor = UIColor.white

You can replace errorInfo.view completely by errorInfo.customView, but new view need to implement KOErrorInfoProtocol.

class UserNameErrorInfoView : UIView, KOErrorInfoProtocol{
    func markerCenterXEqualTo(_ constraint: NSLayoutXAxisAnchor) -> NSLayoutConstraint? {
        return nil
    }
}
...
//sets custom error info view
let userNameErrorInfoView = UserNameErrorInfoView()
userNameErrorInfoView.backgroundColor = UIColor.gray.withAlphaComponent(0.85)

let userNameErrorInfoLabel = UILabel()
userNameErrorInfoLabel.textColor = UIColor.white
userNameErrorInfoLabel.text = "Incorrect username try again"
userNameErrorInfoLabel.translatesAutoresizingMaskIntoConstraints = false
userNameErrorInfoView.addSubview(userNameErrorInfoLabel)
userNameErrorInfoView.addConstraints([
    userNameErrorInfoLabel.leftAnchor.constraint(equalTo: userNameErrorInfoView.leftAnchor, constant: 12),
    userNameErrorInfoLabel.rightAnchor.constraint(equalTo: userNameErrorInfoView.rightAnchor, constant: -12),
    userNameErrorInfoLabel.bottomAnchor.constraint(equalTo: userNameErrorInfoView.bottomAnchor, constant: -8),
    userNameErrorInfoLabel.topAnchor.constraint(equalTo: userNameErrorInfoView.topAnchor, constant: 8)
])

userNameField.errorInfo.customView = userNameErrorInfoView

Error view that is showing at the right corner of the field, can be customized by changing error.iconView or replacing it by error.customView.

//sets custom error view
let passwordErrorLabel = UILabel()
passwordErrorLabel.backgroundColor = UIColor.red
passwordErrorLabel.textColor = UIColor.black
passwordErrorLabel.textAlignment = .center
passwordErrorLabel.text = "Incorrect"

passwordField.error.customView = passwordErrorLabel
passwordField.error.viewWidth = 100

//or just sets the other image
// passwordField.error.iconView.image = UIImage(named:"someImage")

KOScrollOffsetProgressController

Controller that calculates progress from given range based on scroll view offset and selected calculating 'mode'.

First declare variable.

private var scrollOffsetProgressController: KOScrollOffsetProgressController!

Initialize "KOScrollOffsetProgressController", and handle progress event or delegate. Controller based on scrollView and min/max offset will be calculating progress (from 0.0 to 1.0) of translation. Axis can be changed by parameter scrollOffsetAxis.

scrollOffsetProgressController = KOScrollOffsetProgressController(scrollView: collectionView, minOffset: 0, maxOffset: 300)

//user have to scroll content by 300 points in y axis 
//to change topBar's height to smallest 
//and to completely show minTopBarView and hide maxTopBarView
scrollOffsetProgressController.progressChangedEvent = {
    [weak self] progress in
    guard let sSelf = self else{
        return
    }
    let entryProgress = (1.0 - progress)
    sSelf.topBarHeight.constant = entryProgress * sSelf.maxSize + progress * sSelf.minSize
    sSelf.maxTopBarView.alpha = entryProgress
    sSelf.minTopBarView.alpha = progress
    sSelf.view.layoutIfNeeded()
}

Depending on selected mode, progress can be different:

  • contentOffsetBased: (default) progress is calculating from current contentOffset
  • translationOffsetBased: progress is calculating based on difference between last content offset and new one
  • scrollingBlockedUntilProgressMax: progress is calculating based on difference between touches (last and new one), scroll is completely blocked until the progress reaches value 1.0

KODialogViewController

High customizable dialog view, that can be used to create you own dialog in simply way.

You can create your own content by inherit from KODialogViewController or from its descendants. In below example we want to create items table picker with search field. The first step will be create a class that inherit from KOItemsTablePickerViewController, then we need to override function createContentView to add our searchField to the contentView.

class SearchItemsTablePickerViewController : KOItemsTablePickerViewController{
    //our search field
    private(set) weak var searchField : KOTextField!

    override func createContentView() -> UIView {
        //new contentView
        let contentView = UIView()
    
        //create default contentView for KOItemsTablePickerViewController
        let itemsTable = super.createContentView()
        contentView.addSubview(itemsTable)
        itemsTable.translatesAutoresizingMaskIntoConstraints = false
    
        let searchField = KOTextField()
        searchField.borderStyle = .roundedRect
        searchField.border.settings = AppSettings.fieldBorder
        searchField.placeholder = "Search country"
        contentView.addSubview(searchField)
        searchField.translatesAutoresizingMaskIntoConstraints = false
        self.searchField = searchField
    
        contentView.addConstraints([
            searchField.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 8),
            searchField.rightAnchor.constraint(equalTo: contentView.rightAnchor,  constant: -8),
            searchField.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4),
            itemsTable.topAnchor.constraint(equalTo: searchField.bottomAnchor, constant: 4),
            itemsTable.leftAnchor.constraint(equalTo: contentView.leftAnchor),
            itemsTable.rightAnchor.constraint(equalTo: contentView.rightAnchor),
            itemsTable.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
        return contentView
    }
}

We can additional customize our picker by changing its parameters.

dialogViewController.modalPresentationCapturesStatusBarAppearance = true
dialogViewController.mainViewHorizontalAlignment = .center
dialogViewController.mainViewVerticalAlignment = .center
dialogViewController.mainView.backgroundVisualEffect = UIBlurEffect(style: .dark)
dialogViewController.mainView.layer.cornerRadius = 12
dialogViewController.mainView.clipsToBounds = true
dialogViewController.mainView.barMode = .bottom
dialogViewController.mainView.barView.backgroundColor = UIColor.black.withAlphaComponent(0.1)
dialogViewController.mainView.barView.titleLabel.textColor = UIColor.white
(dialogViewController.mainView.barView.leftView as? UIButton)?.setTitleColor(UIColor.white, for: .normal)
(dialogViewController.mainView.barView.rightView as? UIButton)?.setTitleColor(UIColor.white, for: .normal)

Or we can change the transition.

//override presenting animation
let viewToAnimationDuration : TimeInterval = 0.5
let viewToAnimation = KOScaleAnimation(toValue: CGPoint(x: 1, y: 1), fromValue: CGPoint.zero)
viewToAnimation.timingParameters = UISpringTimingParameters(dampingRatio: 0.6)
let animationControllerPresenting = KOAnimatedTransitioningController(duration: viewToAnimationDuration, viewToAnimation: viewToAnimation, viewFromAnimation: nil)

//override dismissing animation
let viewFromAnimationDuration : TimeInterval = 0.5
let viewFromAnimation = KOAnimationGroup(animations: [
    KOFadeOutAnimation(),
    KOScaleAnimation(toValue: CGPoint(x: 0.5, y: 0.5))
], duration : viewFromAnimationDuration)
let animationControllerDismissing = KOAnimatedTransitioningController(duration: viewFromAnimationDuration, viewToAnimation: nil, viewFromAnimation: viewFromAnimation)

dialogViewController.customTransition = KOVisualEffectDimmingTransition(effect: UIBlurEffect(style: .dark), animationControllerPresenting: animationControllerPresenting, animationControllerDismissing: animationControllerDismissing)

KODatePickerViewController

Simple way to get the date from the user.

You can use predefined function to present date picker at the screen like below. Action viewLoaded lets you to set title of barView and left / right button to accept or cancel dialog's result.

_ = presentDatePicker(viewLoadedAction: KODialogActionModel(title: "Select your birthday", action: {
    [weak self](dialogViewController) in
    let datePickerViewController = dialogViewController as! KODatePickerViewController
    //sets the cancel button
    datePickerViewController.leftBarButtonAction = KODialogActionModel.dismissAction(withTitle: "Cancel")
    //sets the done button
    datePickerViewController.rightBarButtonAction = KODialogActionModel.dismissAction(withTitle: "Done", action: {
        [weak self](datePickerViewController: KODatePickerViewController) in
        //get the new date from the picker
        self?.birthdayDate = datePickerViewController.datePicker.date
    })

    //additional customization of datePicker
    datePickerViewController.datePicker.datePickerMode = .date
    datePickerViewController.datePicker.maximumDate = Date()
    datePickerViewController.datePicker.minimumDate = Calendar.current.date(byAdding: .year, value: -120, to: Date())
    datePickerViewController.datePicker.date = self?.date
    }))

Please go to section about KODialogViewController to find more about customization.

KOOptionsPickerViewController

Simple way to get the selected option from the user.

You can use predefined function to present option picker at the screen like below. Options is the arrays of categories / components, that user can select. Action viewLoaded lets you to set title of barView and left / right button to accept or cancel dialog's result.

fileprivate var filmTypes: [String] = [ "Action", "Adventure", "Biographical", "Comedy", "Crime", "Drama", "Family", "Horror", "Musical", "Romance", "Spy", "Thriller", "War", "Incorrect type"]

//...

_ = presentOptionsPicker(withOptions: [filmTypes], viewLoadedAction: KODialogActionModel(title: "Select your favorite film type", action: {
    [weak self](dialogViewController) in
    let optionsPickerViewController = dialogViewController as! KOOptionsPickerViewController
    //sets the cancel button
    optionsPickerViewController.leftBarButtonAction = KODialogActionModel.dismissAction(withTitle: "Cancel")
    //sets the done button
    optionsPickerViewController.rightBarButtonAction = KODialogActionModel.dismissAction(withTitle: "Done", action: {
        [weak self](optionsPickerViewController: KOOptionsPickerViewController) in
        //get the selected option
        self?.favoriteFilmTypeIndex = optionsPickerViewController.optionsPicker.selectedRow(inComponent: 0)
    })
    
    //additional, sets the start value
    if let favoriteFilmTypeIndex = self?.favoriteFilmTypeIndex{
        optionsPickerViewController.optionsPicker.selectRow(favoriteFilmTypeIndex, inComponent: 0, animated: false)
    }
    }))

Please go to section about KODialogViewController to find more about customization.

KOItemsTablePickerViewController

Simple way to get the selected option from the user from table.

You can use predefined function to present items picker at the screen like below. Action viewLoaded lets you to set title of barView and left / right button to accept or cancel dialog's result. You need to remember to set mainView.contentHeight or mainView.contentWidth depending on alignments of main view, because UITableView can't define how much size need. In the default situation you have to set mainView.contentHeight because mainViewVerticalAlignment is different than .fill. Setting and managing items through UITableDataSource need to be handle by the user.

_ = presentItemsTablePicker(viewLoadedAction: KODialogActionModel(title: "Select your country", action: {
    [weak self](dialogViewController) in
    let itemsTablePickerViewController = dialogViewController as! KOItemsTablePickerViewController
    //sets contentHeight, because mainViewVerticalAlignment is other than .fill
    itemsTablePickerViewController.mainView.contentHeight = 300
    //sets the cancel button
    itemsTablePickerViewController.leftBarButtonAction = KODialogActionModel.dismissAction(withTitle: "Cancel")
    //sets the done button
    itemsTablePickerViewController.rightBarButtonAction = KODialogActionModel.dismissAction(withTitle: "Done", action: {
        [weak self](itemsTablePickerViewController : KOItemsTablePickerViewController) in
        if let countryIndex = itemsTablePickerViewController.itemsTable.indexPathForSelectedRow?.row{
            self?.countryIndex = countryIndex
        }
    })
    itemsTablePickerViewController.itemsTable.allowsSelection = true

    //handle UITableViewDataSource
    self?.countryCollectionsController.attach(tableView: itemsTablePickerViewController.itemsTable)
    }))

Please go to section about KODialogViewController to find more about customization.

KOItemsCollectionPickerViewController

Simple way to get the selected option from the user from collection.

You can use predefined function to present items picker at the screen like below. Action viewLoaded lets you to set title of barView and left / right button to accept or cancel dialog's result. You need to remember to set mainView.contentHeight or mainView.contentWidth depending on alignments of main view, because UICollectionView can't define how much size need. In the default situation you have to set mainView.contentHeight because mainViewVerticalAlignment is different than .fill. Setting and managing items through UICollectionViewDataSource need to be handle by the user.

_ = presentItemsCollectionPicker(itemsCollectionLayout : UICollectionViewFlowLayout(), viewLoadedAction: KODialogActionModel(title: "Select your country", action: {
    [weak self](dialogViewController) in
    guard let sSelf = self else{
        return
    }

    let itemsCollectionPickerViewController = dialogViewController as! KOItemsCollectionPickerViewController
    //sets contentHeight, because mainViewVerticalAlignment is other than .fill
    itemsCollectionPickerViewController.mainView.contentHeight = 300
    //sets the cancel button
    itemsCollectionPickerViewController.leftBarButtonAction = KODialogActionModel.dismissAction(withTitle: "Cancel")
    //sets the done button
    itemsCollectionPickerViewController.rightBarButtonAction = KODialogActionModel.dismissAction(withTitle: "Done", action:{
        [weak self](itemsCollectionPickerViewController : KOItemsCollectionPickerViewController) in
        if let countryIndex = itemsCollectionPickerViewController.itemsCollection.indexPathsForSelectedItems?.first?.row{
            self?.countryIndex = countryIndex
        }
    })
    itemsCollectionPickerViewController.itemsCollection.allowsSelection = true

    //handle UICollectionViewDataSource
    sSelf.countryCollectionsController.attach(collectionView: itemsCollectionPickerViewController.itemsCollection)
    //calculate collection size
    sSelf.countryCollectionsController.calculateCollectionSize(itemsCollectionPickerViewController.itemsCollection, availableWidth: sSelf.view.bounds.width, itemMaxWidth: 120)
    }))

Please go to section about KODialogViewController to find more about customization.

KODimmingTransition

Transition uses presentation with dimming view.

Is very simple to use, you need only to set the transitioningDelegate of UIViewController.

let dimmingTransition = KODimmingTransition()
...
viewController.transitioningDelegate = dimmingTransition
viewController.modalPresentationStyle = .custom

KODialogViewController has special field for that, named customTransition.

dialogViewController.customTransition = KOVisualEffectDimmingTransition(effect: UIBlurEffect(style: .dark))

Additional settings can be changed by handle setupPresentationControllerEvent and tweaking KODimmingPresentationController. It have to be done before presentating view, in example in postInit parameter of func present.

(dialogViewController.customTransition as! KODimmingTransition).setupPresentationControllerEvent = {
    presentationController in
    //changes dimmingView color
    presentationController.dimmingView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
    //changes the animations
    presentationController.dimmingShowAnimation = KOScaleAnimation(toValue: CGPoint(x: 1, y: 1), fromValue: CGPoint.zero)
    presentationController.dimmingHideAnimation = KOScaleAnimation(toValue: CGPoint.zero)
}

Presentation animations can be changed by parameters animationControllerPresenting and animationControllerDismissing like that.

let viewToAnimationDuration : TimeInterval = 0.5
let viewToAnimation = KOScaleAnimation(toValue: CGPoint(x: 1, y: 1), fromValue: CGPoint.zero)
viewToAnimation.timingParameters = UISpringTimingParameters(dampingRatio: 0.6)
dialogViewController.customTransition?.animationControllerPresenting = KOAnimatedTransitioningController(duration: viewToAnimationDuration, viewToAnimation: viewToAnimation, viewFromAnimation: nil)

You need to remember that in iOS UIModalPresentationStyle.custom has a fullscreen context, so the view will be always have full screen frame. You can change this by setting KODimmingPresentationController.keepFrameOfView. But the touches outside will not be forwarded to below until you seted KODimmingPresentationController.touchForwardingView.passthroughViews.

KOVisualEffectDimmingTransition

Transition uses presentation with dimming view with visual effect.

It works the same as KODimmingTransition. But UIVisualEffect is setting by the parameter KODimmingPresentationController.dimmingShowAnimation, so don't change it manually.

License

This project is licensed under the MIT License - see the LICENSE file for details

About

Package of useful controls: pickers, presenting queue, textfield thats supports validation, showing the errors etc.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages