Xcode 6.1 Attribute Dictionaries in Swift - xcode

After upgrading to Xcode 6.1 Beta 2 from Xcode 6 Beta 7, the following no longer works:
let font = UIFont(name: "Arial", size: 16)
let colour = UIColor.redColor()
let attributes = [NSFontAttributeName: font, NSForegroundColorAttributeName: colour]
I have tried specifically declaring the dictionary as
let attributes: [NSString : AnyObject] = [NSFontAttributeName: font, NSForegroundColorAttributeName: colour]
but I am receiving the error "Cannot convert ... 'Dictionary' to 'NSString!'". Declaring the key as NSString! rather than NSString complains that NSString! is not hashable. Any clues?

Sorted. As usual, the actual error is a red herring. UIFont(name: , size:) now has an init? initialiser, and so is optional. Correct usage is now :
let font = UIFont(name: "Arial", size: 16)! // Unwrapped
let colour = UIColor.redColor()
let attributes: [NSString : AnyObject] = [NSFontAttributeName: font, NSForegroundColorAttributeName: colour]
or, more correctly:
if let font = UIFont(name: "Arial", size: 16) {
let colour = UIColor.redColor()
let attributes: [NSString : AnyObject] = [NSFontAttributeName: font, NSForegroundColorAttributeName: colour]
// ...
}

Related

iOS 13 - UITextField with Placeholder getting app crash

In iOS 13, I'm getting a crash when accessing the UITextField _placeholderLabel.textColor label key.
The key used to apply placeholder text color.
[textfield setValue:[UIColor whiteColor] forKeyPath:#"_placeholderLabel.textColor"];
"NSGenericException" - reason: "Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug"
You can do it by using runtime:
add the following code to the bottom of placeholder setting
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
UILabel *placeholderLabel = object_getIvar(textField, ivar);
placeholderLabel.textColor = [UIColor whiteColor];
At Xcode 11 beta2 ,this code is work ,but I don't know about GM version or official version.
The complete code:
Objective-C Version
#import "ViewController.h"
#import <objc/runtime.h>
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
self.title = #"UITextField Demo";
UITextField *textField = [UITextField new];
textField.frame = CGRectMake(0, 100, 300, 50);
textField.placeholder = #"UITextField Demo";
[self.view addSubview:textField];
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
UILabel *placeholderLabel = object_getIvar(textField, ivar);
placeholderLabel.textColor = [UIColor whiteColor];
}
#end
Swift Version:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
let textField = UITextField()
textField.frame = CGRect(x: 0, y: 100, width: 300, height: 50)
textField.placeholder = "UITextField Demo"
view.addSubview(textField)
let iVar = class_getInstanceVariable(UITextField.self, "_placeholderLabel")!
let placeholderLabel = object_getIvar(textField, iVar) as! UILabel
placeholderLabel.textColor = .red
}
}
2019/09/25 Update
The above implementation can solve the problem ,but it not be advocated.
The apps that use the private api maybe broken in the future.
Please use new api :
var attributedPlaceholder: NSAttributedString? { get set }
Discussion
This property is nil by default. If set, the placeholder string is drawn using system-defined color and the remaining style information (except the text color) of the attributed string. Assigning a new value to this property also replaces the value of the placeholder property with the same string data, albeit without any formatting information. Assigning a new value to this property does not affect any other style-related properties of the text field.
The complete code:
let textField = UITextField()
textField.frame = CGRect(x: 0, y: 100, width: 300, height: 50)
let placeholderString = NSAttributedString.init(string: "UITextField Demo", attributes: [NSAttributedString.Key.foregroundColor : UIColor.red])
textField.attributedPlaceholder = placeholderString
view.addSubview(textField)
I think it's a bug from the XCode side and hopefully, they will fix this one on the next release. You can quickly fix this by Erase all Contents and Settings on the Simulator device.
Yes this problem is related with the Xcode version. I have Xcode 11.2.1 and i am facing same issue.
_placeholderLabel.textColor
if you are using it from storyboard as runtime var and getting issue so just remove "_"
like that
after removing the "_" runtime Var start working
Remove Underscore "_" and try this.
[textfield setValue:[UIColor whiteColor] forKeyPath:#"placeholderLabel.textColor"];
It should work for all versions!
I did it this way :
Objc
NSMutableAttributedString *placeholderAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:searchField.attributedPlaceholder];
[placeholderAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, [placeholderAttributedString length])];
searchField.attributedPlaceholder = placeholderAttributedString;
Swift 5
var placeholderAttributedString = NSMutableAttributedString(attributedString: searchField.attributedPlaceholder)
placeholderAttributedString.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: 0, length: placeholderAttributedString.length))
searchField.attributedPlaceholder = placeholderAttributedString
It's a bit long but I have nothing better for the moment.
if you are using UITextView+Placeholder.h class please change below method Clean and build if issue persists
Objective C
+(UIColor *)defaultPlaceholderColor {
static UIColor *color = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UITextField *textField = [[UITextField alloc] init];
textField.placeholder = #" ";
// color = [textField valueForKeyPath:#"_placeholderLabel.textColor"];
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
UILabel *placeholderLabel = object_getIvar(textField, ivar);
color = placeholderLabel.textColor;
});
return color;
}
You can set UITextField placeholder text color, font using attributedPlaceholder method as follows :
NSString *text = [textField text];
textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:text attributes:#{NSForegroundColorAttributeName: [UIColor redColor], NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Medium" size:14.0f]}];
"Erase all contents and settings"
This worked for me.
I removed the var "_placeholderLabel" in the section "User Defined Runtime Attributes"
Just replace _placeholderLabel with placeholderLabel
For IOS 13 you can set UITextField placeholder color by one line code.
Objective C
[textField setAttributedPlaceholder:[[NSAttributedString alloc] initWithString:#"PlaceHolder Text" attributes:#{NSForegroundColorAttributeName:[UIColor whiteColor]}]];
In Swift 5
txtTitle.attributedPlaceholder = NSAttributedString(string:"PlaceHolder Text", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
- Swift Version
Add the following code to the bottom of placeholder setting:
let iVar = class_getInstanceVariable(UITextField.self, "_placeholderLabel")!
let placeholderLabel = object_getIvar(textField, iVar) as! UILabel
placeholderLabel.textColor = .white
For example:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
let textField = UITextField()
textField.frame = CGRect(x: 0, y: 100, width: 300, height: 50)
textField.placeholder = "UITextField Demo"
view.addSubview(textField)
let iVar = class_getInstanceVariable(UITextField.self, "_placeholderLabel")!
let placeholderLabel = object_getIvar(textField, iVar) as! UILabel
placeholderLabel.textColor = .white
}
Swift 5
Here is a correct Swift replacement of that property.
Note that if you also change alignment of other properties for the text field, then you need to set these properties to placeholder's attributed text too. Usually only color and font are changed:
/// Set placeholder text color
/// - Parameter color: the color
func setPlaceholderColor(_ color: UIColor) {
// Color
var attributes: [NSAttributedString.Key: Any] = [.foregroundColor: color]
var range = NSRange(location: 0, length: 1)
// Font
if let text = attributedText, text.length > 0, let attrs = attributedText?.attributes(at: 0, effectiveRange: &range), let font = attrs[.font] {
attributes[.font] = font
}
else if let font = font {
attributes[.font] = font
}
self.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: attributes)
}
FOR UISEARCHBAR PLACEHOLDER COLOR
Xcode 11.1
From iOS 13 onwards the SDK provides UISearchBar.searchTextField so we can use
public API instead of private API. In the subclass of UISearchBar i have used this code to change the placeholder color
UITextField *textField = [self findSubviewOfClass:[UITextField class]];
textField.textColor = [UIColor whiteColor];
//textField.font = [UIFont fontWithName:#"HelveticaNeue-Regular" size:14.0f];
if (#available(iOS 13.0, *)) {
self.searchTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder attributes:#{NSForegroundColorAttributeName: [UIColor colorWithRed:0.8 green:0.82 blue:0.81 alpha:1]}];
}else {
[textField setValue:[UIColor colorWithRed:0.8 green:0.82 blue:0.81 alpha:1] forKeyPath:#"_placeholderLabel.textColor"];
}
Remove "_" form "_placeholderLabel.textColor" try with "placeholderLabel.textColor".

Convert Obj-C to Swift

Trying to override drawPlaceholderInRect in Swift2.1 so that I change the text color and I am having a hard time converting the following Obj-C to Swift:
(void)drawPlaceholderInRect:(CGRect)rect
{
UIColor *colour = [UIColor whiteColor];
if ([self.placeholder respondsToSelector:#selector(drawInRect:withAttributes:)]) {
NSDictionary *attributes = #{NSForegroundColorAttributeName: colour, NSFontAttributeName: self.font};
CGRect boundingRect = [self.placeholder boundingRectWithSize:rect.size options:0 attributes:attributes context:nil];
[self.placeholder drawAtPoint:CGPointMake(0, (rect.size.height/2)-boundingRect.size.height/2) withAttributes:attributes];
}
I got this:
public override func drawPlaceholderInRect(rect: CGRect) {
let textDict: NSDictionary = [NSForegroundColorAttributeName: UIColor.whiteColor()]
...
..
.
}
Can someone help out please?
func drawPlaceholder(in rect: CGRect) {
let colour: UIColor? = UIColor.white
if placeholder.responds(to: Selector("drawInRect:withAttributes:")) {
let attributes = [NSForegroundColorAttributeName: colour, NSFontAttributeName: font]
let boundingRect: CGRect = placeholder.boundingRect(with: rect.size, options: [], attributes: attributes, context: nil)
placeholder.draw(at: CGPoint(x: 0, y: (rect.size.height / 2) - boundingRect.size.height / 2), withAttributes: attributes)
}
}
See if this is helpful:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216-CH2-ID0
Also, if you have trouble translating Objective-C to Swift, which can be tricky sometimes, consider overriding the method in a derived Objective-C class and then using that class in Swift.

Unarchive NSData back to NSColor in swift

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

Cocoa NSTextField change placeholder color

I try to change the placeholder text color. This code doesn't work:
let color = NSColor.redColor()
let attrs = [NSForegroundColorAttributeName: color]
let placeHolderStr = NSAttributedString(string: "My placeholder", attributes: attrs)
myTextField.placeholderAttributedString = placeHolderStr
I get the error -[NSTextField setPlaceholderAttributedString:]: unrecognized selector sent to instance. Any ideas, how I can change the color of the placeholder?
UPDATE:
This works:
(myTextField.cell() as NSTextFieldCell).placeholderAttributedString = placeHolderStr
UPDATE 2:
Hmm, it changes the color, but if the text field gets the focus, the placeholder font size get's smaller, very strange.
By explicitly defining the font of the NSAttributedString, the placeholder font resizing referred to in the original question is fixed.
The following is a working example in Swift 3.0.
let color = NSColor.red
let font = NSFont.systemFont(ofSize: 14)
let attrs = [NSForegroundColorAttributeName: color, NSFontAttributeName: font]
let placeholderString = NSAttributedString(string: "My placeholder", attributes: attrs)
(textField.cell as? NSTextFieldCell)?.placeholderAttributedString = placeholderString
The following is a working example in Swift 4.2.
let attrs = [NSAttributedString.Key.foregroundColor: NSColor.lightGray,
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 14)]
let placeholderString = NSAttributedString(string: "My placeholder", attributes: attrs)
(taskTextField.cell as? NSTextFieldCell)?.placeholderAttributedString = placeholderString
You should set the placeholder text in NSTextFieldCell and not NSTextField.
myTextField.cell.placeholderAttributedString = placeHolderStr
You should keep current font, and current value from IB
extension NSTextField {
func setHintTextColor (color: NSColor) {
let currentHint = placeholderString ?? ""
let placeholderAttributes: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.foregroundColor: color,
NSAttributedString.Key.font: font!
]
let placeholderAttributedString = NSMutableAttributedString(string: currentHint, attributes: placeholderAttributes)
let paragraphStyle = NSMutableParagraphStyle()
placeholderAttributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0,length: placeholderAttributedString.length))
self.placeholderAttributedString = placeholderAttributedString
}
}

Xcode: Is there a way to change line spacing (UI Label) in interface builder?

I've got several UILabels with multiple lines of text, but the line spacing is larger than I would prefer. Is there any way to change this?
hi this is a late reply but it may help some one line height can be change change the text from plain to attribute
Since iOS 6, Apple added NSAttributedString to UIKit, making it possible to use NSParagraphStyle to change the line spacing.
To actually change it from NIB, please see souvickcse's answer.
Because I friggin hate using attributed text in interface builder (I always run into IB bugs), here is an extension to allow you to set line height multiple directly to a UILabel in interface builder
extension UILabel {
#IBInspectable
var lineHeightMultiple: CGFloat {
set{
//get our existing style or make a new one
let paragraphStyle: NSMutableParagraphStyle
if let existingStyle = attributedText?.attribute(NSAttributedString.Key.paragraphStyle, at: 0, effectiveRange: .none) as? NSParagraphStyle, let mutableCopy = existingStyle.mutableCopy() as? NSMutableParagraphStyle {
paragraphStyle = mutableCopy
} else {
paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 1.0
paragraphStyle.alignment = self.textAlignment
}
paragraphStyle.lineHeightMultiple = newValue
//set our text from existing text
let attrString = NSMutableAttributedString()
if let text = self.text {
attrString.append( NSMutableAttributedString(string: text))
attrString.addAttribute(NSAttributedString.Key.font, value: self.font, range: NSMakeRange(0, attrString.length))
}
else if let attributedText = self.attributedText {
attrString.append( attributedText)
}
//add our attributes and set the new text
attrString.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attrString.length))
self.attributedText = attrString
}
get {
if let paragraphStyle = attributedText?.attribute(NSAttributedString.Key.paragraphStyle, at: 0, effectiveRange: .none) as? NSParagraphStyle {
return paragraphStyle.lineHeightMultiple
}
return 0
}
}

Resources