Having trouble solving a "unrecognized selector sent to instance" message - swift

I'm trying to create an action that will underline a button when pressed by replacing its current title with an attributed string. However, this keeps resulting in an "unrecognized selector sent to instance" error. I've connected one IBAction to this button, and created an IBOutlet weak variable referring to it. To my knowledge these should not interfere with one another.
Here is my code for the action connected to the button
#IBAction func voButtonTapped(_ sender: Any) {
let voString = "vo"
let attributedvo = NSAttributedString(string: voString, attributes: underlineAttribute)
vButton.setAttributedTitle(attributedvo, for: .normal)
}
and this is the error below:
[__SwiftValue set]: unrecognized selector sent to instance 0x600001584510
underlinedAttribute:
let underlineAttribute: [NSAttributedString.Key: Any] = [
.font: UIFont(name: "Rockwell-Bold", size: 35) as Any,
.foregroundColor: ColorManager.specialYellow,
.underlineStyle: NSUnderlineStyle.single.rawValue]
vButton:
#IBOutlet weak var vButton: UIButton!
ColorManager:
struct ColorManager {
static let specialYellow = Color("Special Yellow")
}

The colorManager struct was causing the issue.
I had to change it from
static let specialYellow = Color("special yellow")
to
static let specialYellow = UIcolor(red: 0.85, green: 0.68, blue: 0.00, alpha: 1.00)
so essentially I couldn't use my original saved color and had to find a similar one from a hex pallet, then I used an online converter to convert it to a UIcolor

Related

Saving and pasting an attributed string with a custom NSTextBlock

I am trying to create a custom NSTextBlock, much like the one Apple did at WWDC 18 (23 mins in).
Full demo project here.
Okay, so it works great when I'm editing and marking a paragraph with my paragraph style that has the text block attached.
But when I cut and paste it (or archive/unarchive from disk), it loses it. EDIT: It actually turns my TweetTextBlock subclass into a NSTableViewTextBlock, which also explains the borders.
Implementation
Here's a full Xcode project. Use the Format top menu item to trigger the markTweet function.
Here's how I add the attributes to the paragraph
#IBAction func markTweet(_ sender : Any?){
print("now we are marking")
let location = textView.selectedRange().location
guard let nsRange = textView.string.extractRange(by: .byParagraphs, at: location) else { print("Not in a paragraph"); return }
let substring = (textView.string as NSString).substring(with: nsRange)
let tweetParagraph = NSMutableParagraphStyle()
tweetParagraph.textBlocks = [TweetTextBlock()]
let twitterAttributes : [AttKey : Any] = [
AttKey.paragraphStyle : tweetParagraph,
AttKey.font : NSFont(name: "HelveticaNeue", size: 15)
]
textView.textStorage?.addAttributes(twitterAttributes, range: nsRange)
}
And this is my NSTextBlock subclass
import Cocoa
class TweetTextBlock: NSTextBlock {
override init() {
super.init()
setWidth(33.0, type: .absoluteValueType, for: .padding)
setWidth(70.0, type: .absoluteValueType, for: .padding, edge: .minX)
setValue(100, type: .absoluteValueType, for: .minimumHeight)
setValue(300, type: .absoluteValueType, for: .width)
setValue(590, type: .absoluteValueType, for: .maximumWidth)
backgroundColor = NSColor(white: 0.97, alpha: 1.0)
}
override func drawBackground(withFrame frameRect: NSRect, in controlView: NSView,
characterRange charRange: NSRange, layoutManager: NSLayoutManager) {
let frame = frameRect
let fo = frameRect.origin
super.drawBackground(withFrame: frame, in: controlView, characterRange:
charRange, layoutManager: layoutManager)
// draw string
let context = NSGraphicsContext.current
context?.shouldAntialias = true
let drawPoint: NSPoint = CGPoint(x: fo.x + 70, y: fo.y + 10)
let nameAttributes = [AttKey.font: NSFont(name: "HelveticaNeue-Bold", size: 15), .foregroundColor: NSColor.black]
var handleAttributes = [AttKey.font: NSFont(name: "HelveticaNeue", size: 15), .foregroundColor: NSColor(red: 0.3936756253, green: 0.4656872749, blue: 0.5323709249, alpha: 1)]
let nameAStr = NSMutableAttributedString(string: "Johanna Appleseed", attributes: nameAttributes)
let handleAStr = NSAttributedString(string: " #johappleseed ยท 3h", attributes: handleAttributes)
nameAStr.append(handleAStr)
nameAStr.draw(at: drawPoint)
let im = NSImage(named: "profile-twitter")!
im.draw(in: NSRect(x: fo.x + 10, y: fo.y + 10, width: 50, height: 50))
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
What I tried
My thinking is that this might happen because TextKit doesn't know how to archive the attributes from the custom block. But I tried overriding init:fromCoder and encode. They don't get called. Not on copy, paste, archiving, unarchiving. So I suppose that was not it. This leads me to think that all this custom drawing logic can't be saved in an attributed string, and that this is all happening in the layout manager. That makes sense. But how do I persist the block, then?
UPDATE: I tried reading the attributes. It has a paragraph style, and that paragraph style has an item in the textBlocks array property. But that text block is an NSTextBlock and not my subclass (i tried if block is TweetTextBlock which returns false)
UPDATE 2: I tried overriding properties like classForArchiver, and then reading them with e.g. print("twb: Class for archiver", block.classForArchiver). What's interesting here is that the text block has been turned into a NSTextTableBlock! I'm so deep in hacking this now that I'm looking for a way to store the className somewhere in the text block. So far, the only one I can think of is the tooltip property, but that's visible to the user, and I might want to use that for something else.
UPDATE 3: The tooltip is also not preserved. That's weird. The next big hack I can think of is setting the text color to HSB (n, 0, 0), where n is the identifier for the NSTextBlock subclass. Let's hope I don't have to go there.
UPDATE 4. This is most likely caused by both archiving and copy/pasting transforms the string into RTF. Here's public.rtf from my clipboard
{\rtf1\ansi\ansicpg1252\cocoartf2509
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;\red245\green245\blue245;}
{\*\expandedcolortbl;;\csgray\c97000;}
\pard\intbl\itap1\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
\f0\fs30 \cf0 THIS text is in a TweetTextBlock}
It appears the NSAttributedString is somehow at fault. I tried subclassing NSMutableParagraphStyle and using it and it is NOT being encoded or decoded (init).
It may be possible to simply annotate the text run with a custom Attribute.Key indicating the delineation of the block content and its "type" and then post-process the AttributedString after the paste.
Alternatively, the out-of-the-box Pasteboard types may not support and archived NSAttributedString. Rather, (and I'm guessing) the highest fidelity text type may be RTF which may account for the fact that the TextBlock NSCoding methods aren't invoked at all.
Looking at NSPasteboard.PasteboardType my vote is option 2.

Style placeholder text NSSearchField and NSTextField?

I've been creating a MacOS app and am having trouble styling the font of an NSSearchField (named searchField). My code so far is as follows:
Declared at top of single main viewController class:
let normalTextStyle = NSFont(name: "PT Mono", size: 14.0)
let backgroundColour = NSColor(calibratedHue: 0.6,
saturation: 0.5,
brightness: 0.2,
alpha: 1.0)
let normalTextColour = NSColor(calibratedHue: 0.5,
saturation: 0.1,
brightness: 0.9,
alpha: 1.0)
Declared in viewDidLoad:
searchField.backgroundColor = backgroundColour
searchField.textColor = normalTextColour
searchField.font = normalTextStyle
searchField.centersPlaceholder = false
searchField.currentEditor()?.font = normalTextStyle
let attrStr = NSMutableAttributedString(string: "Search...",
attributes: [NSForegroundColorAttributeName: normalTextColour])
searchField.placeholderAttributedString = attrStr
Generally this works except in one condition: when the search field has focus but no search term has been entered. In this case the placeholder text has the correct colour but the font seems to return to the default (Helvetica 12 point?). As soon as something is typed in or the field loses focus, then the correct font is used once more.
I have tried with no luck looking through the Apple docs for some kind of font or colour settings not currently being set. I have fiddled about with all the font setting I could find in the interface builder, including cocoa bindings and the normal settings in the inspector.
Do I need to set some value of the currentEditor? I am guessing not because the font is changed once text is entered.. I am stuck - can anyone help?
EDIT: I've now tried with an NSTextField and the results are the same. Does anyone have any ideas?
I eventually managed to find an answer. I created a new class TitleTextFormatter of type Formatter, which is 'is intended for subclassing. A custom formatter can restrict the input and enhance the display of data in novel ways'. All I needed to do was override certain default functions to get what I needed:
import Cocoa
class TitleTextFormatter: Formatter {
override func string(for obj: Any?) -> String? {
/*
* this function receives the object it is attached to.
* in my case it only ever receives an NSConcreteAttributedString
* and returns a plain string to be formatted by other functions
*/
var result: String? = nil
if let attrStr = obj as? NSAttributedString {
result = attrStr.string
}
return result
}
override func getObjectValue( _ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
for string: String,
errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
/*
* this function in general is overridden to provide an object created
* from the input string. in this instance, all I want is an attributed string
*/
let titleParagraphStyle = NSMutableParagraphStyle()
titleParagraphStyle.alignment = .center
let titleAttributes = [NSAttributedStringKey.foregroundColor: NSColor.mainText,
NSAttributedStringKey.font: NSFont.titleText,
NSAttributedStringKey.paragraphStyle: titleParagraphStyle]
let titleAttrStr = NSMutableAttributedString(string: string,
attributes: titleAttributes)
obj?.pointee = titleAttrStr
return true
}
override func attributedString(for obj: Any,
withDefaultAttributes attrs: [NSAttributedStringKey : Any]? = nil) -> NSAttributedString? {
/*
* is overridden to show that an attributed string is created from the
* formatted object. this happens to duplicate what the previous function
* does, only because the object I want to create from string is an
* attributed string
*/
var titleAttrStr: NSMutableAttributedString?
if let str = string(for: obj) {
let titleParagraphStyle = NSMutableParagraphStyle()
titleParagraphStyle.alignment = .center
let titleAttributes = [NSAttributedStringKey.foregroundColor: NSColor.mainText,
NSAttributedStringKey.font: NSFont.titleText,
NSAttributedStringKey.paragraphStyle: titleParagraphStyle]
titleAttrStr = NSMutableAttributedString(string: str,
attributes: titleAttributes)
}
return titleAttrStr
}
}
and then in viewDidLoad I added the following:
let titleTextFormatter = TitleTextFormatter()
titleTextField.formatter = titleTextFormatter

Changing color of NSView

I'm creating a simple Custom NSView that allows the user to input 3 text fields each for R,G and B values respectively. I already referenced this question and answer set here - it only accounts up to Swift 2.0. So, does anyone know the correct technique of obtaining this same effect in Swift 3.0? I can't seem to get anything that I'm doing to work. I keep getting errors of all sorts.
Here's the current technique I'm using (This seems to work... BUT it won't account for the other 2 RGB values, since it's used in the #IBAction):
//viewDidLoad
RVCustomView.wantsLayer = true
#IBAction func redValue(_ sender: AnyObject) {
print(red.stringValue)
var rValue = 0
let str = red.stringValue
if let n = NumberFormatter().number(from: str) {
rValue = Int(CGFloat(n))
}
CustomNSView.layer?.backgroundColor = CGColor(red: rValue, green: 255, blue: 255, alpha: 255)
}
If you want to dynamically change the background colour of a NSView each time the value of one of the three R, G, B text views changes, you could use the controlTextDidChange(notification:) delegate method of NSTextField coupled with outlets for each of the three text fields.
The method is fired every time one of the fields is changed and you use it for reading the value of the RGB fields (via outlets) and change the colour accordingly.
override func controlTextDidChange (notification: NSNotification?) {
// assuming the text fields' value is between 0 and 255
guard
let redValue = Float(redOutlet.stringValue),
let greenValue = Float(greenOutlet.stringValue),
let blueValue = Float(blueOutlet.stringValue)
else { return }
CustomNSView.layer?.backgroundColor = CGColor(red: CGFloat(redValue/255), green: CGFloat(greenValue/255), blue: CGFloat(blueValue/255), alpha: 255)
}
Note : don't forget to properly set the delegate for each of the three text fields!
I think you can just do that:
#IBAction func redValue(_ sender: AnyObject) {
yourView.backgroundColor = UIColor(colorLiteralRed: readValue/255, green: greenValue/255, blue: blueValue/255, alpha: 1)
}

Change color of NSTextField

I'm trying to code an easy example to modify the color of a NSTextfield playing with green and red colors as good answer or wrong.
I cannot achieve that cause i always obtained an error message when using this apple guide page for NSTextField, AppKit framework ref.
I'm trying to use this code :
#IBOutlet weak var mensajeResultado: NSTextField!
when trying to colorize it without success
let rango = NSRange(location: 0,length: 0)
mensajeResultado.superclass.setTextColor(NSColor.redColor(), range: rango)
Shouldn't it just be:
mensajeResultado.textColor = NSColor.redColor()
?
Ken Thomases' answer is right, you just have to assign the color to the textColor property of your text field.
Just for info, it's very convenient to test in a Playground when you're not sure, for example:
import Cocoa
import XCPlayground
let tf = NSTextField(frame: NSMakeRect(50, 50, 100, 100))
tf.stringValue = "test"
tf.font = NSFont(name: "Helvetica", size: 16)
tf.backgroundColor = NSColor.blueColor()
tf.textColor = NSColor.whiteColor()
let v = NSView(frame: NSMakeRect(0, 0, 200, 200))
v.addSubview(tf)
XCPShowView("My View", v)

Manual GestureRecognizer handler in Swift can't have arguments?

If I create a GestureRecognizer handler through InterfaceBuilder, I get a method that looks like this:
#IBAction func Tap(tap: UITapGestureRecognizer) {
var touchLocation = tap.locationInView(self.view)
imageView.transform.tx = touchLocation.x
imageView.transform.ty = touchLocation.y
}
//works great
If I manually create a GestureRecognizer, I have to create a function with no arguments, like this:
let tap = UITapGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
let imageSize = CGSize(width: 100, height: 100)
var iView = UIImageView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: imageSize))
self.view.addSubview(iView)
let image2 = UIImage(named: "square.png");
iView.image = image2
iView.userInteractionEnabled = true
iView.addGestureRecognizer(tap)
tap.addTarget(self, action: "Tap")
}
func Tap( //why can't I put an argument in here?// ){
let tapAlert = UIAlertController(title: "Tap Pressed", message: "You just tapped", preferredStyle: .Alert)
tapAlert.addAction(UIAlertAction(title: "OK", style: .Destructive, handler: nil))
self.presentViewController(tapAlert, animated: true, completion: nil)
}
//works but I've lost access to the sending object
If I add an argument to the Tap handler
func Tap(sender: UITapGestureRecognizer){
}
//crashes: [tapHandler]: unrecognized selector sent to instance
, it crashes on the Tap. I'd prefer to have a method with access to the sender object. Is there a way to manually create a GestureRecognizer handler and have access to the sender object, the same way you can when you do it through the Interface Builder?
Selector names are something that Swift inherits from Objective C. In Objective C a selector with single argument has name selector: as opposite to just selector, which means - no arguments. Change the name of the action in addTarget from Tap to Tap: and it should work then.