I was originally wanting to know how to make something like this
UIColor.myCustomGreen
so that I could define my own colors and use them throughout my app.
I had studied extensions before and I thought that I could probably use them to solve my problem, but I couldn't remember exactly how to set extensions up. Searching on Google at the time of this writing for "Swift extension" resulted in the documentation, several long tutorials, and a rather unhelpful Stack Overflow question.
So the answers are out there, but it takes some digging through the docs and tutorials. I decided to write this question and the following answer to add some better search keywords to Stack Overflow and to provide a quick refresher on how extensions are set up.
Specifically I wanted to know:
Where do the extensions reside (file and naming convention)?
What is the extension syntax?
What are a few simple common use examples?
Creating an extension
Add a new swift file with File > New > File... > iOS > Source > Swift File. You can call it what you want.
The general naming convention is to call it TypeName+NewFunctionality.swift.
Example 1 - Double
Double+Conversions.swift
import Swift // or Foundation
extension Double {
func celsiusToFahrenheit() -> Double {
return self * 9 / 5 + 32
}
func fahrenheitToCelsius() -> Double {
return (self - 32) * 5 / 9
}
}
Usage:
let boilingPointCelsius = 100.0
let boilingPointFarenheit = boilingPointCelsius.celsiusToFahrenheit()
print(boilingPointFarenheit) // 212.0
Example 2 - String
String+Shortcuts.swift
import Swift // or Foundation
extension String {
func replace(target: String, withString: String) -> String {
return self.replacingOccurrences(of: target, with: withString)
}
}
Usage:
let newString = "the old bike".replace(target: "old", withString: "new")
print(newString) // "the new bike"
Here are some more common String extensions.
Example 3 - UIColor
UIColor+CustomColor.swift
import UIKit
extension UIColor {
class var customGreen: UIColor {
let darkGreen = 0x008110
return UIColor.rgb(fromHex: darkGreen)
}
class func rgb(fromHex: Int) -> UIColor {
let red = CGFloat((fromHex & 0xFF0000) >> 16) / 0xFF
let green = CGFloat((fromHex & 0x00FF00) >> 8) / 0xFF
let blue = CGFloat(fromHex & 0x0000FF) / 0xFF
let alpha = CGFloat(1.0)
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}
See here also.
Usage:
view.backgroundColor = UIColor.customGreen
Notes
Once you define an extension it can be used anywhere in your app just like the built in class functions.
If you are not sure of exactly what the function or property syntax should look like, you can Option+click a similar built in method. For example, when I Option+clicked UIColor.greenColor I see the declaration is class func greenColor() -> UIColor. That gives me a good clue for how to set up my custom method.
Apple Documentation for Extensions
In Objective-C extensions are known as categories.
Try this some new extension methods:
UIColor
extension UIColor{
//get new color from rgb value
class func RGB(_ red:CGFloat , andGreenColor green:CGFloat, andBlueColor blue:CGFloat, withAlpha alpha:CGFloat) -> UIColor
{
let color = UIColor(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: alpha)
return color
}
}
//return color from comma separated string of RGB paramater
convenience init(rgbString :String, alpha:CGFloat = 1.0){
let arrColor = rgbString.components(separatedBy: ",")
let red:CGFloat = CGFloat(NumberFormatter().number(from: arrColor[0])!)
let green:CGFloat = CGFloat(NumberFormatter().number(from: arrColor[1])!)
let blue:CGFloat = CGFloat(NumberFormatter().number(from: arrColor[2])!)
self.init(red: red/255.0, green: green/255.0, blue: blue/255.0, alpha: alpha)
}
//return color from hexadecimal value
//let color2 = UIColor(rgbHexaValue: 0xFFFFFFFF)
convenience init(rgbHexaValue: Int, alpha: CGFloat = 1.0) {
self.init(red: CGFloat((rgbHexaValue >> 16) & 0xFF), green: CGFloat((rgbHexaValue >> 8) & 0xFF), blue: CGFloat(rgbHexaValue & 0xFF), alpha: alpha)
}
}
UITextField
extension UITextField{
//set cornerRadius
func cornerRadius(){
self.layoutIfNeeded()
self.layer.cornerRadius = self.frame.height / 2
self.clipsToBounds = true
}
//set bordercolor
func borderColor(){
self.layer.borderColor = TEXTFIELD_BORDER_COLOR.cgColor
self.layer.borderWidth = 1.0
}
//set borderWidth
func borderWidth(size:CGFloat){
self.layer.borderWidth = size
}
//check textfield is blank
func blank() -> Bool{
let strTrimmed = self.text!.trim()//get trimmed string
if(strTrimmed.characters.count == 0)//check textfield is nil or not ,if nil then return false
{
return true
}
return false
}
//set begginning space - left space
func setLeftPadding(paddingValue:CGFloat) {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: paddingValue, height: self.frame.size.height))
self.leftViewMode = .always
self.leftView = paddingView
}
//set end of space
func setRightPadding(paddingValue:CGFloat){
let paddingView = UIView(frame: CGRect(x: (self.frame.size.width - paddingValue), y: 0, width: paddingValue, height: self.frame.size.height))
self.rightViewMode = .always
self.rightView = paddingView
}
}
UIFont
extension UIFont{
// Returns a scaled version of UIFont
func scaled(scaleFactor: CGFloat) -> UIFont {
let newDescriptor = fontDescriptor.withSize(fontDescriptor.pointSize * scaleFactor)
return UIFont(descriptor: newDescriptor, size: 0)
}
}
UIImage
public enum ImageFormat {
case PNG
case JPEG(CGFloat)
}
extension UIImage {
//convert image to base64 string
func toBase64() -> String {
var imageData: NSData
switch format {
case .PNG: imageData = UIImagePNGRepresentation(self)! as NSData
case .JPEG(let compression): imageData = UIImageJPEGRepresentation(self, compression)! as NSData
}
return imageData.base64EncodedString(options: .lineLength64Characters)
}
//convert string to image
class func base64ToImage(toImage strEncodeData: String) -> UIImage {
let dataDecoded = NSData(base64Encoded: strEncodeData, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters)!
let image = UIImage(data: dataDecoded as Data)
return image!
}
//Function for store file/Image into local directory. If image is already on the directory then first remove it and replace new image/File on that location
func storedFileIntoLocal(strImageName:String) -> String{
var strPath = ""
let documentDirectory1 = NSString.init(string: String.documentDirectory())
let imageName:String = strImageName + ".png"
let imagePath = documentDirectory1.appendingPathComponent(imageName)
strPath = imagePath
let fileManager = FileManager.default
let isExist = fileManager.fileExists(atPath: String.init(imagePath))
if(isExist == true)
{
do {
try fileManager.removeItem(atPath: imagePath as String)//removing file if exist
// print("Remove success")
} catch {
print(error)
}
}
let imageData:Data = UIImageJPEGRepresentation(self, 0.5)!
do {
try imageData.write(to: URL(fileURLWithPath: imagePath as String), options: .atomic)
} catch {
print(error)
strPath = "Failed to cache image data to disk"
return strPath
}
return strPath
}
//function for resize image
func resizeImage(targetSize: CGSize) -> UIImage {
let size = self.size
let widthRatio = targetSize.width / self.size.width
let heightRatio = targetSize.height / self.size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if(widthRatio > heightRatio) {
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
} else {
// newSize = size
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
self.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
Date
let YYYY_MM_DD_HH_MM_SS_zzzz = "yyyy-MM-dd HH:mm:ss +zzzz"
let YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"
let DD_MM_YYYY = "dd-MM-yyyy"
let MM_DD_YYYY = "MM-dd-yyyy"
let YYYY_DD_MM = "yyyy-dd-MM"
let YYYY_MM_DD_T_HH_MM_SS = "yyyy-MM-dd'T'HH:mm:ss"
extension Date{
//convert string to date
static func convertStringToDate(strDate:String, dateFormate strFormate:String) -> Date{
let dateFormate = DateFormatter()
dateFormate.dateFormat = strFormate
dateFormate.timeZone = TimeZone.init(abbreviation: "UTC")
let dateResult:Date = dateFormate.date(from: strDate)!
return dateResult
}
//Function for old date format to new format from UTC to local
static func convertDateUTCToLocal(strDate:String, oldFormate strOldFormate:String, newFormate strNewFormate:String) -> String{
let dateFormatterUTC:DateFormatter = DateFormatter()
dateFormatterUTC.timeZone = NSTimeZone(abbreviation: "UTC") as TimeZone!//set UTC timeZone
dateFormatterUTC.dateFormat = strOldFormate //set old Format
if let oldDate:Date = dateFormatterUTC.date(from: strDate) as Date?//convert date from input string
{
dateFormatterUTC.timeZone = NSTimeZone.local//set localtimeZone
dateFormatterUTC.dateFormat = strNewFormate //make new dateformatter for output format
if let strNewDate:String = dateFormatterUTC.string(from: oldDate as Date) as String?//convert dateInUTC into string and set into output
{
return strNewDate
}
return strDate
}
return strDate
}
//Convert without UTC to local
static func convertDateToLocal(strDate:String, oldFormate strOldFormate:String, newFormate strNewFormate:String) -> String{
let dateFormatterUTC:DateFormatter = DateFormatter()
//set local timeZone
dateFormatterUTC.dateFormat = strOldFormate //set old Format
if let oldDate:Date = dateFormatterUTC.date(from: strDate) as Date?//convert date from input string
{
dateFormatterUTC.timeZone = NSTimeZone.local
dateFormatterUTC.dateFormat = strNewFormate //make new dateformatter for output format
if let strNewDate = dateFormatterUTC.string(from: oldDate as Date) as String?//convert dateInUTC into string and set into output
{
return strNewDate
}
return strDate
}
return strDate
}
//Convert Date to String
func convertDateToString(strDateFormate:String) -> String{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = strDateFormate
let strDate = dateFormatter.string(from: self)
// dateFormatter = nil
return strDate
}
//Convert local to utc
static func convertLocalToUTC(strDate:String, oldFormate strOldFormate:String, newFormate strNewFormate:String) -> String{
let dateFormatterUTC:DateFormatter = DateFormatter()
dateFormatterUTC.timeZone = NSTimeZone.local as TimeZone!//set UTC timeZone
dateFormatterUTC.dateFormat = strOldFormate //set old Format
if let oldDate:Date = dateFormatterUTC.date(from: strDate) as Date?//convert date from input string
{
dateFormatterUTC.timeZone = NSTimeZone.init(abbreviation: "UTC")! as TimeZone//set localtimeZone
dateFormatterUTC.dateFormat = strNewFormate //make new dateformatter for output format
if let strNewDate:String = dateFormatterUTC.string(from: oldDate as Date) as String?//convert dateInUTC into string and set into output
{
return strNewDate
}
return strDate
}
return strDate
}
//Comparison two date
static func compare(date:Date, compareDate:Date) -> String{
var strDateMessage:String = ""
let result:ComparisonResult = date.compare(compareDate)
switch result {
case .orderedAscending:
strDateMessage = "Future Date"
break
case .orderedDescending:
strDateMessage = "Past Date"
break
case .orderedSame:
strDateMessage = "Same Date"
break
default:
strDateMessage = "Error Date"
break
}
return strDateMessage
}
}
Calling this functions:
let color1 = UIColor.RGB(100.0, andGreenColor: 200.0, andBlueColor: 300.0, withAlpha: 1.0)
let color2 = UIColor.init(rgbHexaValue: 800000, alpha: 1.0)
let color3 = UIColor.init(rgbString: ("100.0,200.0,300.0", alpha: 1.0)
self.txtOutlet.cornerRadius()
self.txtOutlet.borderColor()
self.txtOutlet.setLeftPadding(paddingValue: 20.0)
self.txtOutlet.setRightPadding(paddingValue: 20.0)
let yourScaledFont = self.dependentView.font.scaled(scaleFactor: n as! CGFloat)
let base64String = (image?.toBase64(format: ImageFormat.PNG))!
let resultImage = UIImage.base64ToImage(toImage: base64String)
let path = yourImage.storedFileIntoLocal(strImageName: "imagename")
Swift 3.0 example:
extension UITextField
{
func useUnderline() {
let border = CALayer()
let borderWidth = CGFloat(1.0)
border.borderColor = UIColor.lightGray.cgColor
border.frame = CGRect(origin: CGPoint(x: 0,y :self.frame.size.height - borderWidth), size: CGSize(width: self.frame.size.width, height: self.frame.size.height))
border.borderWidth = borderWidth
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
}
Underline text in UITextField
Used in function ViewDidLoad()
firstNametext.underlined(0.5)
Extension
extension UITextField {
func underlined(_ size:Double){
let border = CALayer()
let width = CGFloat(size)
border.borderColor = UIColor.red.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width,
width: self.frame.size.width, height: self.frame.size.height)
border.borderWidth = width
self.layer.addSublayer(border)
self.layer.masksToBounds = true }
}
}
UIColor+util.swift
import UIKit
extension UIColor{
class func getCustomBlueColor() -> UIColor
{
return UIColor(red:0.043, green:0.576 ,blue:0.588 , alpha:1.00)
}
func getNameofColour() ->String
{
return "myOrange"
}
}
Usage :
NSLog("\(UIColor.getCustomBlueColor())")
let color=UIColor(red:0.043, green:0.576 ,blue:0.588 , alpha:1.00);
NSLog(color.getNameofColour())
I hope you see that what is difference . One of Function starting with class func another one starting only func . you can use which you like.
One of the best example of extension and convenience initializer :
extension UIActivityIndicatorView {
convenience init(activityIndicatorStyle: UIActivityIndicatorViewStyle, color: UIColor, placeInTheCenterOf parentView: UIView) {
self.init(activityIndicatorStyle: activityIndicatorStyle)
center = parentView.center
self.color = color
parentView.addSubview(self)
}
}
You can use it in following ways :
Initialize activityIndicator
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge, color: .gray, placeInTheCenterOf: view)
Start animating activity indicator
activityIndicator.startAnimating()
Stop animating activity indicator
activityIndicator.stopAnimating()
If you like to use a colour with a given tint like used in brand manuals:
Swift 4.2 + xcode 9.4.1.
extension UIColor {
func withTint(tint: CGFloat)->UIColor {
var tint = max(tint, 0)
tint = min(tint, 1)
/* Collect values of sender */
var r : CGFloat = 0
var g : CGFloat = 0
var b : CGFloat = 0
var a : CGFloat = 0
self.getRed(&r, green: &g, blue: &b, alpha: &a)
/* Calculate the tint */
r = r+(1-r)*(1-tint)
g = g+(1-g)*(1-tint)
b = b+(1-b)*(1-tint)
a = 1
return UIColor.init(red: r, green: g, blue: b, alpha: a)
}
}
In your code
let redWithTint = UIColor.red.withTint(tint: 0.4)
Here is an extension example of an eye catching animation effect that works with cells from UITableView. Each cell grows from a point source to normal size as you scroll a UITableView. Adjust the animation timing as desired.
Since each cell shows up with a little time stagger while scrolling, the effect ripples nicely! See this 15 second clip that showcases the effect : https://www.youtube.com/watch?v=BVeQpno56wU&feature=youtu.be
extension UITableViewCell {
func growCellDuringPresentation(thisCell : UITableViewCell) {
thisCell.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
UIView.animate(withDuration: TimeInterval(0.35), delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations: {
thisCell.transform = CGAffineTransform(scaleX: 1, y: 1)
}, completion: nil)
}
}
To use the extension you make a call to it just before the cell is returned in cellForRowAt, like shown below :
cell.growCellDuringPresentation(thisCell: cell)
return cell
Note this same method works when returning cells for a collection view.
Here is an extension that works exactly the same, except that it rotates the cells during presentation :
extension UITableViewCell {
func rotateCellDuringPresentation(thisCell : UITableViewCell) {
thisCell.transform = CGAffineTransform(rotationAngle: .pi)
UIView.animate(withDuration: TimeInterval(0.35), delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations: {
thisCell.transform = CGAffineTransform(rotationAngle: 0)
}, completion: nil)
}
}
It's called similarly :
cell.rotateCellDuringPresentation(thisCell: cell)
return cell
Here is an extension along the same lines that translates the cells in the X direction
extension UITableViewCell {
func translateCellDuringPresentation(thisCell : UITableViewCell) {
thisCell.layer.transform = CATransform3DMakeTranslation(-300, 0, 0)
UIView.animate(withDuration: TimeInterval(0.5), delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations: {
thisCell.layer.transform = CATransform3DMakeTranslation(0, 0, 0)
}, completion: nil)
}
}
It's called similarly :
cell.translateCellDuringPresentation(thisCell: cell)
return cell
Related
I'm trying to build an app to find similarities as a percentage between two UIImage, the app captures the user stroke pencil and converts it to UIImage then finds similarity pixel by pixel with the image stored on the app.
I found code that finds difference (NOT similarity), that is work if I have two images as JPEG but in my case that gives me an error when I click the button
convertoand:]: unrecognized selector sent to instance 0x10900f2d0"
The button will capture stroke pencil and then compare in this func compareImages(image1: imageStroke, image2: imageOnApp) then print it on Label:
#IBAction func btnFindSimilarity(_ sender: Any) {
let imgRect = CGRect(x: 0, y: 0, width: 400, height: 100)
let imageStroke = canvasView.drawing.image(from: imgRect, scale: 1.0)
let compareResult = compareImages(image1: imageStroke, image2: imageOnApp)
lblPrintScore.text = "\(String(describing: compareResult))"
}
find diffrence code:
func pixelValues(fromCGImage imageRef: CGImage?) -> [UInt8]?
{
var width = 0
var height = 0
var pixelValues: [UInt8]?
if let imageRef = imageRef {
width = imageRef.width
height = imageRef.height
let bitsPerComponent = imageRef.bitsPerComponent
let bytesPerRow = imageRef.bytesPerRow
let totalBytes = height * bytesPerRow
let bitmapInfo = imageRef.bitmapInfo
let colorSpace = CGColorSpaceCreateDeviceRGB()
var intensities = [UInt8](repeating: 0, count: totalBytes)
let contextRef = CGContext(data: &intensities,
width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue)
contextRef?.draw(imageRef, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(width), height: CGFloat(height)))
pixelValues = intensities
}
return pixelValues
}
func compareImages(image1: UIImage, image2: UIImage) -> Double? {
guard let data1 = pixelValues(fromCGImage: image1.cgImage),
let data2 = pixelValues(fromCGImage: image2.cgImage),
data1.count == data2.count else {
return nil
}
let width = Double(image1.size.width)
let height = Double(image1.size.height)
return zip(data1, data2)
.enumerated()
.reduce(0.0) {
$1.offset % 4 == 3 ? $0 : $0 + abs(Double($1.element.0) - Double($1.element.1))
}
* 100 / (width * height * 3.0) / 255.0
}
I tried to change the code to find similarities but I was failed
Although the following example is for SwiftUI, the code can be used elsewhere.
I found on SO how to extract the colors from an UIImage (see findColors How do I get the color of a pixel in a UIImage with Swift?),
and using that, I compare 2 images for rgb similarities (see isRGBSimilar).
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var simili = 0.0
var body: some View {
Text(String(simili))
.onAppear {
guard let img1 = UIImage(named: "cat1.png") else { return }
guard let img2 = UIImage(named: "cat2.png") else { return }
print("---> img1: \(img1.size) img2: \(img2.size)")
if let percent = compareImages(image1: img1, image2: img2) {
print("----> percent: \(percent) \n")
simili = percent
}
}
}
// from: https://stackoverflow.com/questions/25146557/how-do-i-get-the-color-of-a-pixel-in-a-uiimage-with-swift
func findColors(_ image: UIImage) -> [UIColor] {
let pixelsWide = Int(image.size.width)
let pixelsHigh = Int(image.size.height)
guard let pixelData = image.cgImage?.dataProvider?.data else { return [] }
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
var imageColors: [UIColor] = []
for x in 0..<pixelsWide {
for y in 0..<pixelsHigh {
let point = CGPoint(x: x, y: y)
let pixelInfo: Int = ((pixelsWide * Int(point.y)) + Int(point.x)) * 4
let color = UIColor(red: CGFloat(data[pixelInfo]) / 255.0,
green: CGFloat(data[pixelInfo + 1]) / 255.0,
blue: CGFloat(data[pixelInfo + 2]) / 255.0,
alpha: CGFloat(data[pixelInfo + 3]) / 255.0)
imageColors.append(color)
}
}
return imageColors
}
func compareImages(image1: UIImage, image2: UIImage) -> Double? {
let data1 = findColors(image1)
let data2 = findColors(image2)
guard data1.count == data2.count else { return nil }
var similarr = [Bool]()
for i in data1.indices {
similarr.append(isRGBSimilar(data1[i], data2[i], 0.1)) // <-- set epsilon
}
let simi = similarr.filter{$0}
let result = (Double(simi.count * 100) / (Double(image1.size.width) * Double(image1.size.height)))
return result
}
// compare 2 colors to be within d of each other
func isRGBSimilar(_ f: UIColor, _ t: UIColor, _ d: CGFloat) -> Bool {
var r1: CGFloat = 0; var g1: CGFloat = 0; var b1: CGFloat = 0; var a1: CGFloat = 0
f.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
var r2: CGFloat = 0; var g2: CGFloat = 0; var b2: CGFloat = 0; var a2: CGFloat = 0
t.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)
return abs(r1 - r2) <= d && abs(g1 - g2) <= d && abs(b1 - b2) <= d && abs(a1 - a2) <= d
}
I've written simple animations for drawing rectangles in lines, we can treat them as a bars.
Each bar is one shape layer which has a path which animates ( size change and fill color change ).
#IBDesignable final class BarView: UIView {
lazy var pathAnimation: CABasicAnimation = {
let animation = CABasicAnimation(keyPath: "path")
animation.duration = 1
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animation.fillMode = kCAFillModeBoth
animation.isRemovedOnCompletion = false
return animation
}()
let red = UIColor(red: 249/255, green: 26/255, blue: 26/255, alpha: 1)
let orange = UIColor(red: 1, green: 167/255, blue: 463/255, alpha: 1)
let green = UIColor(red: 106/255, green: 239/255, blue: 47/255, alpha: 1)
lazy var backgroundColorAnimation: CABasicAnimation = {
let animation = CABasicAnimation(keyPath: "fillColor")
animation.duration = 1
animation.fromValue = red.cgColor
animation.byValue = orange.cgColor
animation.toValue = green.cgColor
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animation.fillMode = kCAFillModeBoth
animation.isRemovedOnCompletion = false
return animation
}()
#IBInspectable var spaceBetweenBars: CGFloat = 10
var numberOfBars: Int = 5
let data: [CGFloat] = [5.5, 9.0, 9.5, 3.0, 8.0]
override func awakeFromNib() {
super.awakeFromNib()
initSublayers()
}
override func layoutSubviews() {
super.layoutSubviews()
setupLayers()
}
func setupLayers() {
let width = bounds.width - (spaceBetweenBars * CGFloat(numberOfBars + 1)) // There is n + 1 spaces between bars.
let barWidth: CGFloat = width / CGFloat(numberOfBars)
let scalePoint: CGFloat = bounds.height / 10.0 // 10.0 - 10 points is max
guard let sublayers = layer.sublayers as? [CAShapeLayer] else { return }
for i in 0...numberOfBars - 1 {
let barHeight: CGFloat = scalePoint * data[i]
let shapeLayer = CAShapeLayer()
layer.addSublayer(shapeLayer)
var xPos: CGFloat!
if i == 0 {
xPos = spaceBetweenBars
} else if i == numberOfBars - 1 {
xPos = bounds.width - (barWidth + spaceBetweenBars)
} else {
xPos = barWidth * CGFloat(i) + spaceBetweenBars * CGFloat(i) + spaceBetweenBars
}
let startPath = UIBezierPath(rect: CGRect(x: xPos, y: bounds.height, width: barWidth, height: 0)).cgPath
let endPath = UIBezierPath(rect: CGRect(x: xPos, y: bounds.height, width: barWidth, height: -barHeight)).cgPath
sublayers[i].path = startPath
pathAnimation.toValue = endPath
sublayers[i].removeAllAnimations()
sublayers[i].add(pathAnimation, forKey: "path")
sublayers[i].add(backgroundColorAnimation, forKey: "backgroundColor")
}
}
func initSublayers() {
for _ in 1...numberOfBars {
let shapeLayer = CAShapeLayer()
layer.addSublayer(shapeLayer)
}
}
}
The size ( height ) of bar depends of the data array, each sublayers has a different height. Based on this data I've crated a scale.
PathAnimation is changing height of the bars.
BackgroundColorAnimation is changing the collors of the path. It starts from red one, goes through the orange and finish at green.
My goal is to connect backgroundColorAnimation with data array as well as it's connected with pathAnimation.
Ex. When in data array is going to be value 1.0 then the bar going to be animate only to the red color which is a derivated from a base red color which is declared as a global variable. If the value in the data array going to be ex. 4.5 then the color animation will stop close to the delcared orange color, the 5.0 limit going to be this orange color or color close to this. Value closer to 10 going to be green.
How could I connect these conditions with animation properties fromValue, byValue, toValue. Is it an algorithm for that ? Any ideas ?
You have several problems.
You're setting fillMode and isRemovedOnCompletion. This tells me, to be blunt, that you don't understand Core Animation. You need to watch WWDC 2011 Session 421: Core Animation Essentials.
You're adding more layers every time layoutSubviews is called, but not doing anything with them.
You're adding animation every time layoutSubviews runs. Do you really want to re-animate the bars when the double-height “in-call” status bar appears or disappears, or on an interface rotation? It's probably better to have a separate animateBars() method, and call it from your view controller's viewDidAppear method.
You seem to think byValue means “go through this value on the way from fromValue to toValue”, but that's not what it means. byValue is ignored in your case, because you're setting fromValue and toValue. The effects of byValue are explained in Setting Interpolation Values.
If you want to interpolate between colors, it's best to use a hue-based color space, but I believe Core Animation uses an RGB color space. So you should use a keyframe animation to specify intermediate colors that you calculate by interpolating in a hue-based color space.
Here's a rewrite of BarView that fixes all these problems:
#IBDesignable final class BarView: UIView {
#IBInspectable var spaceBetweenBars: CGFloat = 10
var data: [CGFloat] = [5.5, 9.0, 9.5, 3.0, 8.0]
var maxDatum = CGFloat(10)
func animateBars() {
guard window != nil else { return }
let bounds = self.bounds
var flatteningTransform = CGAffineTransform.identity.translatedBy(x: 0, y: bounds.size.height).scaledBy(x: 1, y: 0.001)
let duration: CFTimeInterval = 1
let frames = Int((duration * 60.0).rounded(.awayFromZero))
for (datum, barLayer) in zip(data, barLayers) {
let t = datum / maxDatum
if let path = barLayer.path {
let path0 = path.copy(using: &flatteningTransform)
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.duration = 1
pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
pathAnimation.fromValue = path0
barLayer.add(pathAnimation, forKey: pathAnimation.keyPath)
let colors = gradient.colors(from: 0, to: t, count: frames).map({ $0.cgColor })
let colorAnimation = CAKeyframeAnimation(keyPath: "fillColor")
colorAnimation.timingFunction = pathAnimation.timingFunction
colorAnimation.duration = duration
colorAnimation.values = colors
barLayer.add(colorAnimation, forKey: colorAnimation.keyPath)
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
createOrDestroyBarLayers()
let bounds = self.bounds
let barSpacing = (bounds.size.width - spaceBetweenBars) / CGFloat(data.count)
let barWidth = barSpacing - spaceBetweenBars
for ((offset: i, element: datum), barLayer) in zip(data.enumerated(), barLayers) {
let t = datum / maxDatum
let barHeight = t * bounds.size.height
barLayer.frame = bounds
let rect = CGRect(x: spaceBetweenBars + CGFloat(i) * barSpacing, y: bounds.size.height, width: barWidth, height: -barHeight)
barLayer.path = CGPath(rect: rect, transform: nil)
barLayer.fillColor = gradient.color(at: t).cgColor
}
}
private let gradient = Gradient(startColor: .red, endColor: .green)
private var barLayers = [CAShapeLayer]()
private func createOrDestroyBarLayers() {
while barLayers.count < data.count {
barLayers.append(CAShapeLayer())
layer.addSublayer(barLayers.last!)
}
while barLayers.count > data.count {
barLayers.removeLast().removeFromSuperlayer()
}
}
}
private extension UIColor {
var hsba: [CGFloat] {
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
return [hue, saturation, brightness, alpha]
}
}
private struct Gradient {
init(startColor: UIColor, endColor: UIColor) {
self.startColor = startColor
self.startHsba = startColor.hsba
self.endColor = endColor
self.endHsba = endColor.hsba
}
let startColor: UIColor
let endColor: UIColor
let startHsba: [CGFloat]
let endHsba: [CGFloat]
func color(at t: CGFloat) -> UIColor {
let out = zip(startHsba, endHsba).map { $0 * (1.0 - t) + $1 * t }
return UIColor(hue: out[0], saturation: out[1], brightness: out[2], alpha: out[3])
}
func colors(from t0: CGFloat, to t1: CGFloat, count: Int) -> [UIColor] {
var colors = [UIColor]()
colors.reserveCapacity(count)
for i in 0 ..< count {
let s = CGFloat(i) / CGFloat(count - 1)
let t = t0 * (1 - s) + t1 * s
colors.append(color(at: t))
}
return colors
}
}
Result:
I want to generate pdf from textview after user enters the data. The textview contains paragraphs and attributed texts.
I have Followed
this procedure
and my code is:
func createPdf() {
createPDFNamed("test")
}
func getPDFPath(_ name: String) -> String {
let newPDFName = "\(name).pdf"
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
let pdfPath: String = (documentsDirectory as String).appending(newPDFName);
print(pdfPath)
return pdfPath
}
func createPDFNamed(_ name: String) {
let text = myTextView.text
let currentText: CFAttributedString = CFAttributedStringCreate(nil, (text as CFString?), nil)
if currentText != nil {
let framesetter: CTFramesetter = CTFramesetterCreateWithAttributedString(currentText)
if framesetter != nil {
let pdfFileName: String = getPDFPath(name)
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRect.zero, nil)
var currentRange: CFRange! = CFRangeMake(0, 0)
var currentPage: Int = 0
var done = false
repeat {
UIGraphicsBeginPDFPageWithInfo(CGRect(x: 0, y: 0, width: 612, height: 792), nil)
currentPage += 1
drawPageNbr(currentPage)
currentRange = renderPagewithTextRange(currentRange, andFramesetter: framesetter)
if currentRange.location == CFAttributedStringGetLength((currentText as? CFAttributedString)) {
done = true
}
} while !done
UIGraphicsEndPDFContext()
}
else {
print("Could not create the framesetter needed to lay out the atrributed string.")
}
}
else {
print("Could not create the attributed string for the framesetter")
}
}
func renderPagewithTextRange(_ currentRange: CFRange, andFramesetter framesetter: CTFramesetter) -> CFRange {
var currentRange: CFRange! = CFRangeMake(0, 0)
let currentContext: CGContext? = UIGraphicsGetCurrentContext()
currentContext?.textMatrix = .identity
let frameRect = CGRect(x: 72, y: 72, width: 468, height: 648)
let framePath: CGMutablePath = CGMutablePath()
framePath.addRect(frameRect, transform: .identity)
let frameRef: CTFrame = CTFramesetterCreateFrame(framesetter, currentRange, framePath, nil)
currentContext?.translateBy(x: 0, y: 792)
currentContext?.scaleBy(x: 1.0, y: -1.0)
CTFrameDraw(frameRef, currentContext!)
currentRange = CTFrameGetVisibleStringRange(frameRef)
currentRange.location += currentRange.length
currentRange.length = 0 as? CFIndex ?? CFIndex()
return currentRange
}
func drawPageNumber(_ pageNum: Int) {
let pageString = "Page \(Int(pageNum))"
let theFont = UIFont.systemFont(ofSize: 12)
let pageStringSize: CGSize = pageString.size(withAttributes: [.font: UIFont.systemFont(ofSize: 17.0)])
let stringRect = CGRect(x: ((612.0 - pageStringSize.width) / 2.0), y: 720.0 + ((72.0 - pageStringSize.height) / 2.0), width: pageStringSize.width, height: pageStringSize.height)
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle
paragraphStyle?.lineBreakMode = .byTruncatingTail
paragraphStyle?.alignment = .right
let attributes = [NSAttributedStringKey.font: theFont, .paragraphStyle: paragraphStyle as Any] as [NSAttributedStringKey : Any]
pageString.draw(in: stringRect, withAttributes: attributes)
}
The pdf generates and never ends even it went upto 2Gb file size. But not opening. Could you please advice where I am doing mistake to finish the pdf generation? Also after pdf creation, would like to send by mail
Thanks in advance
EDIT:
If I change the code to while done it stops but prints one page only. If I change to while !done it goes on generating pdf. Past two days searching and not end up with any solution. Also the pdf prints black only. I want to reflect the attributes to pdf. Any suggestions.
if (currentRange.location == CFAttributedStringGetLength(currentText)){
done = true
}
} while done
UIGraphicsEndPDFContext()
}
else {
print("Could not create the framesetter needed to lay out the atrributed string.")
}
}
else {
print("Could not create the attributed string for the framesetter")
}
}
I would like to be able to save a UIImage array created on the Apple Watch with watchOS and play this series of images as an animation as a group background. I can make the image array and play it but I cannot figure out how to store/save these images so I can retrieve/load them the next time I run the app so I don't have to build them every time the app runs.
Here is an example of how I am building the images with Core Graphics (Swift 3):
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController
{
#IBOutlet var colourGroup: WKInterfaceGroup!
override func awake(withContext context: AnyObject?)
{
super.awake(withContext: context)
}
override func willActivate()
{
var imageArray: [UIImage] = []
for imageNumber in 1...250
{
let newImage: UIImage = drawImage(fade: CGFloat(imageNumber)/250.0)
imageArray.append(newImage)
}
let animatedImages = UIImage.animatedImage(with:imageArray, duration: 10)
colourGroup.setBackgroundImage(animatedImages)
let imageRange: NSRange = NSRange(location: 0, length: 200)
colourGroup.startAnimatingWithImages(in: imageRange, duration: 10, repeatCount: 0)
super.willActivate()
}
func drawImage(fade: CGFloat) -> UIImage
{
let boxColour: UIColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: fade)
let opaque: Bool = false
let scale: CGFloat = 0.0
let bounds: CGRect = WKInterfaceDevice.current().screenBounds
let imageSize: CGSize = CGSize(width: bounds.width, height: 20.0)
UIGraphicsBeginImageContextWithOptions(imageSize, opaque, scale)
let radius: CGFloat = imageSize.height/2.0
let rect: CGRect = CGRect(x: 0.0, y: 0.0, width: imageSize.width, height: imageSize.height)
let selectorBox: UIBezierPath = UIBezierPath(roundedRect: rect, cornerRadius: radius)
let boxLineWidth: Double = 0.0
selectorBox.lineWidth = CGFloat(boxLineWidth)
boxColour.setFill()
selectorBox.fill()
// return the image
let result: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return result
}
override func didDeactivate()
{
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
Basically I am looking for a way to save and load a [UIImage] in a manner that I can use UIImage.animatedImage(with:[UIImage], duration: TimeInterval) with the array
Is there a way to save the image array so I can load it next time I run the app rather than rebuild the images?
Thanks
Greg
NSKeyedArchiver and NSKeyedUnarchiver did the trick. Here is Swift code for XCode 8b4:
override func willActivate()
{
var imageArray: [UIImage] = []
let fileName: String = "TheRings"
let fileManager = FileManager.default
let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! as NSURL
let theURL: URL = url.appendingPathComponent(fileName)!
if let rings = NSKeyedUnarchiver.unarchiveObject(withFile: theURL.path) as? [UIImage]
{
print("retrieving rings - found rings")
imageArray = rings
}
else
{
print("retrieving rings - can't find rings, building new ones")
for imageNumber in 1...250
{
let newImage: UIImage = drawImage(fade: CGFloat(imageNumber)/250.0)
imageArray.append(newImage)
}
NSKeyedArchiver.archiveRootObject(imageArray, toFile: theURL.path)
}
let animatedImages = UIImage.animatedImage(with:imageArray, duration: 10)
colourGroup.setBackgroundImage(animatedImages)
let imageRange: NSRange = NSRange(location: 0, length: 200)
colourGroup.startAnimatingWithImages(in: imageRange, duration: 10, repeatCount: 0)
super.willActivate()
}
The question below is similar as mine.
How to use a custom UIImage as an UITabBarItem Badge?
Is it possible to use a background image instead of drawing it myself?
I think it's fine since my app will only use a 1-digit badgeValue.
If it is not really possible, I want to know if we can change the badge color instead.
The answers in the question below are not really helping.
Is it possible to change UITabBarItem badge color
This is your best bet. Add this extension at the file scope and you can customise the badges however you like. Just call self.tabBarController!.setBadges([1,0,2]) in any of your root view controllers.
To be clear that is for a tab bar with three items, with the badge values going from left to right.
If you want to add images instead just change the addBadge method
extension UITabBarController {
func setBadges(badgeValues: [Int]) {
var labelExistsForIndex = [Bool]()
for _ in badgeValues {
labelExistsForIndex.append(false)
}
for view in self.tabBar.subviews where view is PGTabBadge {
let badgeView = view as! PGTabBadge
let index = badgeView.tag
if badgeValues[index] == 0 {
badgeView.removeFromSuperview()
}
labelExistsForIndex[index] = true
badgeView.text = String(badgeValues[index])
}
for i in 0...(labelExistsForIndex.count - 1) where !labelExistsForIndex[i] && (badgeValues[i] > 0) {
addBadge(index: i, value: badgeValues[i], color: .red, font: UIFont(name: "Helvetica-Light", size: 11)!)
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
let itemPosition = CGFloat(index + 1)
let itemWidth: CGFloat = tabBar.frame.width / CGFloat(tabBar.items!.count)
let bgColor = color
let xOffset: CGFloat = 5
let yOffset: CGFloat = -12
let badgeView = PGTabBadge()
badgeView.frame.size = CGSize(width: 12, height: 12)
badgeView.center = CGPoint(x: (itemWidth * itemPosition) - (itemWidth / 2) + xOffset, y: 20 + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width/2
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.white
badgeView.textAlignment = .center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = bgColor
badgeView.tag = index
tabBar.addSubview(badgeView)
}
}
class PGTabBadge: UILabel { }
Here is another solution based on TimWhiting's answer:
extension UITabBar {
func setBadge(value: String?, at index: Int, withConfiguration configuration: TabBarBadgeConfiguration = TabBarBadgeConfiguration()) {
let existingBadge = subviews.first { ($0 as? TabBarBadge)?.hasIdentifier(for: index) == true }
existingBadge?.removeFromSuperview()
guard let tabBarItems = items,
let value = value else { return }
let itemPosition = CGFloat(index + 1)
let itemWidth = frame.width / CGFloat(tabBarItems.count)
let itemHeight = frame.height
let badge = TabBarBadge(for: index)
badge.frame.size = configuration.size
badge.center = CGPoint(x: (itemWidth * itemPosition) - (0.5 * itemWidth) + configuration.centerOffset.x,
y: (0.5 * itemHeight) + configuration.centerOffset.y)
badge.layer.cornerRadius = 0.5 * configuration.size.height
badge.clipsToBounds = true
badge.textAlignment = .center
badge.backgroundColor = configuration.backgroundColor
badge.font = configuration.font
badge.textColor = configuration.textColor
badge.text = value
addSubview(badge)
}
}
class TabBarBadge: UILabel {
var identifier: String = String(describing: TabBarBadge.self)
private func identifier(for index: Int) -> String {
return "\(String(describing: TabBarBadge.self))-\(index)"
}
convenience init(for index: Int) {
self.init()
identifier = identifier(for: index)
}
func hasIdentifier(for index: Int) -> Bool {
let has = identifier == identifier(for: index)
return has
}
}
class TabBarBadgeConfiguration {
var backgroundColor: UIColor = .red
var centerOffset: CGPoint = .init(x: 12, y: -9)
var size: CGSize = .init(width: 17, height: 17)
var textColor: UIColor = .white
var font: UIFont! = .systemFont(ofSize: 11) {
didSet { font = font ?? .systemFont(ofSize: 11) }
}
static func construct(_ block: (TabBarBadgeConfiguration) -> Void) -> TabBarBadgeConfiguration {
let new = TabBarBadgeConfiguration()
block(new)
return new
}
}
You can use a more robust solution #UITabbarItem-CustomBadge.
Demo
Simple two line of code can get you going
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//supplying the animation parameter
[UITabBarItem setDefaultAnimationProvider:[[DefaultTabbarBadgeAnimation alloc] init]];
[UITabBarItem setDefaultConfigurationProvider:[[DefaultSystemLikeBadgeConfiguration alloc] init]];
//rest of your code goes following...
return YES;
}
well... changing the background of the built-in badge does not seem possible to me. But what IS possible instead, is the following:
Subclass the TabBarController, build a custom view laid out in a NIB, add it to the view hierarchy at the location in the tab bar, where you want it to be.
In the custom view you can set up an image as background and a label on top of that background, that will display the number value you can then change by code.
Then you need to figure out the horizontal and vertical location of your custom view where you want to place your view inside of the tab bar.
Hope this helps a little bit.
Sebastian
TimWhiting's answer updated to Swift 4 with the removal of some force unwrapping.
extension UITabBarController {
func setBadges(badgeValues: [Int]) {
var labelExistsForIndex = [Bool]()
for _ in badgeValues {
labelExistsForIndex.append(false)
}
for view in tabBar.subviews {
if let badgeView = view as? PGTabBadge {
let index = badgeView.tag
if badgeValues[index] == 0 {
badgeView.removeFromSuperview()
}
labelExistsForIndex[index] = true
badgeView.text = String(badgeValues[index])
}
}
for i in 0 ... labelExistsForIndex.count - 1 where !labelExistsForIndex[i] && badgeValues[i] > 0 {
addBadge(
index: i,
value: badgeValues[i],
color: UIColor(red: 4 / 255, green: 110 / 255, blue: 188 / 255, alpha: 1),
font: UIFont(name: "Helvetica-Light", size: 11)!
)
}
}
func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
guard let tabBarItems = tabBar.items else { return }
let itemPosition = CGFloat(index + 1)
let itemWidth: CGFloat = tabBar.frame.width / CGFloat(tabBarItems.count)
let bgColor = color
let xOffset: CGFloat = 12
let yOffset: CGFloat = -9
let badgeView = PGTabBadge()
badgeView.frame.size = CGSize(width: 17, height: 17)
badgeView.center = CGPoint(x: (itemWidth * itemPosition) - (itemWidth / 2) + xOffset, y: 20 + yOffset)
badgeView.layer.cornerRadius = badgeView.bounds.width / 2
badgeView.clipsToBounds = true
badgeView.textColor = UIColor.white
badgeView.textAlignment = .center
badgeView.font = font
badgeView.text = String(value)
badgeView.backgroundColor = bgColor
badgeView.tag = index
tabBar.addSubview(badgeView)
}
}
class PGTabBadge: UILabel {}
Sample Image