How do you get text length in Swift 4? - uifont

I am not sure how to update this code:
func textWidth(text: String, font: UIFont?) -> CGFloat
{
let attributes = font?.fontDescriptor.fontAttributes
return text.size(withAttributes: attributes).width
}
Swift 4 complain:
Cannot convert value of type '[UIFontDescriptor.AttributeName : Any]?' to expected argument type '[NSAttributedStringKey : Any]?'
I don't know why they broke this, but it does not get automatically fixed.

(SWIFT 4 Updated)
func textWidth(text: String, font: UIFont?) -> CGFloat {
let attributes = font != nil ? [NSFontAttributeName: font] : [:]
return text.size(withAttributes: attributes).width
}
Hope it helps.

I guess, you want to write something like this:
(Swift 4)
func textWidth(text: String, font: UIFont?) -> CGFloat {
let attributes = font != nil ? [NSAttributedStringKey.font: font!] : [:]
return text.size(withAttributes: attributes).width
}
(Swift 3)
func textWidth(text: String, font: UIFont?) -> CGFloat {
let attributes = font != nil ? [NSFontAttributeName: font!] : [:]
return text.size(attributes: attributes).width
}
fontAttributes is not a valid input for size(withAttributes:) (size(attributes:) in Swift 3) even if the type matches.

An alternative way to do this in Swift 4, avoiding the handling of the optional, is like this:
extension UIFont {
func textWidth(s: String) -> CGFloat
{
return s.size(withAttributes: [NSAttributedStringKey.font: self]).width
}
}

Related

How convert NSColor to Color & vice versa on SwiftUI

I'm developing a macOS app with SwiftUI.
I have an extension to save the NSColors on Userdefaults & get it later.
I want to use this extension but cannot cast the SwiftUI Color to NSColor and vice versa.
The codes I tried so far was these:
let swiftUIColor = Color.red
let nsColor = NSColor(swiftUIColor)
or
let nsColor = NSColor.red
let swiftUIColor = Color(nsColor)
None of the above codes did work out.
Here is the extension to save & read NSColor on Userdefaults:
extension UserDefaults {
func color(forKey key: String) -> NSColor? {
guard let colorData = data(forKey: key) else { return nil }
do {
return try NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData)
} catch let error {
print("color error \(error.localizedDescription)")
return nil
}
}
func set(_ value: NSColor?, forKey key: String) {
guard let color = value else { return }
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false)
set(data, forKey: key)
} catch let error {
print("error color key data not saved \(error.localizedDescription)")
}
}
}

'advanced(by:)' is unavailable convert from swift 3 to swift 4

internal func rangeFromNSRange(_ nsRange: NSRange) -> Range<String.Index>? {
let from16 = utf16.startIndex.advanced(by: nsRange.location)
let to16 = from16.advanced(by: nsRange.length) //advanced(by:) is unavailable
if let from = String.Index(from16, within: self),
let to = String.Index(to16, within: self) {
return from ..< to
}
return nil
}
I have this file in swift 3 and I'm trying to convert it to swift 4 but I get this error and also this error
public func height(_ width: CGFloat, font: UIFont, lineBreakMode: NSLineBreakMode?) -> CGFloat {
var attrib: [String: AnyObject] = [NSAttributedStringKey.font.rawValue: font]
if lineBreakMode != nil {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = lineBreakMode!
attrib.updateValue(paragraphStyle, forKey: NSAttributedStringKey.paragraphStyle.rawValue)
}
let size = CGSize(width: width, height: CGFloat(Double.greatestFiniteMagnitude))
return ceil((self as NSString).boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes:attrib, context: nil).height)
}
// Cannot convert value of type '[String : AnyObject]' to expected argument type '[NSAttributedStringKey : Any]?'
In Swift 4 there is an API to make Range<String.Index> from NSRange and vice versa. The String extension can be reduced to
internal func range(from nsRange: NSRange) -> Range<String.Index>? {
return Range(nsRange, in: self)
}
In Swift 4 the type of string attributes has been changed from String to NSAttributedStringKey. For example NSForegroundColorAttributeName is replaced with NSAttributedStringKey.foregroundColor. You need to change the declaration of attrib to
var attrib: [NSAttributedStringKey: Any] = [.font: font]
and to change the line to add an attribute accordingly.

Cannot convert value of type 'UInt32!' to expected argument type 'UIFontDescriptorSymbolicTraits'

I´m still trying to convert TextKit from Objective-C to Swift. It drives me (and you ?) crazy :-(
After fixing some problems, now i´m having trouble with the bold (and italic) font:
func addOrRemoveFontTraitWithName(traitName: String, andValue traitValue: UInt32, andRange selectedRange: NSRange) {
let currentAttributesDict : NSDictionary! = self.textView.textStorage.attributesAtIndex(selectedRange.location, effectiveRange: nil)
let currentFont : UIFont = currentAttributesDict .objectForKey(NSFontAttributeName) as! UIFont
...
var existingTraitsWithNewTrait : UInt32! = nil
var changedFontDescriptor : UIFontDescriptor! = nil
if fontNameAttribute.rangeOfString(traitName).location == NSNotFound {
existingTraitsWithNewTrait = fontDescriptor.symbolicTraits.rawValue | traitValue
changedFontDescriptor = fontDescriptor.fontDescriptorWithSymbolicTraits(UIFontDescriptorSymbolicTraits.TraitBold)
} else {
existingTraitsWithNewTrait = fontDescriptor.symbolicTraits.rawValue & ~traitValue
changedFontDescriptor =
fontDescriptor.fontDescriptorWithSymbolicTraits(existingTraitsWithNewTrait) // !!!!
}
In the last line i get the error
Cannot convert value of type 'UInt32!' to expected argument type 'UIFontDescriptorSymbolicTraits'
Must i convert UInt32 to UIFontDescriptorSymbolicTraits? The other way is to use .rawValue. But i dont know, how to do this. :-(
Any help is welcome!
Try UIFontDescriptorSymbolicTraits(rawValue: existingTraitsWithNewTrait) in place of existingTraitsWithNewTrait in the error line.

Swift does not conform to protocol 'OS_dispatch_queue'

I'm trying to implement an asynchronous function as told in this topic but I always get the following error from Xcode : Type 'dispatch_queue_t!' does not conform to protocol 'OS_dispatch_queue'
Here's my code:
#IBAction func buyButton(sender: AnyObject) {
// get wanted symbol
let symbol = symbolField.text!
// get price of share
var priceShare :Double = 0
_ = lookup(symbol) { name, symbol, price in
dispatch_async(dispatch_get_main_queue()) {
priceShare = price
}
}
buy(symbol, number: 1, price: priceShare)
}
Here's the lookup function:
func lookup(entry : NSString, completion: ((name :String, symbol :String, price :String) -> Void)) {
// define return values
var name = String()
var symbol = String()
var price = String()
// define URL
let url = NSURL(string: "http://yahoojson.gobu.fr/symbol.php?symbol=\(entry)")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
if let urlContent = data {
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers)
name = jsonResult["name"] as! String
symbol = jsonResult["symbol"] as! String
price = jsonResult["price"]!!.stringValue as String
completion(name: name, symbol: symbol, price: price)
} catch {
print(error)
}
}
}
// run the task
task.resume()
}
Any hint on what I could be doing wrong?
I figure it out by myself. There was a bug inside on my code.
On the line
priceShare = price
I needed to put
priceShare = Double(price)!
since priceShare require a Double. Don't understand why Xcode didn't tell me so.

Swift Xcode 6: How do I use multiple functions in one statement?

I am a newcomer in xcode and swift, and I am having a problem with using two IBActions to allow a button to be enabled. I have 2 text fields, and I have a button that is disabled. I want the button to be enabled when both of the text fields are filled in. How can I do this? So far I have declared two functions with IBActions for the two text fields:
#IBAction func yourWeightEditingDidBegin(sender: AnyObject) {
}
#IBAction func calorieNumberEditingDidBegin(sender: AnyObject) {
}
Thanks!
One way is to use the UITextFieldDelegate function instead of IBOutlets:
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder()
if yourWeight.text != "" && calorieNumber.text != "" {
button.enabled = true
}
return true
}
I, too, implement UITextFieldDelegate, but I use shouldChangeCharactersInRange. This way, the status of that button changes as the user types:
If dealing with only two text fields, it looks like:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// get the value this text field will have after the string is replaced
let value: NSString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
// get the value of the other text field
let otherValue = textField == yourWeight ? calorieNumber.text : yourWeight.text
// only enable done if these are both non-zero length
doneButton.enabled = (value != "" && otherValue != "")
return true
}
Clearly, for this to work, you must specify the delegate for both of those text fields (either in IB or programmatically). I also generally marry the above with the "auto-enable return key" option for the text fields, too.
You don't. Instead, try something like this
var weightIsFilled = false
var calorieNumberIsFilled = false
#IBAction func yourWeightEditingDidBegin(sender: AnyObject) {
if valueOfWeightTextFieldIsValid() {
self.weightIsFilled = true
}
if self.weightIsFilled && self.calorieNumberIsFilled {
self.enableButton()
}
}
#IBAction func calorieNumberEditingDidBegin(sender: AnyObject) {
if valueOfCalorieNumberTextFieldIsValid() {
self.calorieNumberIsFilled = true
}
if self.weightIsFilled && self.calorieNumberIsFilled {
self.enableButton()
}
}
You also may want to be using IBAction functions that are called when the textfield's value changes, not when editing begins on it.

Resources