HandyUIKit

1.9.2

Handy UI features that should have been part of UIKit in the first place.
Flinesoft/HandyUIKit

What's New

2020-05-07T00:53:12Z

Added

  • Made cornerRadius and IBDesignables available for override.

Changed

  • Updated code to Xcode 11.4 & Swift 5.

Build Status codebeat badge Version: 1.9.2 Swift: 5.0 Platforms: iOS | tvOS License: MIT
PayPal: Donate GitHub: Become a sponsor Patreon: Become a patron

InstallationUsageDonationIssuesContributingLicense

HandyUIKit

The goal of this library is to provide handy UI related features that we feel like they should have been part of the UIKit frameworks themselves. Therefore this library is intended to inherit solutions for common tasks that appear in daily programming and tries to comply to the same naming conventions as already used in the Apple frameworks.

If you like this, please also checkout HandySwift for handy features that didn't make it into the Swift standard library. It includes additions that are not UI related.

Installation

Currently the recommended way of installing this library is via Carthage. Cocoapods might work, too, but is not tested.

You can of course also just include this framework manually into your project by downloading it or by using git submodules.

Usage

Please have a look at the UsageExamples.playground for a complete list of features provided. Open the Playground from within the .xcworkspace in order for it to work.


Feature Overview


UIColorExtension

If you are using the RGB color system to define your colors, you definitely should checkout this great blog post on why RGB is a bad choice for most projects (not all, of course). And if you think you found the perfect way of changing the brightness of a color using the HSB system, you'll be proved wrong there, too. Honestly, it's worth a read. If you don't want though: "Luminance" is the keyword here and it is the single most important reason why HandyUIKit integrates native support for the HLC (or sometimes called LCh) color system to the UIColor class. HLC is a more human-understandable transformation of the LAB color space and shares its great advantage of having a single value that you need to change to correctly change the perceived brightness of any given color: The luminance value. And changing the brightness can save you a lot of time when working with colors in apps, as described in the blog post.

init(hue:luminance:chroma:)

Initializes a UIColor with given HLC (LCh) colors normed to ranges from 0 to 1.

let hlcaColor = UIColor(hue: 180/360, luminance: 30/100, chroma: 125/128, alpha: 1)

.hlca

Returns a tuple with named HLCA parameters for easy access.

hlcaColor.hlca.hue // => 0.5
hlcaColor.hlca.luminance // => 0.3
hlcaColor.hlca.chroma // => 0.97
hlcaColor.hlca.alpha // => 1.0

.rgba

Returns a tuple with named RGBA parameters for easy access.

let rgbaColor = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 0.4)
rgbaColor.rgba.red // => 0.1
rgbaColor.rgba.green // => 0.2
rgbaColor.rgba.blue // => 0.3
rgbaColor.rgba.alpha // => 0.4

.hsba

Returns a tuple with named HSBA parameters for easy access.

let hsbaColor = UIColor(hue: 0.1, saturation: 0.2, brightness: 0.3, alpha: 0.4)
hsbaColor.hsba.hue // => 0.1
hsbaColor.hsba.saturation // => 0.2
hsbaColor.hsba.brightness // => 0.3
hsbaColor.hsba.alpha // => 0.4

.change(ChangeableAttribute, by:)

Creates a new UIColor object with a single attribute changed by a given difference using addition.

color.rgba.blue // => 0.3
let newColor = color.change(.blue, by: 0.2)
newColor.rgba.blue // => 0.5

.change(ChangeableAttribute, to:)

Creates a new UIColor object with the value of a single attribute set to a given value.

color.hlca.luminance // => 0.3
let newColor = color.change(.luminance, to: 0.8)
newColor.hlca.luminance // => 0.8

UIViewExtension

.toImage(size:)

Takes a screenshot of the UIView's content optionally resizing the result to a given size.

let view = UIView(frame: CGRect(width: 500, height: 500))
let subview = UIView(frame: CGRect(width: 200, height: 200))
view.addSubview(subview)

view.backgroundColor = .blue
subview.backgroundColor = .red

let fullSizeContent = view.toImage() // => <UIImage: width: 500, height: 500>
let downSizedContent = view.toImage(size: CGSize(width: 80, height: 80))
// => <UIImage: width: 80, height: 80>

.bindEdgesToSuperview()

Adds constraints to the subview so it always has the same size and position as the superview.

view.frame // => {x: 0, y: 0, w: 500, h: 500}
subview.frame // => {x: 150, y: 150, w: 200, h: 200}
subview.bindEdgesToSuperview()
view.layoutIfNeeded()
subview.frame // => {x: 0, y: 0, w: 500, h: 500}

CoreGraphicsExtensions

CGSize.inPixels / CGSize.inPixels(screen:)

Returns a new CGSize object with the width and height converted to true pixels on screen.

let size = CGSize(width: 100, height: 50)
size.inPixels // test this with a Retina screen target
// => {w 200 h 100}
size.inPixels(UIScreen.screens.last!) // pass a different screen
// => {w 50 h 25}

CGPoint.inPixels / CGPoint.inPixels(screen:)

Returns a new CGPoint object with the x and y converted to true pixels on screen.

let point = CGPoint(x: 100, y: 50)
point.inPixels // test this with a Retina screen target
// => {x 200 y 100}
let someScreen = UIScreen.screens.last!
point.inPixels(someScreen) // pass a different screen
// => {x 50 y 25}

CGRect.inPixels / CGRect.inPixels(screen:)

Returns a new CGRect object with the origin and size converted to true pixels on screen.

let rect = CGRect(x: 10, y: 20, width: 100, height: 50)
rect.inPixels // test this with a Retina screen target
// => {x 20 y 40 w 200 h 100}
let someScreen = UIScreen.screens.last!
rect.inPixels(someScreen) // pass a different screen
// => {x 5 y 10 w 50 h 25}

CGRect.init(size:) / CGRect.init(width:height:)

Creates a new CGRect object from origin zero with given size.

let someSize = CGSize(width: 100, height: 50)

let originZeroRect1 = CGRect(size: someSize)
let originZeroRect2 = CGRect(width: 100, height: 50)

StringExtension

.height(forFixedWidth:font:)

Calculates and returns the height needed to fit the text into a width-constrained rect.

let loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

loremIpsum.height(forFixedWidth: 300, font: UIFont.systemFont(ofSize: 14, weight: UIFontWeightBold))
// => 183.77734375

.width(forFixedHeight:font:)

Calculates and returns the width needed to fit the text into a height-constrained rect.

loremIpsum.width(forFixedHeight: 21, font: UIFont.systemFont(ofSize: 12, weight: UIFontWeightUltraLight))
// => 2351.0390625

.hyphenated()

A hyphenated NSAttributedString with justified alignment and word wrapping line break mode.

loremIpsum.hyphenated() // => a justified & hyphenated NSAttributedString object

.superscripted(font:) / .subscripted(font:) / .superAndSubscripted(font:)

Superscript and/or subscript part of your strings with the structures ^{superscripted text} and _{subscripted text}.

"x^{2}".superscripted(font: UIFont.systemFont(ofSize: 20, weight: .medium))

Result: x2

"CO_{2}".subscripted(font: UIFont.systemFont(ofSize: 20, weight: .medium))

Result: CO2

"_{20}Ca^{1,0}".superAndSubscripted(font: UIFont.systemFont(ofSize: 20, weight: .regular))

Result: 20Ca1,0

UIImageExtension

.toGrayscale()

Creates a grayscale version of the image.

let image = UIImage(named: "someImage")!
let grayscaleImage = image.toGrayscale()

UITableViewExtension

dequeueCell(ofType:, for:)

Returns a reusable table view cell of type cellType with the name of its type as reuse identifier and adds it to the table.

let cell = tableView.dequeueCell(ofType: MyUITableViewCell.self, for: indexPath)

dequeueHeaderFooterView(ofType:)

Returns a reusable header or footer view of type viewType with the name of its type as reuse identifier and adds it to the table.

let view = tableView.dequeueHeaderFooterView(ofType: MyUITableHeaderFooterView.self)

registerCell(ofType:)

Registers a nib with the name of cellType if it exists or registers the class of type cellType as reusable cell.

tableView.registerCell(ofType: MyUITableViewCell.self)

registerHeaderFooterView(ofType:)

Registers a nib with the name of viewType if it exists or registers the class of type viewType as reusable header footer view.

tableView.registerHeaderFooterView(ofType: MyUITableHeaderFooterView.self)

UIWindowExtension

visibleViewController

Returns the currently visible view controller if any reachable within the window.

visibleViewController(from:)

Recursively follows navigation controllers, tab bar controllers and modal presented view controllers starting from the given view controller to find the currently visible view controller.

NibLoadable

This is a protocol helper you can make any UIView subclass conform to. The situation where you might want to do this is when you want to design a UIView subclass in a XIB file. In this case, just make your view type conform to NibLoadable like this:

class MyTableViewCell: UITableViewCell, NibLoadable {
		// your code
}

By default NibLoadable will search for a file named like your type, for example MyTableViewCell.xib within the project and load it. You can override static var nibName: String to change this behavior if you need to.

Your view must be set as the Files owner within the XIB file, also there must be only one root UIView object (which should just be of type UIView, not your subclass).

Now you can add IBOutlets and IBActions to your subclass and connect them within the XIB file to the Files owner.

In order to make loading work from both code and Storyboards, call loadFromNib() from within your init methods like so:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    loadFromNib()
}

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    loadFromNib()
}

That's it, now you should be able to load your custom view types designed within XIBs from code & in Storyboards. For Storyboard usage, simply add a UIView object and change it's type to your view subclass and everything should work when running your app. To see your custom view within Interface Builder, add @IBDesignable in front of the class declaration.

If you need to do any setup steps after the IBOutlets are loaded, you can override nibDidLoad which can be seen as the analogous to viewDidLoad in view controller in this perspective.

RoundableView

This is an IBDesignable subclass of UIView which provides the cornerRadius to be set right from within Interface Builder. Simply add a UIView object to your IB file and change it's type to RoundableView and you should see cornerRadius within the property inspector.

TemplateButton

This is an IBDesignable subclass of UIButton which will automatically make the image a mask for the tintColor value by setting the image rendering mode to .alwaysTemplate automatically.

TemplateImageView

This is an IBDesignable subclass of UIImageView which will automatically make the image a mask for the tintColor value by setting the image rendering mode to .alwaysTemplate automatically.

Donation

BartyCrouch was brought to you by Cihat Gündüz in his free time. If you want to thank me and support the development of this project, please make a small donation on PayPal. In case you also like my other open source contributions and articles, please consider motivating me by becoming a sponsor on GitHub or a patron on Patreon.

Thank you very much for any donation, it really helps out a lot! 💯

Contributing

See the file CONTRIBUTING.md.

License

This library is released under the MIT License. See LICENSE for details.

Description

  • Swift Tools 4.2.0

Dependencies

  • None
Last updated: Mon Apr 19 2021 16:58:40 GMT-0500 (GMT-05:00)