Unarchive NSData back to NSColor in swift - macos

I have archived an NSColor to store it in NSUserDefaults:
var data = NSArchiver.archivedDataWithRootObject(NSColor.redColor())
storage.setObject(data, forKey: "color")
storage.synchronize()
But now I need to get the color back from NSData, I have no idea how to do that

You just need to use if let to unwrap your NSData and also you will need a conditional cast as follow:
edit/update:
Swift 3 or later
// archiving
let color: NSColor = .red
let data = NSKeyedArchiver.archivedData(withRootObject: color)
UserDefaults.standard.set(data, forKey: "color")
// unarchiving
if let loadedData = UserDefaults.standard.data(forKey: "color"),
let loadedColor = NSKeyedUnarchiver.unarchiveObject(with: loadedData) as? NSColor {
// you can access loadedColor here
print(loadedColor) // "sRGB IEC61966-2.1 colorspace 1 0 0 1\n"
}

Ran into some errors trying to get Leo's answer working in Swift 5. Came up with this extension which lets UserDefaults store and retrieve colors. Try pasting this into a Playground.
import Cocoa
extension UserDefaults {
func set(_ color: NSColor, forKey: String) {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false) {
self.set(data, forKey: forKey)
}
}
func color(forKey: String) -> NSColor? {
guard
let storedData = self.data(forKey: forKey),
let unarchivedData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: storedData),
let color = unarchivedData as NSColor?
else {
return nil
}
return color
}
}
// get defaults instance
let defaults = UserDefaults.standard
// create a color
let mycolor = NSColor(red: 0.0, green: 0.5, blue: 0.8, alpha: 0.5)
// save the color
defaults.set(mycolor, forKey: "mycolor")
// read the color back. this returns an optional, may be nil
defaults.color(forKey: "mycolor")

Related

Converting from UIImage to a SwiftUI Image results in a blank image of the same size

I am trying to convert a UIImage to a SwiftUI Image using the init(uiImage:) initializer. My UIImage itself is created from a CIImage generated by a CIQRCodeGenerator CIFilter. I am running my code on a Playground in Xcode 11.1 GM seed 1. Here is the entirety of my code:
import SwiftUI
import UIKit
func qrCodeImage(for string: String) -> Image? {
let data = string.data(using: String.Encoding.utf8)
guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
qrFilter.setValue(data, forKey: "inputMessage")
guard let ciImage = qrFilter.outputImage else { return nil }
let uiImage = UIImage(ciImage: ciImage)
let image = Image(uiImage: uiImage)
return image
}
let image = qrCodeImage(for: "fdsa")
And here is the result:
Even when I transform the image with CGAffineTransform(scaleX: 10, y: 10), the resulting SwiftUI Image at the end is still the same size, but blank.
Following solution provided in: Generating QR Code with SwiftUI shows empty picture
Here is the code:
var ciContext = CIContext()
func qrCodeImage(for string: String) -> Image? {
let data = string.data(using: String.Encoding.utf8)
guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
qrFilter.setValue(data, forKey: "inputMessage")
guard let ciImage = qrFilter.outputImage else { return nil }
let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent)
let uiImage = UIImage(cgImage: cgImage!)
let image = Image(uiImage: uiImage)
return image
}
let image = qrCodeImage(for: "fdsa")
Result:
screenshot in swift playground
Can confirm I encounter the same issue with a SwiftUI Image using a UIImage initialized from data. Can verify that the image is loaded when paused in debugging, but it does not display in the SwiftUI Image.
This solution worked for me: explicitly specify the image rendering mode. In my case I added the following: .renderingMode(.original)
#Eugene remark worked for me:
let image = Image(uiImage: uiImage).renderingMode(.original)

set image color of a template image

I have an image like this:
(Rendered as a template image)
I tried this code:
#IBOutlet weak var imgAdd: NSImageView!
imgAdd.layer?.backgroundColor = CGColor.white
Which only changes the background color of course.
Is there a way to change the color of this image programmatically?
So far I've tried the code below which doesn't work. (The image color doesn't change.)
func tintedImage(_ image: NSImage, tint: NSColor) -> NSImage {
guard let tinted = image.copy() as? NSImage else { return image }
tinted.lockFocus()
tint.set()
let imageRect = NSRect(origin: NSZeroPoint, size: image.size)
NSRectFillUsingOperation(imageRect, .sourceAtop)
tinted.unlockFocus()
return tinted
}
imgDok.image = tintedImage(NSImage(named: "myImage")!, tint: NSColor.red)
Swift 4
Updated answer for Swift 4
Please note, this NSImage extension is based on #Ghost108 and #Taehyung_Cho's answers, so a larger credit goes to them.
extension NSImage {
func tint(color: NSColor) -> NSImage {
let image = self.copy() as! NSImage
image.lockFocus()
color.set()
let imageRect = NSRect(origin: NSZeroPoint, size: image.size)
imageRect.fill(using: .sourceAtop)
image.unlockFocus()
return image
}
}
Swift 4 version
extension NSImage {
func image(withTintColor tintColor: NSColor) -> NSImage {
guard isTemplate else { return self }
guard let copiedImage = self.copy() as? NSImage else { return self }
copiedImage.lockFocus()
tintColor.set()
let imageBounds = NSMakeRect(0, 0, copiedImage.size.width, copiedImage.size.height)
imageBounds.fill(using: .sourceAtop)
copiedImage.unlockFocus()
copiedImage.isTemplate = false
return copiedImage
}
}
I found the solution with everyone's help:
(Swift 3)
func tintedImage(_ image: NSImage, tint: NSColor) -> NSImage {
guard let tinted = image.copy() as? NSImage else { return image }
tinted.lockFocus()
tint.set()
let imageRect = NSRect(origin: NSZeroPoint, size: image.size)
NSRectFillUsingOperation(imageRect, .sourceAtop)
tinted.unlockFocus()
return tinted
}
imgDok.image = tintedImage(NSImage(named: "myImage")!, tint: NSColor.red)
Important: in interface builder I had to set the "render as" setting of the image to "Default".
The other solutions don't work when the user wants to change between light and dark mode, this method solves that:
extension NSImage {
func tint(color: NSColor) -> NSImage {
return NSImage(size: size, flipped: false) { (rect) -> Bool in
color.set()
rect.fill()
self.draw(in: rect, from: NSRect(origin: .zero, size: self.size), operation: .destinationIn, fraction: 1.0)
return true
}
}
}
Be aware that if you use .withAlphaComponent(0.5) on an NSColor instance, that color loses support for switching between light/dark mode. I recommend using color assets to avoid that issue.
Had to modify #Ghost108's answer little bit for Xcode 9.2.
NSRectFillUsingOperation(imageRect, .sourceAtop)
to
imageRect.fill(using: .sourceAtop)
Thanks.
Since your image is inside an NSImageView, the following should work fine (available since macOS 10.14):
let image = NSImage(named: "myImage")!
image.isTemplate = true
let imageView = NSImageView(image: image)
imageView.contentTintColor = .green
The solution is to apply "contentTintColor" to your NSImageView instead of the NSImage.
See: Documentation
no need to copt:
extension NSImage {
func tint(with color: NSColor) -> NSImage {
self.lockFocus()
color.set()
let srcSpacePortionRect = NSRect(origin: CGPoint(), size: self.size)
srcSpacePortionRect.fill(using: .sourceAtop)
self.unlockFocus()
return self
}
}
Since you can't use the UIImage functions, you can try using CoreImage (CI). I don't know if there is an easier version but this one will work fore sure!
First you create the CIImage
let image = CIImage(data: inputImage.tiffRepresentation!)
Now you can apply all kinds of filters and other stuff to the image, it's a really powerful tool.
The documentation for CI: https://developer.apple.com/documentation/coreimage
The Filter List: https://developer.apple.com/library/content/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html
Here is a simple filter example, you basically initialise a filter and then set the values for it, output it and repeat.
let yourFilterName = CIFilter(name: "FilterName")
yourFilterName!.setValue(SomeInputImage, forKey: kCIInputImageKey)
yourFilterName!.setValue(10, forKey: kCIInputRadiusKey)
let yourFilterName = yourFilterName!.outputImage
Now you can just convert the output back as NSImage.
let cgimg = context.createCGImage(yourFilterName!, from: yourFilterName!.extent)
let processedImage = NSImage(cgImage: cgimg!, size: NSSize(width: 0, height: 0))
Try this code it helps.
Swift 3
let theImageView = UIImageView(image: UIImage(named:"foo")!.withRenderingMode(.alwaysTemplate))
theImageView.tintColor = UIColor.red

Saving text labels on top of images

I'm building an app which takes Strings from the user and saves them using NSUserDefaults. Then I recover them and paste them on top of an image (a form or questionnaire) I'm currently saving the image with .write to file but when I check the image I've saved, I discovered that it doesn't save the full image and as a consequence the positions for the fields aren't right. I need to be able to send this image and later on print on a sheet of A4 paper.
The simulator :
http://postimg.org/image/5szujdgw3/
The saved image:
http://postimg.org/image/uq6mea6vb/
Why isn't it saving the full image?
Here's my code :
import UIKit
class StartMorgagesSave: UIViewController {
let rectangle = UIBezierPath(rect: CGRectMake(80, 166, 80, 05)) //x , y , width , height 240, 165
let shapeLayer = CAShapeLayer()
//let label = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
shapeLayer.path = rectangle.CGPath
shapeLayer.strokeColor = UIColor.blueColor().CGColor
shapeLayer.fillColor = UIColor.whiteColor().CGColor
view.layer.addSublayer(shapeLayer)
view.pdfData.writeToURL(NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!.URLByAppendingPathComponent("test53.pdf"), atomically: true) // what it is saved as
view.pdfData.writeToFile("fdfdlo", atomically: false)
print(NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!.path!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension UIView {
var pdfData: NSData {
let result = NSMutableData()
UIGraphicsBeginPDFContextToData(result, frame, nil)
guard let context = UIGraphicsGetCurrentContext() else { return result }
UIGraphicsBeginPDFPage()
layer.renderInContext(context)
UIGraphicsEndPDFContext()
return result
}
}
Niall

Swift - saving and retrieving images from userDefaults

I'm trying to save images retrieved from Parse.com like this:
let userImageFile = object["Image"] as PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
image = UIImage(data:imageData)
let imageToSave:NSData = UIImagePNGRepresentation(image)
self.saveImage(intRandomNumb, retImage: imageToSave)
}
}
where the saveImage-function looks like this:
func saveImage(imagepath:Int, retImage:NSData){
println("image is being saved")
let defaults = NSUserDefaults.standardUserDefaults()
let imagePathName = "\(imagepath)"
defaults.setObject(retImage, forKey: imagePathName)
}
and later, I'm trying to display this image like this:
var point = gestureRecognizer.locationInView(self.tv)
if let indexPath = self.tv.indexPathForRowAtPoint(point)
{
let data = mainList[indexPath.row] as SecondModel
let fileRef = data.fileReference
let intFileRef = Int(fileRef)
println(intFileRef)
let defaults = NSUserDefaults.standardUserDefaults()
let usedKeyName = "\(intFileRef)"
if let photo = defaults.objectForKey(usedKeyName) as? UIImage {
println("Image created")
let photo = defaults.objectForKey(usedKeyName) as UIImage
var imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
imageView.image = photo
self.view.addSubview(imageView)
}
and the "Image created" never gets printed which means the retrieving somehow doesn't work.
I'm not quite sure if you're able to save images to the userdefaults like I've done here, but that was the best I could come up with, and I couldn't find any previous questions like this for Swift.
Any suggestions on how to proceed would be appreciated.
SOLUTION: The problem was that I tried to load the image directly as a UIImage. I also had to convert the NSData to a UIImage, this all happens in the last section of the code displayed above. Finally my code looks like this:
if let photo = defaults.objectForKey("\(intFileRef)") as? NSData {
println("Image created")
let photo = defaults.objectForKey("\(intFileRef)") as NSData
let imageToView:UIImage = UIImage(data: photo)
var imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
imageView.image = imageToView
self.view.addSubview(imageView)
}
I hope this can help others struggling with something similar to this.
Swift 3
Hey, try this beautiful code here:
Convert your UIImage to Data.
PNG:
yourDataImagePNG = UIImagePNGRepresentation(yourUIImageHere)
JPEG :
yourDataImageJPG = UIImage(data: yourUIImageHere,scale:1.0)
Save in UserDefaults.
UserDefaults().set(yourDataImagePNG, forKey: "image")
Recover from:
UserDefaults.standard.object(forKey: "image") as! Data
I hope to help!
It seems like you do not call defaults.synchronize() so it's not written to the defaults file.

NSPopUpButton arrow color

Is there a way to customize the color of a NSPopUpButton arrow? I've looked around but I've not found an answer yet
I really dont think there is an "easy" way to do this. If you look at the API description, it even states that it doesnt respond to the setImage routine. I have done quite a bit of work sub-classing button objects, etc... and I think this is where you would have to go in order to do what you are asking.
Like too many of these controls, I did it by subclassing NSPopupButton(Cell) and then doing all my own drawing in drawRect...I cheated a little though, and used an image do the actual triangle rather than trying to do it via primitives.
- (void)drawRect:(NSRect)dirtyRect
{
//...Insert button draw code here...
//Admittedly the above statement includes more work than we probably want to do.
//Assumes triangleIcon is a cached NSImage...I also make assumptions about location
CGFloat iconSize = 6.0;
CGFloat iconYLoc = (dirtyRect.size.height - iconSize) / 2.0;
CGFloat iconXLoc = (dirtyRect.size.width - (iconSize + 8));
CGRect triRect = {iconXLoc, iconYLoc, iconSize, iconSize};
[triangleIcon drawInRect:triRect];
}
i did this and its worked for me.
(void)drawImageWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSPopUpButton *temp = (NSPopUpButton*)controlView;
NSString *strtile = temp.title;
AppDelegate *appdel = (AppDelegate*)[NSApplication sharedApplication].delegate;
NSFont *font = [NSFont systemFontOfSize:13.5];
NSSize size = NSMakeSize(40, 10);// string size
CGRect rect = controlView.frame;
rect = CGRectMake((size.width + temp.frame.size.width)/2, rect.origin.y, 8, 17);
[self drawImage:[NSImage imageNamed:#"icon_downArrow_white.png"] withFrame:rect inView:self.
}
I have changed arrow color by using "False Color" filter without using any image. So far it is the easiest way to change cocoa control to me.
class RLPopUpButton: NSPopUpButton {
init() {
super.init(frame: NSZeroRect, pullsDown: false)
addFilter()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addFilter()
}
func addFilter() {
let colorFilter = CIFilter(name: "CIFalseColor")!
colorFilter.setDefaults()
colorFilter.setValue(CIColor(cgColor: NSColor.black.cgColor), forKey: "inputColor0")
colorFilter.setValue(CIColor(cgColor: NSColor.white.cgColor), forKey: "inputColor1")
// colorFilter.setValue(CIColor(cgColor: NSColor.yellow.cgColor), forKey: "inputColor0")
// colorFilter.setValue(CIColor(cgColor: NSColor.property.cgColor), forKey: "inputColor1")
self.contentFilters = [colorFilter]
}
}
Swift 5
In interface builder, remove default arrow setting.
Then, apply this subclass for cell, which will add an NSImageView to the right side of the NSPopUpButton.
This way you have complete control over what you set as your custom button and how you position it.
import Cocoa
#IBDesignable class NSPopUpButtonCellBase: NSPopUpButtonCell {
let textColor = NSColor(named: "white")!
let leftPadding: CGFloat = 16
let rightPadding: CGFloat = 30
override func awakeFromNib() {
super.awakeFromNib()
let imageView = NSImageView()
imageView.image = NSImage(named: "ic_chevron_down")!
controlView!.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: CGFloat(20)).isActive = true
imageView.heightAnchor.constraint(equalToConstant: CGFloat(20)).isActive = true
imageView.trailingAnchor.constraint(equalTo: controlView!.trailingAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: controlView!.centerYAnchor).isActive = true
}
// overriding this removes the white container
override func drawBezel(withFrame frame: NSRect, in controlView: NSView) {
}
// overriding this allows us to modify paddings to text
override func titleRect(forBounds cellFrame: NSRect) -> NSRect {
// this gets rect, which has title's height, not the whole control's height
// also, it's origin.y is such that it centers title
let processedTitleFrame = super.titleRect(forBounds: cellFrame)
let paddedFrame = NSRect(
x: cellFrame.origin.x + leftPadding,
y: processedTitleFrame.origin.y,
width: cellFrame.size.width - leftPadding - rightPadding,
height: processedTitleFrame.size.height
)
return paddedFrame
}
// overriding this allows us to style text
override func drawTitle(_ title: NSAttributedString, withFrame frame: NSRect, in controlView: NSView) -> NSRect {
let attributedTitle = NSMutableAttributedString.init(attributedString: title)
let range = NSMakeRange(0, attributedTitle.length)
attributedTitle.addAttributes([NSAttributedString.Key.foregroundColor : textColor], range: range)
return super.drawTitle(attributedTitle, withFrame: frame, in: controlView)
}
}

Resources