Set color of NSButton programmatically swift - macos

I generate some NSButtons programmatically with this code:
for i in 1...height {
for j in 1...width {
var but = NSButton(frame: NSRect(x: x, y: y + 78, width: 30, height: 30))
but.tag = k
but.title = ""
but.action = Selector("buttonPressed:")
but.target = self
but.bezelStyle = NSBezelStyle(rawValue: 6)!
but.layer?.backgroundColor = NSColor.blackColor().CGColor
var ges = NSClickGestureRecognizer(target: self, action: Selector("addFlag:"))
ges.buttonMask = 0x2
ges.numberOfClicksRequired = 1
but.addGestureRecognizer(ges)
ar.append(but)
self.view.addSubview(but)
x += 30
k++
}
y += 30
x = 0
}
And I need to set the background color of all buttons to gray or black (randomly). I have no idea how to get or set the NSButton background color. I'm quite new to swift programming and I don't really know obj-c, so if you'll write it on swift, I'll really appreciate it!

I'm doing it with this code
final class MyButton: NSButton {
override func awakeFromNib() {
let layer = CALayer()
layer.backgroundColor = CGColorCreateGenericRGB(59.0/255, 52.0/255.0, 152.0/255.0, 1.0)
self.wantsLayer = true
self.layer = layer
}
}

I read throgh the NSButton reference guide, And it seems that the only way to change it it changing the image. But I have gotten a way to create an image using a colour. Add this extionsion at the top of your class file
extension NSImage {
class func swatchWithColor(color: NSColor, size: NSSize) -> NSImage {
let image = NSImage(size: size)
image.lockFocus()
color.drawSwatchInRect(NSMakeRect(0, 0, size.width, size.height))
image.unlockFocus()
return image
}
}
This will allow us to later create NSImage's using a UIColour. Lastly, When you want to create your button, Set the image using the extension we made.
myButton.image = NSImage.swatchWithColor( NSColor.blackColor(), size: NSMakeSize(100, 100) )
For the size parameter set it to your button's size.
UPDATE* If you want to randomly choose the colour you can do this..
var randomIndex = arc4random_uniform(2) + 1 //Creates a random number between 1 and 2
var colour = NSColor() //Empty colour
if randomIndex == 1 {
//If the random number is one then the colour is black
colour = NSColor.blackColor()
}
else {
//Else if it's not one it's grey
colour = NSColor.grayColor()
}
//set the button's image to the new colour
myButton.image = NSImage.swatchWithColor(colour, size: //My button size)
To get the size go to your storyboard, click on your button and go to the ruler tab. There it shows your button's size.

The bad news is that there is, IMHO, no good way of colouring buttons; they all have advantages and drawbacks.
For everyone who, like me, felt curious about colouring buttons under Yosemite I have written what may not be the ultimate guide to colouring buttons, but definitely a strong contender:
http://www.extelligentcocoa.org/colouring-buttons/
This covers all styles of buttons, and has screenshots for almost any combination, using
backgroundColor
drawRect
cell backgroundColor
monochrome layerEffect
whiteBalance Adjust layerEffect
images set in IB
subclassing NSButton and adding images depending on the button state
If anyone can think of another way of colouring buttons, I'm interested to hear of it!

Okay, here is what I did when I needed to change the look of a button: subclass it.
//
// LSButtonColor.swift
//
// Created by Lloyd Sargent on 8/26/16.
// Copyright © 2016 Canna Software. All rights reserved.
// License: Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the “Software”), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
import Cocoa
class LSButtonColor: NSButton {
fileprivate struct AssociatedKeys {
static var variableDictionary = "ls_variableDictionary"
}
fileprivate var variableDictionary: [String:AnyObject]! {
get {
var localVariableDictionary = objc_getAssociatedObject(self, &AssociatedKeys.variableDictionary) as! [String:AnyObject]!
if localVariableDictionary == nil {
localVariableDictionary = [String:AnyObject]()
objc_setAssociatedObject(self, &AssociatedKeys.variableDictionary, localVariableDictionary, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return localVariableDictionary
}
set(updatedDictionary) {
objc_setAssociatedObject(self, &AssociatedKeys.variableDictionary, updatedDictionary, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var backgroundColor: NSColor! {
get {
return variableDictionary["backgroundColor"] as? NSColor
}
set(newBackgroundColor) {
var localVariableDictionary = variableDictionary
localVariableDictionary?["backgroundColor"] = newBackgroundColor
variableDictionary = localVariableDictionary
}
}
var altBackgroundColor: NSColor! {
get {
return variableDictionary["altBackgroundColor"] as? NSColor
}
set(newAltBackgroundColor) {
var localVariableDictionary = variableDictionary
localVariableDictionary?["altBackgroundColor"] = newAltBackgroundColor
variableDictionary = localVariableDictionary
}
}
var borderColor: NSColor! {
get {
return variableDictionary["borderColor"] as? NSColor
}
set(newBorderColor) {
var localVariableDictionary = variableDictionary
localVariableDictionary?["borderColor"] = newBorderColor
variableDictionary = localVariableDictionary
self.layer?.borderColor = newBorderColor.cgColor
}
}
var cornerRadius: CGFloat {
get {
return variableDictionary["cornerRadius"] as! CGFloat
}
set(newCornerRadius) {
var localVariableDictionary = variableDictionary
localVariableDictionary?["cornerRadius"] = newCornerRadius as AnyObject?
variableDictionary = localVariableDictionary
self.layer?.cornerRadius = newCornerRadius
}
}
var borderWidth: CGFloat {
get {
return variableDictionary["borderWidth"] as! CGFloat
}
set(newBorderWidth) {
var localVariableDictionary = variableDictionary
localVariableDictionary?["borderWidth"] = newBorderWidth as AnyObject?
variableDictionary = localVariableDictionary
self.layer?.borderWidth = newBorderWidth
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
if self.isHighlighted {
if self.layer != nil {
self.layer?.backgroundColor = self.altBackgroundColor.cgColor
}
}
else {
if self.layer != nil {
self.layer?.backgroundColor = self.backgroundColor.cgColor
}
}
}
}
Okay, the first thing you might be puzzling over is the objc_getAssociatedObject - originally I did this as a category and it is an easy-peasy way of adding variables to categorys (everyone says you can’t do it, but everyone doesn’t realize that, yes, you can - it’s just not obvious).
Unfortunately, I ran into some issues, so I just made it a class - and didn’t bother to take out the associated object (why mess with success?)
Anyway, you should be able to pull out the relevant code to change the background of any button.

Related

Cropping NSImage with onscreen coordinates incorrect on different screen sizes

I'm trying to replicate macOS's screenshot functionality, dragging a selection onscreen to provide coordinates for cropping an image. I have it working fine on my desktop Mac (2560x1600), but testing on my laptop (2016 rMBP 15", 2880x1800), the cropped image is completely wrong. I don't understand why I'd get the right results on my desktop, but not on my laptop. I think it has something to do with the Quarts coordinates being different from Cocoa coordinates, seeing as how on the laptop, the resulting image seems like the coordinates are flipped on the Y-axis.
Here is the code I am using to generate the cropping CGRect:
# Segment used to draw the CAShapeLayer:
private func handleDragging(_ event: NSEvent) {
let mouseLoc = event.locationInWindow
if let point = self.startPoint,
let layer = self.shapeLayer {
let path = CGMutablePath()
path.move(to: point)
path.addLine(to: NSPoint(x: self.startPoint.x, y: mouseLoc.y))
path.addLine(to: mouseLoc)
path.addLine(to: NSPoint(x: mouseLoc.x, y: self.startPoint.y))
path.closeSubpath()
layer.path = path
self.selectionRect = path.boundingBox
}
}
private func startDragging(_ event: NSEvent) {
if let window = self.window,
let contentView = window.contentView,
let layer = contentView.layer,
!self.isDragging {
self.isDragging = true
self.startPoint = window.mouseLocationOutsideOfEventStream
shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 1.0
shapeLayer.fillColor = NSColor.white.withAlphaComponent(0.5).cgColor
shapeLayer.strokeColor = NSColor.systemGray.cgColor
layer.addSublayer(shapeLayer)
}
}
Then this is the code where I actually generate the screenshot and crop using the CGRect:
public func processResults(_ rect: CGRect) {
if let windowID = self.globalWindow?.windowNumber,
let screen = self.getScreenWithMouse(), rect.width > 5 && rect.height > 5 {
self.delegate?.processingResults()
let cgScreenshot = CGWindowListCreateImage(screen.frame, .optionOnScreenBelowWindow, CGWindowID(windowID), .bestResolution)
var rect2 = rect
rect2.origin.y = NSMaxY(self.getScreenWithMouse()!.frame) - NSMaxY(rect);
if let croppedCGScreenshot = cgScreenshot?.cropping(to: rect2) {
let rep = NSBitmapImageRep(cgImage: croppedCGScreenshot)
let image = NSImage()
image.addRepresentation(rep)
self.showPreviewWindow(image: image)
let requests = [self.getTextRecognitionRequest()]
let imageRequestHandler = VNImageRequestHandler(cgImage: croppedCGScreenshot, orientation: .up, options: [:])
DispatchQueue.global(qos: .userInitiated).async {
do {
try imageRequestHandler.perform(requests)
} catch let error {
print("Error: \(error)")
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.hidePreviewWindow()
}
}
}
self.globalWindow = nil
}
Not 15 minutes after I asked this question, I tried one more thing and it works!
Relevant snippet:
var correctedRect = rect
// Set the Y origin properly (counteracting the flipped Y-axis)
correctedRect.origin.y = screen.frame.height - rect.origin.y - rect.height;
// Checks if we're on another screen
if (screen.frame.origin.y < 0) {
correctedRect.origin.y = correctedRect.origin.y - screen.frame.origin.y
}
// Finally, correct the x origin (if we're on another screen, the origin will be larger than zero)
correctedRect.origin.x = correctedRect.origin.x + screen.frame.origin.x
// Generate the screenshot inside the requested rect
let cgScreenshot = CGWindowListCreateImage(correctedRect, .optionOnScreenBelowWindow, CGWindowID(windowID), .bestResolution)

How to merge 3 images(100x100px) to one new large (300x100px) in Swift

iam very new to swiftUI and dont found a way.
I want to create one image (300x100) dyncmicaly by merging three single 100x100 images horizontaly
ImageA(100x100) + ImageB(100x100) + ImageC(100x100) = ImageD(300x100)
Found a way to show them in a HStack but how can is get one new image to send the Date to new function
Regards
Alex
thanks a lot, I tried to use your function but got an error here "Cannot convert value of type 'UIImage?' to expected element type 'UIImage'!
Code should drat 3x zero. in the number-0.png ist just one lagre zero
//
// ContentView.swift
// imagecombine
//
// Created by Alex on 02.08.20.
// Copyright © 2020 Alex. All rights reserved.
//
import SwiftUI
func combineHorizontally(_ images: [UIImage]) -> UIImage? {
guard !images.isEmpty else { return nil }
var size = CGSize.zero
var scale = CGFloat.zero
for image in images {
scale = max(scale, image.scale)
size = CGSize(width: size.width + image.size.width, height: max(size.height, image.size.height))
}
var position = CGPoint.zero
let format = UIGraphicsImageRendererFormat()
format.scale = scale
return UIGraphicsImageRenderer(size: size, format: format).image { context in
for image in images {
image.draw(at: position)
position.x += image.size.width
}
}
}
let redImage = UIImage(named: "number-0.png")
let greenImage = UIImage(named: "number-0.png")
let blueImage = UIImage(named: "number-0.png")
let image = combineHorizontally([redImage, greenImage, blueImage])
struct ContentView: View {
var body: some View {
Image(image)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can use UIImageGraphicsRenderer and draw the images one after another:
func combineHorizontally(_ images: [UIImage]) -> UIImage? {
guard !images.isEmpty else { return nil }
var size = CGSize.zero
var scale = CGFloat.zero
for image in images {
scale = max(scale, image.scale)
size = CGSize(width: size.width + image.size.width, height: max(size.height, image.size.height))
}
var position = CGPoint.zero
let format = UIGraphicsImageRendererFormat()
format.scale = scale
return UIGraphicsImageRenderer(size: size, format: format).image { _ in
for image in images {
image.draw(at: position)
position.x += image.size.width
}
}
}
You said that there were only images and they were 100 × 100, but the above should work regardless of the number and size (memory permitting, of course).
Anyway, this
let image = combineHorizontally([redImage, greenImage, blueImage])
Results in:
To use that in a context where you don’t want an optional, you can use ! forced unwrapping operator, ?? nil coalescing operator, or some other unwrapping pattern, e.g. guard let, if let, etc.
Alternatively, if you don’t want to deal with optionals at all, you can write a rendition that doesn’t return an optional at all (but also doesn’t detect the error scenario where an empty array was provided):
func combineHorizontally(_ images: [UIImage]) -> UIImage {
var size = CGSize.zero
var scale: CGFloat = 1
for image in images {
scale = max(scale, image.scale)
size = CGSize(width: size.width + image.size.width, height: max(size.height, image.size.height))
}
var position = CGPoint.zero
let format = UIGraphicsImageRendererFormat()
format.scale = scale
return UIGraphicsImageRenderer(size: size, format: format).image { _ in
for image in images {
image.draw(at: position)
position.x += image.size.width
}
}
}

NSDocument printOperationWithSettings not showing all pages

In NSDocument subclass, have this function:
override func printOperationWithSettings(printSettings: [String : AnyObject]) throws -> NSPrintOperation {
let printInfo: NSPrintInfo = self.printInfo
var pageSize = printInfo.paperSize
pageSize.width -= printInfo.leftMargin + printInfo.rightMargin
pageSize.height -= printInfo.topMargin + printInfo.bottomMargin
pageSize.width = pageSize.width * 2
pageSize.height = pageSize.height * 2
let myPage = MyPage(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: pageSize))
let printOperation = NSPrintOperation(view: myPage, printInfo: printInfo)
return printOperation
}
MyPage is, for this test, an NSView subclass that just draws an oval.
class MyPage: NSView {
override var flipped: Bool {
return true
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.greenColor().set() // choose color
let figure = NSBezierPath() // container for line(s)
figure.appendBezierPathWithOvalInRect(self.frame)
figure.stroke() // draw line(s)
}
}
I'd expect this to show four pages in the print panel, but it only shows two, equating to the top left and bottom left of the oval. No matter how wide I make myPage's frame, only the leftmost pages are shown. Any ideas why? Thank you!

Dynamically adding cells to a NSMatrix laid out with Auto Layout has weird effects; why?

I want to create a group of radio buttons using the NSMatrix method that Interface Builder uses, but in code. The matrix is laid out using Auto Layout. I have it mostly working, except for when I add new options at runtime.
In the following example, clicking Append Item a few times will work fine, then the matrix starts going out of the window near the top (at least I think it's clipped at the top). If you maximize this window after adding a bunch of items, the window will stay the same height and all the items will be clipped to about a pixel high each, which is a very undesirable thing :)
In my real program (not this test below), it works mostly fine, but if I add an option dynamically, after certain numbers of items (initially 5), the options will clip very slightly, appearing slightly squeezed or squished. Adding another option reverts this until the next magic number is hit.
What's going on? I'm testing this on OS X Yosemite. Thanks.
// 17 august 2015
import Cocoa
var keepAliveMainwin: NSWindow? = nil
var matrix: NSMatrix? = nil
class ButtonHandler : NSObject {
#IBAction func onClicked(sender: AnyObject) {
var lastRow = matrix!.numberOfRows
matrix!.renewRows(lastRow + 1, columns: 1)
var cell = matrix!.cellAtRow(lastRow, column: 0) as! NSButtonCell
cell.title = "New Item"
matrix!.sizeToCells()
}
}
var buttonHandler: ButtonHandler = ButtonHandler()
func appLaunched() {
var mainwin = NSWindow(
contentRect: NSMakeRect(0, 0, 320, 240),
styleMask: (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask),
backing: NSBackingStoreType.Buffered,
defer: true)
var contentView = mainwin.contentView as! NSView
var prototype = NSButtonCell()
prototype.setButtonType(NSButtonType.RadioButton)
prototype.font = NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(NSControlSize.RegularControlSize))
matrix = NSMatrix(frame: NSZeroRect,
mode: NSMatrixMode.RadioModeMatrix,
prototype: prototype,
numberOfRows: 0,
numberOfColumns: 0)
matrix!.allowsEmptySelection = false
matrix!.selectionByRect = true
matrix!.intercellSpacing = NSMakeSize(4, 2)
matrix!.autorecalculatesCellSize = true
matrix!.drawsBackground = false
matrix!.drawsCellBackground = false
matrix!.autosizesCells = true
matrix!.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(matrix!)
var button = NSButton(frame: NSZeroRect)
button.title = "Append Item"
button.setButtonType(NSButtonType.MomentaryPushInButton)
button.bordered = true
button.bezelStyle = NSBezelStyle.RoundedBezelStyle
button.font = NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(NSControlSize.RegularControlSize))
button.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(button)
button.target = buttonHandler
button.action = "onClicked:"
var views: [String: NSView]
views = [
"button": button,
"matrix": matrix!,
]
addConstraints(contentView, "V:|-[matrix]-[button]-|", views)
addConstraints(contentView, "H:|-[matrix]-|", views)
addConstraints(contentView, "H:|-[button]-|", views)
mainwin.cascadeTopLeftFromPoint(NSMakePoint(20, 20))
mainwin.makeKeyAndOrderFront(mainwin)
keepAliveMainwin = mainwin
}
func addConstraints(view: NSView, constraint: String, views: [String: NSView]) {
var constraints = NSLayoutConstraint.constraintsWithVisualFormat(
constraint,
options: NSLayoutFormatOptions(0),
metrics: nil,
views: views)
view.addConstraints(constraints)
}
class appDelegate : NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(note: NSNotification) {
appLaunched()
}
func applicationShouldTerminateAfterLastWindowClosed(app: NSApplication) -> Bool {
return true
}
}
func main() {
var app = NSApplication.sharedApplication()
app.setActivationPolicy(NSApplicationActivationPolicy.Regular)
// NSApplication.delegate is weak; if we don't use the temporary variable, the delegate will die before it's used
var delegate = appDelegate()
app.delegate = delegate
app.run()
}
main()
Apparently, you need to omit the call to sizeToCells() after calling renewRows(_:columns:). My guess is that it sets the frame size, which is mostly useless when using auto layout, but also clears a "dirty" flag somewhere that tells the matrix that it needs to invalidate its intrinsic size. In other words, the matrix thinks it already did the re-layout stuff it needed to do.

How does one manually tell NSTextContainer to recalculate the lineFragmentRects?

In my application, I have been adding support for NSTextView allowing subviews to work naturally with the text editor. (For example, pressing "enter" drops every subview lower than the insertion point, and text wraps around subviews.)
I have successfully been able to get word wrap around subviews by overridinglineFragmentRectForProposedRect(proposedRect: NSRect,
sweepDirection: NSLineSweepDirection,
movementDirection: NSLineMovementDirection,
remainingRect: NSRectPointer) -> NSRect.
However, when a subview is dragged, or resized, the breaks in the text need to be recalculated to get the desired effect. The text needs to update itself to wrap around the subview as it is dragged or resized. However, I noticed that typing on a line updates the lineFragmentRect for that line. Is there a way to immediately mark the text as "dirty" after the subview is dragged? Here is the code for the text container subclass:
import Cocoa
class CASTextContainer: NSTextContainer {
var mainView: CASTextView = CASTextView()
var textSubviews: Array<AnyObject> {
get {
return mainView.subviews
}
}
override var simpleRectangularTextContainer: Bool {
get {
return false
}
}
func rectForActiveRange() -> NSRect {
let range = layoutManager.glyphRangeForCharacterRange(textView.selectedRange, actualCharacterRange:nil)
var rect = layoutManager.boundingRectForGlyphRange(range, inTextContainer: self)
rect = NSOffsetRect(rect, textView.textContainerOrigin.x, textView.textContainerOrigin.y)
return rect;
}
override func lineFragmentRectForProposedRect(proposedRect: NSRect,
sweepDirection: NSLineSweepDirection,
movementDirection: NSLineMovementDirection,
remainingRect: NSRectPointer) -> NSRect
{
remainingRect.initialize(NSZeroRect)
var frames: Array<NSRect> = []
if textSubviews.isEmpty {
let fullRect = NSMakeRect(0, 0, containerSize.width, containerSize.height)
return NSIntersectionRect(fullRect, proposedRect)
}
for view in textSubviews {
frames.append(view.frame)
}
// Sort the array so that the frames are ordered by increasing origin x coordinate.
let fullRect = NSMakeRect(0, 0, containerSize.width, containerSize.height)
let region = NSIntersectionRect(fullRect, proposedRect)
let sortedFrames: Array<NSRect> = sorted(frames, { (first: NSRect, second: NSRect) -> Bool in
return first.origin.x < second.origin.x
})
// Get the first rectangle height that overlaps with the text region's height.
var textDrawingRegion = NSZeroRect
var subviewFrame = NSZeroRect // Will be needed later to set remainingRect pointer.
var precedingRegion: NSRect = NSZeroRect // Hold the view frame preceding our insertion point.
for frame in sortedFrames {
if region.origin.y + NSHeight(region) >= frame.origin.y &&
region.origin.y <= frame.origin.y + NSHeight(frame)
{
if region.origin.x <= frame.origin.x {
// Calculate the distance between the preceding subview and the approaching one.
var width: CGFloat = frame.origin.x - precedingRegion.origin.x - NSWidth(precedingRegion)
textDrawingRegion.origin = region.origin
textDrawingRegion.size = NSMakeSize(width, NSHeight(region))
subviewFrame = frame
break
}
else {
precedingRegion = frame
}
}
}
if textDrawingRegion == NSZeroRect {
return region
}
else {
// Set the "break" in the text to the subview's location:
let nextRect = NSMakeRect(subviewFrame.origin.x + NSWidth(subviewFrame),
region.origin.y,
NSWidth(proposedRect),
NSHeight(proposedRect))
remainingRect.initialize(nextRect)
return textDrawingRegion
}
}
}

Resources