How to change UISlider Thumb Appearance when Touch Ends - swift

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

Related

How to dispose from UIView subviews on one ViewController when navigating away from it

I'm starting to learn Swift and decided to build an app but without storyboard.
My SceneDelegate scene function instantiates a TabBarController
window = UIWindow(frame: UIScreen.main.bounds)
let tb = TabBarController()
window?.rootViewController = tb
window?.makeKeyAndVisible()
window?.windowScene = windowSceme
I have a TabBarController that extends from UITabBarController which pretty much styles the tab bar and sets it all up
For each item of the Tabbar I have a ViewController. For the purpose of this question I'm going to specify the first "Home".
In Home, which extends ViewController, I have the following
let homePageView = HomePageView()
override func viewWillLayoutSubviews() {
navigationController?.isNavigationBarHidden = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
homePageView.setupHomePage()
view.addSubview(homePageView)
}
override func viewWillDisappear(_ animated: Bool) {
homePageView.dispose()
}
The Controller is pretty much only in charge of calling whatever is going to be displayed on the screen within HomePageView(), the main component. This last, holds two more views. One carrousel HomePageCarrousel() and one header HomePageSocialUp(). This view instantiates the two latter referred to and sets the layout programmatically and adds them as subviews. It also includes a dispose function that sets the instantiated classes to nil such as --> Instantiate - homePageCarrousel = HomePageCarrousel() and the dispose function has homePageCarrousel = nil
That works perfectly fine but when I navigate away from the current view controller via the tab bar and navigate back to it, now I have two instances of HomePageCarrousel() and HomePageSocialUp() within HomeView
I'm probably holding a strong reference somewhere but I can't figure out when. Could someone point me to where should I look to debug it or what might be that is creating the issue.
I'm also providing the code for the two views duplicated in case the issue is there
HomePageSocialUp
class HomePageSocialUp: UIView {
let logo = UIImage(named: "LogoSmiles")
let socialImages: [UIImage] = [UIImage(named: "tripadvisor")!, UIImage(named: "instagram")!, UIImage(named: "facebook")!, UIImage(named: "linkedin")!]
func setupHeaderCircle() {
guard let insetTop = UIApplication.shared.keyWindow?.safeAreaInsets.top else {return}
let circlePath = UIBezierPath(arcCenter: CGPoint(x: UIScreen.main.bounds.width / 2, y: insetTop + 60), radius: CGFloat(90), startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
layer.addSublayer(shapeLayer)
}
func setupSocialHeader() {
setupHeaderCircle()
layer.masksToBounds = true;
backgroundColor = UIColor.TintColor
layer.shadowColor = UIColor.lightGray.cgColor
layer.shadowOpacity = 0.8
layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
layer.shadowRadius = 3.0
layer.masksToBounds = false
frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 280)
let imageView = UIImageView()
guard let logo = logo else {return}
guard let insetTop = UIApplication.shared.keyWindow?.safeAreaInsets.top else {return}
imageView.frame = CGRect(x: CGFloat(Int((UIScreen.main.bounds.width - (logo.size.width)))) / 2, y: 120 - (logo.size.width / 2), width: logo.size.width, height: logo.size.height)
imageView.image = logo
addSubview(imageView)
}
}
HomePageCarrousel
class HomePageCarrousel: UIScrollView {
var images: [UIImage]?
var originX = 0
var numberOfIterations = 0
var timer: Timer?
func setupCarrousel() {
let x = (Int(UIScreen.main.bounds.width) - (Int(UIScreen.main.bounds.width) - 60)) / 2
frame = CGRect(x: x, y: (Int(frame.origin.y) - 350) / 2, width: Int(UIScreen.main.bounds.width) - 60, height: 350)
let newImage = textToImage(drawText: "Creating Smiles in unique places.", frame: frame, inImage: UIImage(named: "smiles1")!, atPoint: CGPoint(x: frame.origin.x + 20, y: frame.height - 20))
images = [newImage, UIImage(named: "smiles2")!]
guard timer == nil else { return }
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(startScrolling), userInfo: nil, repeats: true)
guard let imageCount = images?.count, let images = images else { return }
contentSize = CGSize(width: frame.width * CGFloat(images.count), height: frame.height)
for image in 0..<imageCount {
let imageView = UIImageView()
imageView.image = images[image]
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
let xPosition = frame.width * CGFloat(image)
imageView.frame = CGRect(x: xPosition, y: 0, width: frame.width, height: frame.height)
addSubview(imageView)
}
timer?.fire()
}
func textToImage(drawText text: String, frame: CGRect, inImage image: UIImage, atPoint point: CGPoint) -> UIImage {
let textColor = UIColor.white
let textFont = UIFont(name: "Helvetica Bold" , size: 12)!
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(image.size, false, scale)
let textFontAttributes = [
NSAttributedString.Key.font: textFont,
NSAttributedString.Key.foregroundColor: textColor,
] as [NSAttributedString.Key : Any]
image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))
text.draw(in: frame, withAttributes: textFontAttributes)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
#objc func startScrolling() {
print(originX)
guard let images = images else { return }
if originX == images.count {
originX = 0
numberOfIterations += 1
}
if numberOfIterations > 2 {
timer?.invalidate()
timer = nil
numberOfIterations = 0
}
let x = CGFloat(originX) * frame.size.width
setContentOffset(CGPoint(x: x, y: 0), animated: true)
originX += 1
}
}
Thanks upfront
Did you tried to remove the subviews, something like
homePageView.subviews.forEach { (view) in
//taking appropriate action to whichever view you want
view.removeFromSuperview()//for removing views
}

UIProgressView setProgress issue

I've encountered an issue using a UIProgressView where low values (1% - about 10%) look off. You can see with the example above that 97% looks accurate while 2% does not.
Here's the code for setting colors:
self.progressView.trackTintColor = UIColor.green.withAlphaComponent(0.3)
self.progressView.tintColor = UIColor.green.withAlphaComponent(1.0)
But, if I comment out the trackTintColor or the tintColor, then the 2% looks correct. Why when using these together does it cause this issue? Just an Xcode bug? Has anyone resolved this before?
I've experienced the same issue in my project. For me it's fixed by using progressTintColor instead of tintColor.
progressView.progressTintColor = UIColor.green.withAlphaComponent(1.0)
progressView.trackTintColor = UIColor.green.withAlphaComponent(0.3)
you need to create color image
SWIFT 3 Example:
class ViewController: UIViewController {
#IBOutlet weak var progressView: UIProgressView!
#IBAction func lessButton(_ sender: UIButton) {
let percentage = 20
let invertedValue = Float(100 - percentage) / 100
progressView.setProgress(invertedValue, animated: true)
}
#IBAction func moreButton(_ sender: UIButton) {
let percentage = 80
let invertedValue = Float(100 - percentage) / 100
progressView.setProgress(invertedValue, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
//create gradient view the size of the progress view
let gradientView = GradientView(frame: progressView.bounds)
//convert gradient view to image , flip horizontally and assign as the track image
progressView.trackImage = UIImage(view: gradientView).withHorizontallyFlippedOrientation()
//invert the progress view
progressView.transform = CGAffineTransform(scaleX: -1.0, y: -1.0)
progressView.progressTintColor = UIColor.black
progressView.progress = 1
}
}
extension UIImage{
convenience init(view: UIView) {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: (image?.cgImage)!)
}
}
#IBDesignable
class GradientView: UIView {
private var gradientLayer = CAGradientLayer()
private var vertical: Bool = false
override func draw(_ rect: CGRect) {
super.draw(rect)
// Drawing code
//fill view with gradient layer
gradientLayer.frame = self.bounds
//style and insert layer if not already inserted
if gradientLayer.superlayer == nil {
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = vertical ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0)
gradientLayer.colors = [UIColor.green.cgColor, UIColor.red.cgColor]
gradientLayer.locations = [0.0, 1.0]
self.layer.insertSublayer(gradientLayer, at: 0)
}
}
}

Swift: Image losing aspect ratio when drawing starts

I am attempting to make an image editing VC for my app and encountered the above issue. Whenever I start drawing on my image, the image would warp and then lose the aspect ratio.
Gif is:
My full code is:
class DrawImageController: UIViewController {
var canvasImageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.backgroundColor = .yellow
iv.contentMode = .scaleAspectFit
return iv
}()
var lastTouch = CGPoint.zero
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
view.backgroundColor = .black
view.addSubview(canvasImageView)
canvasImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
canvasImageView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
canvasImageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
canvasImageView.heightAnchor.constraint(equalToConstant: 300).isActive = true
canvasImageView.image = UIImage(named: "testImage")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let firstTouch = touches.first {
lastTouch = firstTouch.location(in: canvasImageView)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let firstTouch = touches.first {
let touchLocation = firstTouch.location(in: canvasImageView)
drawLine(from: lastTouch, to: touchLocation)
lastTouch = touchLocation
}
}
func drawLine(from: CGPoint, to: CGPoint) {
UIGraphicsBeginImageContext(canvasImageView.frame.size)
if let context = UIGraphicsGetCurrentContext() {
canvasImageView.image?.draw(in: CGRect(x: 0, y: 0, width: canvasImageView.frame.size.width, height: canvasImageView.frame.size.height))
context.move(to: from)
context.addLine(to: to)
context.setLineCap(.round)
context.setLineWidth(5.0)
context.setStrokeColor(UIColor.blue.cgColor)
context.strokePath()
let image = UIGraphicsGetImageFromCurrentImageContext()
canvasImageView.image = image
UIGraphicsEndImageContext()
}
}
}
I adapted my draw method from various tutorials on YouTube, GitHub and SO. Where have I gone wrong?
Solved
With the advice from #Sweeper, I have modified my code in setupViews() and drawLine to account for aspect ratio of the image and imageView.
func setupViews() {
view.backgroundColor = .black
view.addSubview(canvasImageView)
canvasImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
canvasImageView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
canvasImageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
let aspectRatio = getImageAspectRatio(image: UIImage(named: "testImage")!)
let screenWidth = UIScreen.main.bounds.width
let height = CGFloat(1.0) / aspectRatio * screenWidth
canvasImageView.heightAnchor.constraint(equalToConstant: height).isActive = true
canvasImageView.image = UIImage(named: "testImage")
}
func drawLine(from: CGPoint, to: CGPoint) {
UIGraphicsBeginImageContext(canvasImageView.frame.size)
guard let context = UIGraphicsGetCurrentContext() else {return}
if let canvasImage = canvasImageView.image {
let imageViewAspectRatio = getAspectRatio(frame: canvasImageView.frame)
let imageAspectRatio = getImageAspectRatio(image: canvasImage)
if imageViewAspectRatio > imageAspectRatio {
canvasImageView.image?.draw(in: CGRect(x: 0, y: 0, width: imageAspectRatio * canvasImageView.frame.size.height, height: canvasImageView.frame.size.height))
} else if imageViewAspectRatio < imageAspectRatio {
canvasImageView.image?.draw(in: CGRect(x: 0, y: 0, width: canvasImageView.frame.size.width, height: CGFloat(1.0) / imageAspectRatio * canvasImageView.frame.size.width))
} else {
canvasImageView.image?.draw(in: CGRect(x: 0, y: 0, width: canvasImageView.frame.size.width, height: canvasImageView.frame.size.height))
}
context.move(to: from)
context.addLine(to: to)
context.setLineCap(.round)
context.setLineWidth(5.0)
context.setStrokeColor(UIColor.blue.cgColor)
context.strokePath()
let image = UIGraphicsGetImageFromCurrentImageContext()
canvasImageView.image = image
UIGraphicsEndImageContext()
}
}
The problem is here:
canvasImageView.image?.draw(in: CGRect(x: 0, y: 0, width: canvasImageView.frame.size.width, height: canvasImageView.frame.size.height))
You are drawing the image using the image view's frame. This stretches the image.
You need to draw the image as if contentMode is .scaleAspectFit.
To do this, first determine the image's aspect ratio (W:H). You can do this by access the size property of UIImage. Compare this ratio to the aspect ratio of the image view.
If the image's ratio is smaller than the view's, then that means the height at which you draw the image can be the same as the image view height, and the image width can be calculated using the aspect ratio of the image.
If the image's ratio is larger than the view's, then that means the width at which you draw the image can be the same as the image view width, and the image height can be calculated,

How to crop image swift

in my project i have a UIImageView with square format for the image of the user profile, but when i add an image for example from the library, it appears shapeless. I have finded a code to crop the images to square format but i don't understand where to insert it in my code to crop the selected image from library or a new photo. Can you help me?
Here the code:
ImageUtil.swift
import UIKit
class ImageUtil: NSObject {
func cropToSquare(image originalImage: UIImage) -> UIImage {
// Create a copy of the image without the imageOrientation property so it is in its native orientation (landscape)
let contextImage: UIImage = UIImage(CGImage: originalImage.CGImage)!
// Get the size of the contextImage
let contextSize: CGSize = contextImage.size
let posX: CGFloat
let posY: CGFloat
let width: CGFloat
let height: CGFloat
// Check to see which length is the longest and create the offset based on that length, then set the width and height of our rect
if contextSize.width > contextSize.height {
posX = ((contextSize.width - contextSize.height) / 2)
posY = 0
width = contextSize.height
height = contextSize.height
} else {
posX = 0
posY = ((contextSize.height - contextSize.width) / 2)
width = contextSize.width
height = contextSize.width
}
let rect: CGRect = CGRectMake(posX, posY, width, height)
// Create bitmap image from context using the rect
let imageRef: CGImageRef = CGImageCreateWithImageInRect(contextImage.CGImage, rect)
// Create a new image based on the imageRef and rotate back to the original orientation
let image: UIImage = UIImage(CGImage: imageRef, scale: originalImage.scale, orientation: originalImage.imageOrientation)!
return image
}
}
AddController.swift
import UIKit
class AddController: UIViewController, UITextFieldDelegate, CameraManagerDelegate {
#IBOutlet var immagine: UIImageView!
#IBOutlet var fieldNome: UITextField!
var immagineSelezionata : UIImage?
override func viewDidLoad() {
super.viewDidLoad()
navigationController!.navigationBar.barStyle = UIBarStyle.BlackTranslucent
navigationController!.navigationBar.tintColor = UIColor.whiteColor()
navigationController!.navigationBar.barTintColor = UIColor(red: 60/255.0, green: 172/255.0, blue: 183/255.0, alpha: 1.0)
//navigationItem.titleView = UIImageView(image: UIImage(named: "logo"))
//tableView.backgroundColor = UIColor(red: 60/255.0, green: 172/255.0, blue: 183/255.0, alpha: 1.0)
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: false)
let singleTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "selezionaFoto:")
singleTap.numberOfTapsRequired = 1
singleTap.numberOfTouchesRequired = 1
self.immagine.addGestureRecognizer(singleTap)
self.immagine.userInteractionEnabled = true
fieldNome.delegate = self
CameraManager.sharedInstance.delegate = self
var keyboardToolbar = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 44))
keyboardToolbar.barStyle = UIBarStyle.BlackTranslucent
keyboardToolbar.backgroundColor = UIColor(red: 60/255.0, green: 172/255.0, blue: 183/255.0, alpha: 1.0)
keyboardToolbar.tintColor = UIColor.whiteColor()
var flex = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
var save = UIBarButtonItem(title: NSLocalizedString("Done", comment: ""),
style: UIBarButtonItemStyle.Done,
target: fieldNome,
action: "resignFirstResponder")
keyboardToolbar.setItems([flex, save], animated: false)
fieldNome.inputAccessoryView = keyboardToolbar
}
func myUIImageViewTapped(recognizer: UITapGestureRecognizer) {
if(recognizer.state == UIGestureRecognizerState.Ended){
println("myUIImageView has been tapped by the user.")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func annulla(sender: UIBarButtonItem) {
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func salva(sender: UIBarButtonItem) {
if fieldNome.text.isEmpty
{
return
}
var profilo = ProfiloModel(nomeIn: fieldNome.text,
immagineIn: UIImage(named:"icon-profile")!)
if let img = immagineSelezionata {
profilo.immagine = img
}
//DataManager.sharedInstance.storage.insert(profilo, atIndex: 0)
DataManager.sharedInstance.storage.append(profilo)
DataManager.sharedInstance.salvaArray()
DataManager.sharedInstance.master.tableView.reloadData()
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func selezionaFoto(sender: UITapGestureRecognizer) {
fieldNome.resignFirstResponder()
func selezionaLibreria(action : UIAlertAction!) {
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.Default, animated: true)
CameraManager.sharedInstance.newImageFromLibraryForController(self, editing: false)
}
func scattaFoto(action : UIAlertAction!) {
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.Default, animated: true)
var circle = UIImageView(frame: UIScreen.mainScreen().bounds)
circle.image = UIImage(named: "overlay")
CameraManager.sharedInstance.newImageShootForController(self, editing: false, overlay:circle)
}
var myActionSheet = UIAlertController(title: NSLocalizedString("ACTION_IMAGE_TITLE", comment: ""),
message: NSLocalizedString("ACTION_IMAGE_TEXT", comment: ""),
preferredStyle: UIAlertControllerStyle.ActionSheet)
myActionSheet.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_LIBRARY", comment: ""),
style: UIAlertActionStyle.Default,
handler: selezionaLibreria))
myActionSheet.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_SHOOT", comment: ""),
style: UIAlertActionStyle.Default,
handler: scattaFoto))
myActionSheet.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_CANCEL", comment: ""),
style: UIAlertActionStyle.Cancel,
handler: nil))
self.presentViewController(myActionSheet, animated: true, completion: nil)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder() // chiudere la tastiera nei campi di testo
return true
}
func incomingImage(image: UIImage) {
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true)
immagine.image = image
immagineSelezionata = image
}
func cancelImageSelection() {
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true)
}
}
I Found a link can help you
CGBitmapContextCreate & CGContextDrawImage
Core Graphics / Quartz 2D offers a lower-level set of APIs that allow for more advanced configuration. Given a CGImage, a temporary bitmap context is used to render the scaled image, using CGBitmapContextCreate() and
CGBitmapContextCreateImage():
let image = UIImage(contentsOfFile: self.URL.absoluteString!).CGImage
let width = CGImageGetWidth(image) / 2.0
let height = CGImageGetHeight(image) / 2.0
let bitsPerComponent = CGImageGetBitsPerComponent(image)
let bytesPerRow = CGImageGetBytesPerRow(image)
let colorSpace = CGImageGetColorSpace(image)
let bitmapInfo = CGImageGetBitmapInfo(image)
let context = CGBitmapContextCreate(nil, width, height,
bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo)
CGContextSetInterpolationQuality(context, kCGInterpolationHigh)
CGContextDrawImage(context, CGRect(origin: CGPointZero, size: CGSize(width: CGFloat(width), height: CGFloat(height))), image)
let scaledImage = UIImage(CGImage: CGBitmapContextCreateImage(context))
CGBitmapContextCreate takes several arguments to construct a context with desired dimensions and amount of memory for each channel within a given colorspace. In the example, these values are fetched from the CGImage. Next, CGContextSetInterpolationQuality allows for the context to interpolate pixels at various levels of fidelity. In this case, kCGInterpolationHigh is passed for best results. CGContextDrawImage allows for the image to be drawn at a given size and position, allowing for the image to be cropped on a particular edge or to fit a set of image features, such as faces. Finally, CGBitmapContextCreateImage creates a CGImage from the context.
Font Image Resizing Techiniques
See Image-Cropper.
Excerpt:
func btnCropAndSaveClicked(sender: UIButton!) {
print("save btn clicked")
let scale = 1 / scrollView.zoomScale
let visibleRect = CGRect(
x: (scrollView.contentOffset.x + scrollView.contentInset.left) * scale,
y: (scrollView.contentOffset.y + scrollView.contentInset.top) * scale,
width: CROP_WINDOW_WIDTH * scale,
height: CROP_WINDOW_HEIGHT * scale)
let imageRef: CGImageRef? = CGImageCreateWithImageInRect(processedImage.CGImage, visibleRect)!
let croppedImage:UIImage = UIImage(CGImage: imageRef!)
UIImageWriteToSavedPhotosAlbum(croppedImage, nil, nil, nil)
showResultMessage()
}
That code allows you to zoom in and out the selected picture, move it around and crop the desired area of that picture. It's simple to implement, everything is done on the ViewController. Hope this solves your problem.

Swift add badge to navigation barButtonItem and UIButton

I am trying to display badge on my notification button, in app as displayed on AppIcon.
So far whatever i have researched is related to Obj. C, but nothing that specifically discussed way to implement that solution into Swift,
Please help to find a solution to add a custom class / code to achieve Badge on UiBarbutton and UiButton.
Researched so far:
https://github.com/Marxon13/M13BadgeView
along with MKBadge class etc.
There is a more elegant solution with an extension for UIButtonItem
extension CAShapeLayer {
func drawCircleAtLocation(location: CGPoint, withRadius radius: CGFloat, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
let origin = CGPoint(x: location.x - radius, y: location.y - radius)
path = UIBezierPath(ovalIn: CGRect(origin: origin, size: CGSize(width: radius * 2, height: radius * 2))).cgPath
}
}
private var handle: UInt8 = 0
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
func addBadge(number: Int, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true) {
guard let view = self.value(forKey: "view") as? UIView else { return }
badgeLayer?.removeFromSuperlayer()
// Initialize Badge
let badge = CAShapeLayer()
let radius = CGFloat(7)
let location = CGPoint(x: view.frame.width - (radius + offset.x), y: (radius + offset.y))
badge.drawCircleAtLocation(location: location, withRadius: radius, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// Initialiaze Badge's label
let label = CATextLayer()
label.string = "\(number)"
label.alignmentMode = CATextLayerAlignmentMode.center
label.fontSize = 11
label.frame = CGRect(origin: CGPoint(x: location.x - 4, y: offset.y), size: CGSize(width: 8, height: 16))
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// Save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
func updateBadge(number: Int) {
if let text = badgeLayer?.sublayers?.filter({ $0 is CATextLayer }).first as? CATextLayer {
text.string = "\(number)"
}
}
func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
This great code was created by Stefano Vettor and you can find all the details at:
https://gist.github.com/freedom27/c709923b163e26405f62b799437243f4
Working Solution :
Step 1:
Firstly create new swift file which is a subclass to UIButton as follows:
import UIKit
class BadgeButton: UIButton {
var badgeLabel = UILabel()
var badge: String? {
didSet {
addbadgetobutton(badge: badge)
}
}
public var badgeBackgroundColor = UIColor.red {
didSet {
badgeLabel.backgroundColor = badgeBackgroundColor
}
}
public var badgeTextColor = UIColor.white {
didSet {
badgeLabel.textColor = badgeTextColor
}
}
public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
didSet {
badgeLabel.font = badgeFont
}
}
public var badgeEdgeInsets: UIEdgeInsets? {
didSet {
addbadgetobutton(badge: badge)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addbadgetobutton(badge: nil)
}
func addbadgetobutton(badge: String?) {
badgeLabel.text = badge
badgeLabel.textColor = badgeTextColor
badgeLabel.backgroundColor = badgeBackgroundColor
badgeLabel.font = badgeFont
badgeLabel.sizeToFit()
badgeLabel.textAlignment = .center
let badgeSize = badgeLabel.frame.size
let height = max(18, Double(badgeSize.height) + 5.0)
let width = max(height, Double(badgeSize.width) + 10.0)
var vertical: Double?, horizontal: Double?
if let badgeInset = self.badgeEdgeInsets {
vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
horizontal = Double(badgeInset.left) - Double(badgeInset.right)
let x = (Double(bounds.size.width) - 10 + horizontal!)
let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
} else {
let x = self.frame.width - CGFloat((width / 2.0))
let y = CGFloat(-(height / 2.0))
badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
}
badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
badgeLabel.layer.masksToBounds = true
addSubview(badgeLabel)
badgeLabel.isHidden = badge != nil ? false : true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addbadgetobutton(badge: nil)
fatalError("init(coder:) is not implemented")
}
}
Step 2:
Create a function in your base file which u can use in each View Controller :
func addBadge(itemvalue: String) {
let bagButton = BadgeButton()
bagButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
bagButton.tintColor = UIColor.darkGray
bagButton.setImage(UIImage(named: "ShoppingBag")?.withRenderingMode(.alwaysTemplate), for: .normal)
bagButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
bagButton.badge = itemvalue
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: bagButton)
}
Step 3 :
Use above function from any View Controller in this way :
self.addBadge(itemvalue: localStorage.string(forKey: "total_products_in_cart") ?? "0")
First create label, then right bar button. On right bar button add subview which will be badge count. Finally add navigation right bar button.
SWIFT 5
let badgeCount = UILabel(frame: CGRect(x: 22, y: -05, width: 20, height: 20))
badgeCount.layer.borderColor = UIColor.clear.cgColor
badgeCount.layer.borderWidth = 2
badgeCount.layer.cornerRadius = badgeCount.bounds.size.height / 2
badgeCount.textAlignment = .center
badgeCount.layer.masksToBounds = true
badgeCount.textColor = .white
badgeCount.font = badgeCount.font.withSize(12)
badgeCount.backgroundColor = .red
badgeCount.text = "4"
let rightBarButton = UIButton(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
rightBarButton.setBackgroundImage(UIImage(named: "NotificationBell"), for: .normal)
rightBarButton.addTarget(self, action: #selector(self.onBtnNotification), for: .touchUpInside)
rightBarButton.addSubview(badgeCount)
let rightBarButtomItem = UIBarButtonItem(customView: rightBarButton)
navigationItem.rightBarButtonItem = rightBarButtomItem
I had the same task. I didn't want to use third-party libraries. Firstly, I tried Stefano's solution and it's great however I decided to implement my own way to solve it.
In my humble opinion, there are simple steps described below briefly:
Create UIView instance within .xib file and put necessary items like UILabel or UIImageView instance depending on your design requirements.
The final action I did in this step is putting invisible button in the top of view's hierarchy.
Create YourCustomView.swift and link all #IBOutlets from xib to current file inside your custom view class implementation.
Next, implement class function in YourCustomView class which will load custom view from xib and return it as YourCustomView instance.
Finally, add your custom badge to your custom view controller instance!
My result is..
P.S. If you need to implement #IBActions I recommend to link your custom view and custom view controller through the delegate pattern.
using M13BadgeView.. use this code
(im using fontawesome.swift for buttons :: https://github.com/thii/FontAwesome.swift)
let rightButton = UIButton(frame: CGRect(x:0,y:0,width:30,height:30))
rightButton.titleLabel?.font = UIFont.fontAwesome(ofSize: 22)
rightButton.setTitle(String.fontAwesomeIcon(name: .shoppingBasket), for: .normal)
let rightButtonItem : UIBarButtonItem = UIBarButtonItem(customView: rightButton)
let badgeView = M13BadgeView()
badgeView.text = "1"
badgeView.textColor = UIColor.white
badgeView.badgeBackgroundColor = UIColor.red
badgeView.borderWidth = 1.0
badgeView.borderColor = UIColor.white
badgeView.horizontalAlignment = M13BadgeViewHorizontalAlignmentLeft
badgeView.verticalAlignment = M13BadgeViewVerticalAlignmentTop
badgeView.hidesWhenZero = true
rightButton.addSubview(badgeView)
self.navigationItem.rightBarButtonItem = rightButtonItem
Good answer #Julio Bailon (https://stackoverflow.com/a/45948819/1898973)!
Here is the author's site with full explanation: http://www.stefanovettor.com/2016/04/30/adding-badge-uibarbuttonitem/.
It seems not to be working on iOS 11, maybe because the script try to access the "view" property of the UIBarButtonItem. I made it work:
By creating a UIButton and then creating the UIBarButtonItem using the UIButton as a customView:
navigationItem.rightBarButtonItem = UIBarButtonItem.init(
customView: shoppingCartButton)
By replacing the line in the UIBarButtonItem extension:
guard let view = self.value(forKey: "view") as? UIView else { return }
with the following:
guard let view = self.customView else { return }
Seems elegant to me and, best of all, it worked!
You can set below constraints to UILabel with respect to UIButton
align UILabel's top and trailing to UIButton
And when you need to show badge set text to UILabel and when you don't want to show badge then set empty string to UILabel
Download This
For BarButtonItem : Drag and Drop UIBarButtonItem+Badge.h and UIBarButtonItem+Badge.m class in project.
Write this code for set Badges:
self.navigationItem.rightBarButtonItem.badgeValue = "2"
self.navigationItem.rightBarButtonItem.badgeBGColor = UIColor.black
For UIButtton : Drag and Drop UIButton+Badge.h and UIButton+Badge.m class in project.
self.notificationBtn.badgeValue = "2"
self.notificationBtn.badgeBGColor = UIColor.black
Answer with extension from Julio will not work.
Starting from iOS 11 this code will not work cause line of code below will not cast UIView. Also it's counting as private API and seems to be will not pass AppStore review.
guard let view = self.value(forKey: "view") as? UIView else { return }
Thread on Apple Developer Forum
Second thing that this snippet always draws circle, so it can't fit numbers bigger than 9.
Here the simplified version by using custom view
Easy and clear solution if you are looking for only adding the red dot without the number;
private var handle: UInt8 = 0;
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
func setBadge(offset: CGPoint = .zero, color: UIColor = .red, filled: Bool = true, fontSize: CGFloat = 11) {
badgeLayer?.removeFromSuperlayer()
guard let view = self.value(forKey: "view") as? UIView else {
return
}
var font = UIFont.systemFont(ofSize: fontSize)
if #available(iOS 9.0, *) {
font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: .regular)
}
//Size of the dot
let badgeSize = UILabel(frame: CGRect(x: 22, y: -05, width: 10, height: 10))
// initialize Badge
let badge = CAShapeLayer()
let height = badgeSize.height
let width = badgeSize.width
// x position is offset from right-hand side
let x = view.frame.width + offset.x - 17
let y = view.frame.height + offset.y - 34
let badgeFrame = CGRect(origin: CGPoint(x: x, y: y), size: CGSize(width: width, height: height))
badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// initialiaze Badge's label
let label = CATextLayer()
label.alignmentMode = .center
label.font = font
label.fontSize = font.pointSize
label.frame = badgeFrame
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// bring layer to front
badge.zPosition = 1_000
}
private func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
// MARK: - Utilities
extension CAShapeLayer {
func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
path = UIBezierPath(roundedRect: rect, cornerRadius: 7).cgPath
}
}
The source of the code:
https://gist.github.com/freedom27/c709923b163e26405f62b799437243f4
I only made a few changes to eliminate the number.
The MIBadgeButton-Swift is working also on UIBarButtonItems.
Here is my code after the navigation bar is created:
let rightBarButtons = self.navigationItem.rightBarButtonItems
let alarmsBarButton = rightBarButtons?.last
let alarmsButton = alarmsBarButton.customView as! MIBadgeButton?
alarmsButton.badgeString = "10"
You can do it programmatically with
self.tabBarItem.badgeColor = .red
or use the storyboard. See: