Save and Load Core Graphic UIImage Array on watchOS - swift

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

Related

get the cgImage from a UIImage that was selected using UIImagePickerController

I am trying to build something for my own learning where I select an image from my photo library then divide that image up into sections. I had found info on how to split a single UIImage into sections, but in order to do that I need to have access to the cgImage property of the UIImage object. My problem is, the cgImage is always nil/null when selecting an image from the UIImagePickerController. Below is a stripped down version of my code, I'm hoping someone knows why the cgImage is always nil/null...
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#IBOutlet weak var selectButton: UIButton!
let picker = UIImagePickerController()
var image: UIImage!
var images: [UIImage]!
override func viewDidLoad() {
super.viewDidLoad()
picker.delegate = self
picker.sourceType = .photoLibrary
}
#objc func selectPressed(_ sender: UIButton) {
self.present(picker, animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info [UIImagePickerController.InfoKey : Any]) {
guard let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else {
self.picker.dismiss(animated: true, completion: nil)
return
}
self.image = image
self.picker.dismiss(animated: true, completion: nil)
self.makePuzzle()
}
func makePuzze() {
let images = self.image.split(times: 5)
}
}
extension UIImage {
func split(times: Int) -> [UIImage] {
let size = self.size
var xpos = 0, ypos = 0
var images: [UIImage] = []
let width = Int(size.width) / times
let height = Int(size.height) / times
for x in 0..<times {
xpos = 0
for y in 0..<times {
let rect = CGRect(x: xpos, y: ypos, width: width, height: height)
let ciRef = self.cgImage?.cropping(to: rect) //this is always nil
let img = UIImage(cgImage: ciRef!) //crash because nil
xpos += width
images.append(img)
}
ypos += height
}
return images
}
}
I can't seem to get the cgImage to be anything but nil/null and the app crashes every time. I know I can change the ! to ?? nil or something similar to avoid the crash, or add a guard or something, but that isn't really the problem, the problem is the cgImage is nil. I have looked around and the only thing I can find is how to get the cgImage with something like image.cgImage but that doesn't work. I think it has something to do with the image being selected from the UIImagePickerController, maybe that doesn't create the cgImage properly? Honestly not sure and could use some help. Thank you.
This is not an answer, just a beefed up comment with code.
Your assumption that the problem may be due to the UIImagePickerController could be correct.
Here is my SwiftUI test code. It shows your split(..) code (with some minor mods) working.
extension UIImage {
func split(times: Int) -> [UIImage] {
let size = self.size
var xpos = 0, ypos = 0
var images: [UIImage] = []
let width = Int(size.width) / times
let height = Int(size.height) / times
if let cgimg = self.cgImage { // <-- here
for _ in 0..<times {
xpos = 0
for _ in 0..<times {
let rect = CGRect(x: xpos, y: ypos, width: width, height: height)
if let ciRef = cgimg.cropping(to: rect) { // <-- here
let img = UIImage(cgImage: ciRef)
xpos += width
images.append(img)
}
}
ypos += height
}
}
return images
}
}
struct ContentView: View {
#State var imgSet = [UIImage]()
var body: some View {
ScrollView {
ForEach(imgSet, id: \.self) { img in
Image(uiImage: img).resizable().frame(width: 100, height: 100)
}
}
.onAppear {
if let img = UIImage(systemName: "globe") { // for testing
imgSet = img.split(times: 2)
}
}
}
}

PDF Thumbnail Generator Always Returning Nil | Swift/Xcode

Would someone please explain to me why this pdf generator I'm attempting to use is always returning nil? I'm attempting to get a thumbnail to display in a UITableView alongside the filename of the PDF. Unfortunately, out of the four or so thumbnail generators I've tried, none of them have returned anything other than nil.
func uploadPDF() {
let types = UTType.types(tag: "pdf",
tagClass: UTTagClass.filenameExtension,
conformingTo: nil)
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
for url in urls {
let thumbnail = thumbnailFromPdf(withUrl: url, pageNumber: 0)
self.modelController.bidPDFUploadThumbnails.append(thumbnail!)
}
tableView.reloadData()
}
func thumbnailFromPdf(withUrl url:URL, pageNumber:Int, width: CGFloat = 240) -> UIImage? {
guard let pdf = CGPDFDocument(url as CFURL),
let page = pdf.page(at: pageNumber)
else {
return nil
}
var pageRect = page.getBoxRect(.mediaBox)
let pdfScale = width / pageRect.size.width
pageRect.size = CGSize(width: pageRect.size.width*pdfScale, height: pageRect.size.height*pdfScale)
pageRect.origin = .zero
UIGraphicsBeginImageContext(pageRect.size)
let context = UIGraphicsGetCurrentContext()!
// White BG
context.setFillColor(UIColor.white.cgColor)
context.fill(pageRect)
context.saveGState()
// Next 3 lines makes the rotations so that the page look in the right direction
context.translateBy(x: 0.0, y: pageRect.size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.concatenate(page.getDrawingTransform(.mediaBox, rect: pageRect, rotate: 0, preserveAspectRatio: true))
context.drawPDFPage(page)
context.restoreGState()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
Generator source: Thumbnail Generator
the pdf document starts from page 1 not 0 because its not an array.
so simple is
let thumbnail = thumbnailFromPdf(withUrl: url, pageNumber: 1)
you'll get it
rather than using page number you can direct access the thumbnail of by default first page as follow:
import PDFKit
func generatePdfThumbnail(of thumbnailSize: CGSize , for documentUrl: URL, atPage pageIndex: Int) -> UIImage? {
let pdfDocument = PDFDocument(url: documentUrl)
let pdfDocumentPage = pdfDocument?.page(at: pageIndex)
return pdfDocumentPage?.thumbnail(of: thumbnailSize, for: PDFDisplayBox.trimBox)
}

CGBitmapContext 2x as slow compared to CALayer's draw()

I have some code I can't change that expects to be able to draw at any time. It's the main() function in BackgroundThread below - pretend it can't be modified in any way. Running this will use 70-80% CPU.
If instead of running the thread I replicate what it is doing in View::draw() (i.e. draw 5000 white rectangles at random positions), this will use about 30% CPU.
Where's the difference coming from? Looking at Instruments, although the call stack is the same starting from CGContextFillRect, the View::draw() version only spends 16% of the time doing memset() whereas the threaded version spends 80% of the time.
The code below is the FAST version. Comment out the FAST lines and uncomment the SLOW lines to switch to the SLOW (threaded) version. Compile with swiftc test.swift -otest && ./test. I'm on macOS 10.13, integrated graphics, if that matters.
Is there anything I can do to make the threaded version as fast as the View::draw() version?
import Cocoa
let NSApp = NSApplication.shared,
vwaitSem = DispatchSemaphore(value: 0)
var
mainWindow: NSWindow?,
screen: CGContext?,
link: CVDisplayLink?
class View: NSView, CALayerDelegate {
var lastTime: CFTimeInterval = 0
override var acceptsFirstResponder: Bool {return true}
required init(coder aDecoder: NSCoder) {fatalError("This class does not support NSCoding")}
override func makeBackingLayer() -> CALayer {return CALayer()}
override init(frame: CGRect) {
super.init(frame: frame)
self.wantsLayer = true
self.layer?.contentsScale = 2.0
self.layer?.backgroundColor = CGColor(red:0, green:0, blue:0, alpha: 1)
self.layerContentsRedrawPolicy = NSView.LayerContentsRedrawPolicy.onSetNeedsDisplay // FAST
}
func draw(_ layer: CALayer, in ctx: CGContext) {
let now = CACurrentMediaTime(), timePassed = ((now-lastTime)*1000).rounded()
// NSLog("\(timePassed)")
lastTime = now
ctx.setFillColor(CGColor.white)
ctx.setStrokeColor(CGColor.white)
for _ in 0...5000 {
let rect = CGRect(x: CGFloat(arc4random_uniform(640)+1), y: CGFloat(arc4random_uniform(480)+1), width:6, height:6)
ctx.setFillColor(CGColor.white)
ctx.fill(rect)
}
}
}
func displayLinkOutputCallback(_ displayLink: CVDisplayLink, _ nowPtr: UnsafePointer<CVTimeStamp>,
_ outputTimePtr: UnsafePointer<CVTimeStamp>, _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer<CVOptionFlags>,
_ displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn {
DispatchQueue.main.async {
// mainWindow!.contentView!.layer!.contents = screen!.makeImage() // SLOW
mainWindow!.contentView!.display() // FAST
vwaitSem.signal()
}
return kCVReturnSuccess
}
class BackgroundThread: Thread {
var lastTime: CFTimeInterval = 0
override func main() {
while true {
let now = CACurrentMediaTime(), timePassed = ((now-lastTime)*1000).rounded()
// NSLog("\(timePassed)")
lastTime = now
screen?.clear(CGRect(x:0, y:0, width:640*2, height:480*2))
for _ in 0...5000 {
screen?.setFillColor(CGColor.white)
screen?.setStrokeColor(CGColor.white)
screen?.fill(CGRect(x: CGFloat(arc4random_uniform(640*2)+1), y: CGFloat(arc4random_uniform(480*2)+1), width: 6*2, height: 6*2))
}
vwaitSem.wait()
}
}
}
let width = 640, height = 480,
appMenuItem = NSMenuItem(),
quitMenuItem = NSMenuItem(title:"Quit",
action:#selector(NSApplication.terminate), keyEquivalent:"q"),
window = NSWindow(contentRect:NSMakeRect(0,0, CGFloat(width), CGFloat(height)),
styleMask:[.closable,.titled], backing:.buffered, defer:false),
colorProfile = ColorSyncProfileCreateWithDisplayID(0),
colorSpace = CGColorSpace(platformColorSpaceRef: colorProfile!.toOpaque()),
screen_ = CGContext(data: nil, width: Int(width)*2, height:Int(height)*2, bitsPerComponent:8, bytesPerRow: 0,
space: colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue),
backgroundThread = BackgroundThread()
NSApp.setActivationPolicy(NSApplication.ActivationPolicy.regular)
NSApp.mainMenu = NSMenu()
NSApp.mainMenu?.addItem(appMenuItem)
appMenuItem.submenu = NSMenu()
appMenuItem.submenu?.addItem(quitMenuItem)
window.cascadeTopLeft(from:NSMakePoint(20,20))
window.makeKeyAndOrderFront(nil)
window.contentView = View()
window.makeFirstResponder(window.contentView)
NSApp.activate(ignoringOtherApps:true)
mainWindow = window
screen = screen_
CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &link)
CVDisplayLinkSetOutputCallback(link!, displayLinkOutputCallback, UnsafeMutableRawPointer(Unmanaged.passUnretained(window).toOpaque()))
CVDisplayLinkStart(link!)
// backgroundThread.start() // SLOW
NSApp.run()
I misread the note in the documentation for makeImage() and thought it would not copy the data unless it really had to. Well, Instruments shows it does copy the data. Every single frame.
So I switched to Metal and now I can draw from the background thread with the same performance/CPU usage as with CGContext alone, with no copies as far as I can tell.
Here's some working code:
import Cocoa
import MetalKit
class View: MTKView {
var screen: CGContext?
var commandQueue: MTLCommandQueue?
var buffer: MTLBuffer?
var texture: MTLTexture?
var vwaitSem = DispatchSemaphore(value: 0)
var backgroundThread: Thread?
var allocationSize = 0
func alignUp(size: Int, align: Int) -> Int {return (size+(align-1)) & ~(align-1)}
override var acceptsFirstResponder: Bool {return true}
required init(coder aDecoder: NSCoder) {fatalError("This class does not support NSCoding")}
init() {super.init(frame: CGRect(x:0, y:0, width:0, height: 0), device: MTLCreateSystemDefaultDevice())}
override func viewDidMoveToWindow() {
layer?.contentsScale = NSScreen.main!.backingScaleFactor
let metalLayer = layer as! CAMetalLayer
let pixelRowAlignment = metalLayer.device!.minimumLinearTextureAlignment(for: metalLayer.pixelFormat)
let bytesPerRow = alignUp(size: Int(layer!.frame.width)*Int(layer!.contentsScale)*4, align: pixelRowAlignment)
let pagesize = Int(getpagesize())
var data: UnsafeMutableRawPointer? = nil
allocationSize = alignUp(size: bytesPerRow*Int(layer!.frame.height)*Int(layer!.contentsScale), align: pagesize)
posix_memalign(&data, pagesize, allocationSize)
let colorProfile = ColorSyncProfileCreateWithDisplayID(0),
colorSpace = CGColorSpace(platformColorSpaceRef: colorProfile!.toOpaque()),
screen_ = CGContext(data: data,
width: Int(layer!.frame.width)*Int(layer!.contentsScale),
height: Int(layer!.frame.height)*Int(layer!.contentsScale),
bitsPerComponent:8, bytesPerRow: bytesPerRow,
space: colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!,
buffer_ = metalLayer.device!.makeBuffer(bytesNoCopy: data!, length: allocationSize, options: .storageModeManaged,
deallocator: { pointer, length in free(self.screen!.data!) })!,
textureDescriptor = MTLTextureDescriptor()
textureDescriptor.pixelFormat = metalLayer.pixelFormat
textureDescriptor.width = screen_.width
textureDescriptor.height = screen_.height
textureDescriptor.storageMode = buffer_.storageMode
textureDescriptor.usage = MTLTextureUsage(rawValue: MTLTextureUsage.shaderRead.rawValue)
texture = buffer_.makeTexture(descriptor: textureDescriptor, offset: 0, bytesPerRow: screen_.bytesPerRow)
commandQueue = device?.makeCommandQueue()
screen = screen_
buffer = buffer_
backgroundThread = BackgroundThread(screen: screen!, vwaitSem: vwaitSem)
backgroundThread!.start()
}
override func draw(_ dirtyRect: NSRect) {
if let drawable = currentDrawable {
buffer!.didModifyRange(0..<allocationSize)
texture!.replace(region: MTLRegionMake2D(0,0, screen!.width, screen!.height),
mipmapLevel:0, slice:0, withBytes: screen!.data!, bytesPerRow: screen!.bytesPerRow, bytesPerImage: 0)
let commandBuffer = commandQueue!.makeCommandBuffer()!
let blitPass = commandBuffer.makeBlitCommandEncoder()!
blitPass.copy(from: texture!, sourceSlice:0, sourceLevel:0, sourceOrigin: MTLOrigin(x:0,y:0,z:0),
sourceSize: MTLSize(width:screen!.width, height:screen!.height, depth: 1),
to: drawable.texture, destinationSlice:0, destinationLevel:0, destinationOrigin: MTLOrigin(x:0,y:0,z:0))
blitPass.endEncoding()
if let renderPass = currentRenderPassDescriptor {
renderPass.colorAttachments[0].texture = drawable.texture
renderPass.colorAttachments[0].loadAction = .load
commandBuffer.makeRenderCommandEncoder(descriptor: renderPass)!.endEncoding()
commandBuffer.addCompletedHandler {cb in self.vwaitSem.signal()}
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
}
}
class BackgroundThread: Thread {
var screen: CGContext
var vwaitSem: DispatchSemaphore
var x = 0
init(screen:CGContext, vwaitSem:DispatchSemaphore) {
self.screen = screen
self.vwaitSem = vwaitSem
}
override func main() {
while true {
// screen.clear(CGRect(x:0,y:0, width:screen.width, height:screen.height))
// screen.setFillColor(CGColor.white)
// screen.fill(CGRect(x:x, y:0, width:100, height:100))
// x += 1
screen.clear(CGRect(x:0,y:0, width:screen.width, height:screen.height))
screen.setFillColor(CGColor.white)
let screenWidth = UInt32(screen.width), screenHeight = UInt32(screen.height)
for _ in 0...5000 {
let rect = CGRect(x: CGFloat(arc4random_uniform(screenWidth+1)),
y: CGFloat(arc4random_uniform(screenHeight+1)), width:6, height:6)
screen.fill(rect)
}
vwaitSem.wait()
}
}
}
let width = 640, height = 480,
appMenuItem = NSMenuItem(),
quitMenuItem = NSMenuItem(title:"Quit",
action:#selector(NSApplication.terminate), keyEquivalent:"q"),
window = NSWindow(contentRect:NSMakeRect(0,0, CGFloat(width), CGFloat(height)),
styleMask:[.closable,.titled], backing:.buffered, defer:false)
NSApp.setActivationPolicy(NSApplication.ActivationPolicy.regular)
NSApp.mainMenu = NSMenu()
NSApp.mainMenu?.addItem(appMenuItem)
appMenuItem.submenu = NSMenu()
appMenuItem.submenu?.addItem(quitMenuItem)
window.cascadeTopLeft(from:NSMakePoint(20,20))
window.makeKeyAndOrderFront(nil)
window.contentView = View()
window.makeFirstResponder(window.contentView)
NSApp.activate(ignoringOtherApps:true)
NSApp.run()

How to change UISlider Thumb Appearance when Touch Ends

I am changing the color of a UISlider by calling .thumbTintColor
#IBAction func slider1Master(sender: AnyObject) {
slider1.thumbTintColor = UIColor.orangeColor()}
It works, but I want the color to change back to it's original state when the touch ends (user lifts finger).
Does anyone have any suggestions? Thank you.
You can use "setThumbImage" instead.
Then you have the option of setting an image for a specific state of action.
For the image, just create a rounder image with the color you desire.
//Creating an Image with rounded corners:
extension UIImage {
class func createThumbImage(size: CGFloat, color: UIColor) -> UIImage {
let layerFrame = CGRectMake(0, 0, size, size)
let shapeLayer = CAShapeLayer()
shapeLayer.path = CGPathCreateWithEllipseInRect(layerFrame.insetBy(dx: 1, dy: 1), nil)
shapeLayer.fillColor = color.CGColor
shapeLayer.strokeColor = color.colorWithAlphaComponent(0.65).CGColor
let layer = CALayer.init()
layer.frame = layerFrame
layer.addSublayer(shapeLayer)
return self.imageFromLayer(layer)
}
class func imageFromLayer(layer: CALayer) -> UIImage {
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, UIScreen.mainScreen().scale)
layer.renderInContext(UIGraphicsGetCurrentContext()!)
let outputImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return outputImage
}
}
//Setting the image for a selected state of UISlider:
func setupSlider() {
let size:CGFloat = 12
let highlightedStateOrangeColorImage = UIImage.createThumbImage(size, color: UIColor.orangeColor())
let defaultStateBlueColorImage = UIImage.createThumbImage(size, color: UIColor.blueColor())
self.slider.setThumbImage(highlightedStateOrangeColorImage, forState: UIControlState.Highlighted)
self.slider.setThumbImage(defaultStateBlueColorImage, forState: UIControlState.Normal)
}
You can safely accept McMatan’s solution as your answer. It is good for several reasons.
the colour changes back to its original state when the user lifts a finger, as you requested
using the extension to create a shape does away with image assets for the UISlider
it could also be used to draw images for circular UIButtons and circular UIViews.
it can also create a shape with colours matching other UISlider design elements (if so desired).
The code below does just that. I used McMatan’s UIImage extension with no changes other than translation to Swift 3. But I have split his function setUpSlider() into two, one for drawing the circular image in its default state, the other for drawing it in its highlighted state.
By accepting McMatan’s solution, you will encourage those who contribute their experience and free time to continue making this forum worthwhile for the rest of us. So please do.
class ViewController: UIViewController {
var slider: UISlider!
let defaultColour = UIColor.blue
let highlightedColour = UIColor.orange
let thumbSize = 20
override func viewDidLoad() {
super.viewDidLoad()
slider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 23))
slider.minimumValue = 0
slider.minimumTrackTintColor = defaultColour
slider.maximumValue = 100
slider.maximumTrackTintColor = highlightedColour
slider.center = view.center
slider.value = slider.maximumValue / 2.0
let highlightedImage = makeHighlightedImage()
let defaultImage = makeDefaultImage()
slider.setThumbImage(highlightedImage, for: UIControlState.highlighted)
slider.setThumbImage(defaultImage, for: UIControlState.normal)
slider.isContinuous = false
view.addSubview(slider)
slider.addTarget(self, action: #selector(sliderValueChanged), for: UIControlEvents.valueChanged)
}
func sliderValueChanged(sender: UISlider){
print(sender.value)
}
func makeHighlightedImage() -> (UIImage) {
let size = CGFloat(thumbSize)
let highlightedStateImage = UIImage.createThumbImage(size: size, color: highlightedColour)
return (highlightedStateImage)
}
func makeDefaultImage() -> (UIImage) {
let size = CGFloat(thumbSize)
let defaultStateImage = UIImage.createThumbImage(size: size, color: defaultColour)
return (defaultStateImage)
}
}
Extension translated to Swift 3
import UIKit
extension UIImage {
class func createThumbImage(size: CGFloat, color: UIColor) -> UIImage {
let layerFrame = CGRect(x: 0, y: 0, width: size, height: size)
let shapeLayer = CAShapeLayer()
shapeLayer.path = CGPath(ellipseIn: layerFrame.insetBy(dx: 1, dy: 1), transform: nil)
shapeLayer.fillColor = color.cgColor
shapeLayer.strokeColor = color.withAlphaComponent(0.65).cgColor
let layer = CALayer.init()
layer.frame = layerFrame
layer.addSublayer(shapeLayer)
return self.imageFromLayer(layer: layer)
}
class func imageFromLayer(layer: CALayer) -> UIImage {
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, UIScreen.main.scale)
layer.render(in: UIGraphicsGetCurrentContext()!)
let outputImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return outputImage!
}
}
I came up with an answer similar to MCMatan's but without the need for a UIImage extension:
func setThumbnailImage(for slider: UISlider, thumbnailHeight: CGFloat, thumbnailColor: UIColor) {
let cornerRadius: CGFloat = 25
let rect = CGRect(x: 0, y: 0, width: thumbnailHeight, height: thumbnailHeight)
let size = CGSize(width: thumbnailHeight, height: thumbnailHeight)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
// this is what makes it round
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
thumbnailColor.setFill()
UIRectFill(rect)
if let newImage = UIGraphicsGetImageFromCurrentImageContext() {
slider.setThumbImage(nil, for: .normal)
slider.setThumbImage(newImage, for: .normal)
}
UIGraphicsEndImageContext()
}
To use:
override func viewDidLoad() {
super.viewDidLoad()
setThumbnailImage(for: yourSlider, thumbnailHeight: 20.0, thumbnailColor: UIColor.red)
}
or
func someActionJustFinishedNowUpdateThumbnail() {
setThumbnailImage(for: yourSlider, thumbnailHeight: 40.0, thumbnailColor: UIColor.blue)
}
or
func setThumbnailToSliderHeight() {
let sliderHeight = yourSlider.frame.size.height
setThumbnailImage(for: yourSlider, thumbnailHeight: sliderHeight, thumbnailColor: UIColor.purple)
}

Swift extension example

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