Draw NSString right justified with NSString.draw(in rect: withAttributes: ) in MacOS? - macos

Cannot draw a String right justified in NSView ( MacOs )
Here is my code, which crashes with :
-[__SwiftValue lineBreakMode]: unrecognized selector sent to instance 0x600003408750
let attrs = [NSAttributedString.Key.foregroundColor:NSColor.white,
NSAttributedString.Key.paragraphStyle:NSTextAlignment.right
] as [NSAttributedString.Key : Any]
let absolutPoint = NSPoint(x: 0.0, y: 0.0)
var convertedPt = convertPoint(dataPoint:se,scaleX:scaleX,scaleY:scaleY)
convertedPt.x = absolutPoint.x
let scalaLabel = NSString(format:"%3.0f",se.y)
let place = NSMakeRect(convertedPt.x, convertedPt.y, 80.0, 12.0)
scalaLabel.draw(in:place ,withAttributes:attrs )
If I do not set paragraphStyle in attrs, text is drawn correctly. But not right-justified.
Any idea what´s wrong with my code ?

NSAttributedString.Key.paragraphStyle:NSTextAlignment.right
That's not valid, that's why it's crashing.
The value should be a NSParagraphStyle, not a NSTextAlignment as stated in the documentation:
The value of this attribute is an NSParagraphStyle object. Use this
attribute to apply multiple attributes to a range of text. If you do
not specify this attribute, the string uses the default paragraph
attributes, as returned by the default method of NSParagraphStyle.
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .right
let attrs = [NSAttributedString.Key.foregroundColor:NSColor.white,
NSAttributedString.Key.paragraphStyle: paragraphStyle
] as [NSAttributedString.Key : Any]
Side note, if you state the type before, it might be shorten:
let attrs: NSAttributedString.Key: Any] = [foregroundColor: NSColor.white,
paragraphStyle: paragraphStyle]

Related

Applying a gradient effect on an SKSpriteNode in Swift 5.0

iOS 14, Swift 5.x
Trying to add a gradient to a spriteNode using SKEffect. Code compiles, but then crashes, am I attempting the impossible here.
let image2U = UIImage(named: "2140983")?.ciImage
let effectsNode = SKEffectNode()
let filter = CIFilter(name: "CILinearGradient")
let startColor = UIColor.red
let endColor = UIColor.yellow
let startVector = CIVector(cgPoint: CGPoint(x: 0, y: 0))
let endVector = CIVector(cgPoint: CGPoint(x: box.size.width, y: box.size.height))
filter?.setDefaults()
filter?.setValue(startVector, forKey: "inputPoint0")
filter?.setValue(endVector, forKey: "inputPoint1")
filter?.setValue(startColor, forKey: "inputColor0")
filter?.setValue(endColor, forKey: "inputColor1")
filter?.setValue(image2U, forKey: "inputImage")
effectsNode.filter = filter
self.addChild(effectsNode)
effectsNode.addChild(box)
Compiles, but then crashes with this message ...
2021-07-09 21:08:47.584142+0200 GameIV[19791:1140737] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<CILinearGradient 0x600002070d20> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key InputImage.'
And as you can see I added an inputImage? Tried a UIImage, same error... tried no image at all, same error?
Probably you have a typo in the key name. Maybe "inputImage" instead of "InputImage". Usually you'd use one of the constants to avoid these. There's a partial list at https://developer.apple.com/documentation/coreimage/cifilter/filter_parameter_keys
The answer to my question is that SKEffectNodes need an input image, that is what it is trying to tell me here. And the gradient filter doesn't need/work with an image, it simply creates a new image. This is CIFilter code to do just that.
extension UIImage {
func returnCheckerboard() -> UIImage {
let context = CIContext(options: nil)
let checkerFilter = CIFilter.checkerboardGenerator()
checkerFilter.color0 = .white
checkerFilter.color1 = .black
checkerFilter.center = CGPoint(x: 0, y: 0)
checkerFilter.sharpness = 1
checkerFilter.width = 8
guard let outputImage = checkerFilter.outputImage else { return UIImage() }
if let cgimg = context.createCGImage(outputImage, from: CGRect(x: 0, y: 0, width: 128, height: 128)) {
let filteredImage = UIImage(cgImage: cgimg)
return filteredImage
}
return UIImage()
}
}
This doesn't create a gradient, but the principle is the same. It creates a checkerboard, doesn't require an input image and couldn't/isn't a CIFilter you can use with SKEffectNode.

macOS Swift CALayer.contents does not load image

The code below creates a red rectangle that is animated to move across the view from left to right. I would like to have an arbitrary shape loaded from an image to either superimpose or replace the rectangle. However, the circleLayer.contents = NSImage statement in the initializeCircleLayer function doesn't produce any effect. The diagnostic print statement seems to verify that the image exists and has been found, but no image appears in the view. How do I get an image into the layer to replace the animated red rectangle? Thanks!
CODE BELOW:
import Cocoa
class ViewController: NSViewController {
var circleLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
initializeCircleLayer()
simpleCAAnimationDemo()
}
func initializeCircleLayer(){
circleLayer.bounds = CGRect(x: 0, y: 0, width: 150, height: 150)
circleLayer.position = CGPoint(x: 50, y: 150)
circleLayer.backgroundColor = NSColor.red.cgColor
circleLayer.cornerRadius = 10.0
let testIm = NSImage(named: NSImage.Name(rawValue: "testImage"))
print("testIm = \(String(describing: testIm))")
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage
circleLayer.contentsGravity = kCAGravityCenter
self.view.layer?.addSublayer(circleLayer)
}
func simpleCAAnimationDemo(){
circleLayer.removeAllAnimations()
let animation = CABasicAnimation(keyPath: "position")
let startingPoint = NSValue(point: NSPoint(x: 50, y: 150))
let endingPoint = NSValue(point: NSPoint(x: 600, y: 150))
animation.fromValue = startingPoint
animation.toValue = endingPoint
animation.repeatCount = Float.greatestFiniteMagnitude
animation.duration = 10.0
circleLayer.add(animation, forKey: "linearMovement")
}
}
Why it doesn't work
The reason why
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage
doesn't work is because it's a reference to the cgImage(forProposedRect:context:hints:) method, meaning that its type is
((UnsafeMutablePointer<NSRect>?, NSGraphicsContext?, [NSImageRep.HintKey : Any]?) -> CGImage?)?
You can see this by assigning NSImage(named: NSImage.Name(rawValue: "testImage"))?.cgImage to a local variable and ⌥-clicking it to see its type.
The compiler allows this assignment because circleLayer.contents is an Any? property, so literally anything can be assigned to it.
How to fix it
As of macOS 10.6, you can assign NSImage objects to a layers contents directly:
circleLayer.contents = NSImage(named: NSImage.Name(rawValue: "testImage"))

Swift 4: How to change font attributes of number in multiple UITextFields when .count > 3 and how to reverse calculation?

I have three UITextFields that will only contain a whole number between 0 and either 13066 or 3915 maximum. It's presented with a special font where font1 at 50pt is larger than font2 at 50pt so I only need to play around with the font file, not the size.
1) I need help finding a way to have the hundreds presented with font2 but when the number >= 1000, the digits for thousands is presented with font1, while the hundreds still maintain font2. As this is a UITextField input, I need this to happen real-time.
Animated picture of how the end result eventually will be!
2) If you watch the animation, you'll see that each of the three fields totals to the field at the bottom. This is straight forward. However, I also want to reverse this, i.e filling in the total and the dials and digits should fill in as shown (half in each MAIN up to 3920 and remaining in CTR up to 13066). How do I code a functioning reversible calculation like that and avoid conflict?
This is what I got so far but it doesn't quite do what I want yet:
`class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var centerField: UITextField!
#IBOutlet weak var main1Field: UITextField!
#IBOutlet weak var main2Field: UITextField!
#IBOutlet weak var totalField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
//UITextField delegation
centerField.delegate = self
main1Field.delegate = self
main2Field.delegate = self
totalField.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//Change font when number input exceeds 3 digits
func getAttributedString(for number: Int) -> NSAttributedString {
let defaultAttributes = [
NSAttributedStringKey.font: UIFont(name: "PMDG_777_DU_A", size: UIFont.labelFontSize)!
]
let bigNumberAttributes = [
NSAttributedStringKey.font: UIFont(name: "PMDG_777_DU_B", size: UIFont.labelFontSize)!
]
let attributedString = NSMutableAttributedString(string: "\(number)", attributes: defaultAttributes)
if attributedString.length > 3 {
let substr = attributedString.string.dropLast(3)
let range = NSMakeRange(0, substr.utf16.count)
attributedString.setAttributes(bigNumberAttributes, range: range)
}
return attributedString
}
//Hide keyboard when hitting Return
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
//Hide keyboard when tapping outside of field
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
//Real-time calculation of entries made to the fields and output it live to the total
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//Convert the String inserted into the fields into Int and create variables
let centerFuel = Int(centerField.text!) ?? 0
centerField.attributedText = getAttributedString(for: centerFuel)
let main1Fuel = Int(main1Field.text!) ?? 0
main1Field.attributedText = getAttributedString(for: main1Fuel)
let main2Fuel = Int(main2Field.text!) ?? 0
main2Field.attributedText = getAttributedString(for: main2Fuel)
let total: Int = centerFuel + main1Fuel + main2Fuel
totalField.attributedText = getAttributedString(for: total)
return true
}
Use NSMutableAttributedString:
func getAttributedString(for number: Int) -> NSAttributedString {
precondition(number >= 0)
let defaultAttributes = [
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 18)
]
let bigNumberAttributes = [
NSAttributedStringKey.font: UIFont.systemFont(ofSize: 30)
]
let attributedString = NSMutableAttributedString(string: "\(number)", attributes: defaultAttributes)
if attributedString.length > 3 {
let range = NSMakeRange(0, attributedString.length - 3)
attributedString.setAttributes(bigNumberAttributes, range: range)
}
return attributedString
}
Then set the attributedText property on your label:
label.attributedText = getAttributedString(for: 13006)
And you can get result like this:
Adjust the styles to taste!
Here is a sample solution:
func attributedString(fromIntStr: String) -> NSAttributedString {
print("Working with: \(fromIntStr)")
let finalAttr = NSMutableAttributedString.init()
let length = fromIntStr.count
if length > 3 //we test this because we can't do dropLast(n) where n is negative
{
let start = String(fromIntStr.dropLast(3))
let firstPart = NSAttributedString.init(string: start,
attributes: [.font: UIFont.systemFont(ofSize: 20)])
finalAttr.append(firstPart)
}
var dropEnd = 0
if length > 3 //we test this because we can't do dropFirst(n) where n is negative
{
dropEnd = length - 3
}
else
{
dropEnd = 0 //That's just an explicit value but it was already set by default
}
let end = String(fromIntStr.dropFirst(dropEnd))
let lastPart = NSAttributedString.init(string: end,
attributes: [.font: UIFont.systemFont(ofSize: 15)])
finalAttr.append(lastPart)
//It seems that you want a right alignement and since we are using NSAttributedString we can do it by using ParagraphStyle
let paragraphStyle = NSMutableParagraphStyle.init()
paragraphStyle.alignment = .right
finalAttr.addAttribute(.paragraphStyle,
value: paragraphStyle,
range: NSRange(location: 0, length: finalAttr.length))
return finalAttr
}
It can be tested on Playground adding this at the end:
let values = ["", "9", "89", "789", "6789", "56789"]
let view = UIView.init(frame: CGRect(x: 0, y: 0, width: 800, height: 400))
for (i, str) in values.enumerated().reversed()
{
let label = UILabel.init(frame: CGRect(x: 0, y: i*50, width: 800, height: 50))
let attr = attributedString(fromIntStr: str)
label.attributedText = attr
view.addSubview(label)
}
view
There are various way to do this but here is the logic I used:
• Create a NSMutableAttributedString.
• Separate the string in two strings (one for 0-999, the other one for the rest)
• Create from them two NSAttributedString with the corresponding font
• Append them to the NSMutableAttributedString previously created.
Of course this could be improved:
Is dropFirst()/dropLast() the best solutions?
I supposed that you add already a String value from a Int (I didn't do the conversion).
Another solution more classic would have been:
• Create a NSMutableAttributedString from the whole String.
• Apply small font on range last from last-3
• Apply big font on range start to last -3
Last point be ommited if you apply the big font from the start on the whole string.

Swift Activity Indicator and Label

I'm trying to do a simple task of creating a activity indicator with a label that says, "loading" or "saving" or whatever I program it to say when it is running. I can not seem to figure out how to get it to be directly under my activity indicator though, right now it is right along the side of it and I want to to be centered below it.
Here is my code:
public func show(viewController : UIViewController) {
Async.main {
self.spinner = UIActivityIndicatorView(frame: CGRectMake(0, 0, self.size, self.size))
self.activityLabel = UILabel(frame: CGRectMake(0,0,200,200))
if let spinner = self.spinner {
spinner.activityIndicatorViewStyle = self.style
let screenSize: CGRect = UIScreen.mainScreen().bounds
spinner.center = CGPoint (x: screenSize.width/2 , y: screenSize.height/2)
spinner.hidesWhenStopped = true
viewController.view.addSubview(spinner)
spinner.startAnimating()
self.activityLabel?.center = CGPoint (x: screenSize.width/2 , y: screenSize.height/1.9 )
self.activityLabel?.text = self.textmessage
viewController.view.addSubview(self.activityLabel!)
}
}
}
Thanks for any help!
I think the problem you are having with the label being misaligned is the fact that you didn't set the label's textAlignment to .center. You need to add:
self.activityLabel.textAlignment = .center
That should put the text inside the label directly under the spinner.

Move and animate UIImageView using swift

I have what should be a fairly simple question here. Basically I'm trying to move an object (a UIImageView) from a point A (where it is set in the storyboard) to a point B which I define programatically.
My first pass through this resulted in this code
UIView.animateWithDuration(0.75, delay: 0.5, options: UIViewAnimationOptions.CurveLinear, animations: {
self.signInButton.alpha = 1
self.signInButton.center.y = 10
}, completion: nil)
However, what this code does is to basically move the button offcenter and then back to its original location.
I then looked into QuartzCore to help me, but everything is in Objective-C. I have this method:
func moveImage(view: UIImageView){
var toPoint: CGPoint = CGPointMake(0.0, -10.0)
var fromPoint : CGPoint = CGPointZero
var movement = CABasicAnimation(keyPath: "movement")
movement.additive = true
movement.fromValue = fromPoint
movement.toValue = toPoint
movement.duration = 0.3
view.layer.addAnimation(movement, forKey: "move")
}
However the problem here is that movement.fromValue cannot accept a CGPoint. I know that in objective C there was a function that converted a CGPoint to a NSValue, however this function seems to be deprecated out of Swift and I can't find the other way of doing this.
Therefore my question is either, how do I convert CGPoint to NSValue to make my moveImage() function work, or is there a better way to move an object from point A to point B?
Thanks!
I'm looking at this question Animate UIImage in UIImageView Up & Down (Like it's hovering) Loop
Use NSValue(CGPoint: cgpiont) instead of NSValue.valueWithCGPoint(<#point: CGPoint#>) which is deprecated in swift. NSValue(CGPoint: cgpiont) is constructor given for that which can be used to convert CGPoint to NSValue
in swift.Following code will work
func moveImage(view: UIImageView){
var toPoint: CGPoint = CGPointMake(0.0, -10.0)
var fromPoint : CGPoint = CGPointZero
var movement = CABasicAnimation(keyPath: "movement")
movement.additive = true
movement.fromValue = NSValue(CGPoint: fromPoint)
movement.toValue = NSValue(CGPoint: toPoint)
movement.duration = 0.3
view.layer.addAnimation(movement, forKey: "move")
}
SWIFT 3 UPDATES
func moveImageView(imgView: UIImageView){
var toPoint:CGPoint = CGPoint(x: 0.0, y: -10.0)
var fromPoint:CGPoint = CGPoint.zero
var movement = CABasicAnimation(keyPath: "movement")
movement.isAdditive = true
movement.fromValue = NSValue(cgPoint: fromPoint)
movement.toValue = NSValue(cgPoint: toPoint)
movement.duration = 0.3
imgView.layer.add(movement, forKey: "move")
}

Resources