Swift: How to unwrap UITextField Optional String to Float - ios

I'm making an app, specifically a tip calculator, to practice my Swift skills. Currently using Swift 5.
I've set up the layout, labels, textfields, etc. But one problem I am running into is the optional string from the user input in text field box. I am trying to multiply the user input (String?) with the slider value, which is a Float.
I've tried several ways, force unwrapping, guard statements, if let, and type casting, but everything I get something similar where Xcode won't let me mix two types together. I've also gotten the text field input to an optional Float (Float?) but still, Xcode wants me to find a way to get the two types together.
My text field input box is a variable closure.
private let priceTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "$00.00"
textField.textColor = UIColor.black
textField.font = UIFont.init(name: "HelveticaNeue-Thin", size: 60)
textField.keyboardType = UIKeyboardType.numberPad
textField.textAlignment = .center
textField.borderStyle = UITextField.BorderStyle.roundedRect
textField.borderStyle = UITextField.BorderStyle.none
textField.sizeToFit()
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
This is my slider action function that will calculate the tip amount.
#objc func sliderChange() {
totalTipAmount.text = (tipSlider.value) * Float(priceTextField.text! as NSString)
myTipTextView.text = "My Tip (\(Int(tipSlider.value.rounded()))%):"
}

Your calculation seems fine but it might be throwing a compiler error for setting Float in place of String to totalTipAmount. You can try as follows,
#objc func sliderChange() {
guard let priceText = self.priceTextField.text, let price = Float(priceText) else { return }
totalTipAmount.text = "\(tipSlider.value * price)"
}

Related

Iterate through objects using their tag (swift)

I have a lot of buttons, labels, fields, on a form for which would like to set a number of properties (e.g., textAlignment, font, isEditable, backgroundColor, etc.). Because there are so many, I don't want to create outlets or have hundreds of lines of code (e.g.,
f1.textAlignment = .center
f2.textAlignment = .center
...
f1.isEditable = false
f2.isEditable = false
...
Since I can easily assign each a unique tag, how would I do this by iterating thought their tags instead?
Something along the lines of:
for tag in 0 ..< 75 {
object[tag].textAlignment = .center
object[tag].isEditable = false
}
(Or is there a better way?)
I don't like working with tags, but you can do something like this:
for tag in 0 ..< 75 {
let taggedView = view.viewWithTag(tag)
switch taggedView {
case let label as UILabel:
label.textAlignment = .center
case let button as UIButton:
button.backgroundColor = UIColor.blue
default:
print("could not cast view")
}
}
Just cast the view to each specific type, like UILabel or UIButton.
I would collect the controls into different arrays so that I have a button array, a text field array and so on and then loop over the arrays to set the properties
let buttonArray: [UIButton] = [button1, button2,...]
let textFieldArray: [UITextField] = [field1, field2,...
for field in textFieldArray {
field.textAlignment = .center
field.font = ...
}

How to go back when the UITextfield is empty in Swift code?

My question is: When the UITextField is empty, how do I click the "Backspace" button to go to the previous UITextField? I have been struggling trying to do this in my code below?
Second Question: How do I only allow 1 character to get entered in the UITextField?
I am new at Swift code and trying to learn. Any help would be great.
What I am trying to do is have the user be able to type in a code in the 6 UITextFields and be able to click the "Backspace" button on any one of the UITextFields with only allowing the user to enter one number in each UITextField.
Code Below:
#objc func textFieldDidChange(textfield: UITextField) {
let text = textfield.text!
if text.utf16.count == 0 {
switch textfield {
case textField2:
textField1.becomeFirstResponder()
textField1.backgroundColor = UIColor.blue
textField1.tintColor = .clear
case textField3:
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.blue
textField2.tintColor = .clear
case textField4:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.blue
textField3.tintColor = .clear
case textField5:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.blue
textField4.tintColor = .clear
case textField6:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.blue
textField5.tintColor = .clear
textField6.resignFirstResponder()
textField6.backgroundColor = UIColor.blue
textField6.tintColor = .clear
default:
break
}
}
else if text.utf16.count == 1 {
switch textfield {
case textField1:
textField1.backgroundColor = UIColor.black
textField1.textColor = .white
textField1.tintColor = .clear
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.black
textField2.textColor = .white
textField2.tintColor = .clear
case textField2:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.black
textField3.textColor = .white
textField3.tintColor = .clear
case textField3:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.black
textField4.textColor = .white
textField4.tintColor = .clear
case textField4:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.black
textField5.textColor = .white
textField5.tintColor = .clear
case textField5:
textField6.becomeFirstResponder()
textField6.backgroundColor = UIColor.black
textField6.textColor = .white
textField6.tintColor = .clear
case textField6:
textField6.resignFirstResponder()
default:
break
}
}
}
I'd just like to point out that I'm still relatively new to iOS and Swift in general, but even with just a few minutes of searching, I was able to find some seeds of ideas which provided me with the suggested solution.
Based on your (improved) question, I believe a different approach is required. What you really don't want to use a text component. "Why"?
I here you ask. Because they don't actually provide you with the functionality that you want and come with a considerable overhead.
For this, what you really want is more control. You want to know when a key is pressed and you want to respond to it (I know, sounds like a text component, but) and be notified when more extended functionality occurs, like the delete key is pressed.
After a few minutes of research, some trial and error, I found that the UIKeyInput is more along the lines of what you want.
It will tell you when text is inserted and, more importantly, will tell you when Delete is pressed
The added benefit is, you can filter the input directly. You can take the first character from the String and ignore the rest or auto fill the following elements with the remaining text. You can perform validation (for numerical only content) and what ever else you might want to do
So, I started a really new project, added a UILabel to the UIViewController in the storyboard, bound it to the source and implemented the UIKeyInput protocol as such...
class ViewController: UIViewController {
override var canBecomeFirstResponder: Bool {
return true
}
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
becomeFirstResponder()
}
}
extension ViewController: UIKeyInput {
var hasText: Bool {
return true
}
func insertText(_ text: String) {
print(text)
label.text = text
}
func deleteBackward() {
print("Delete backward")
}
}
I ran the project and when a key was typed, the label was updated with the new key and when delete was pressed, the Delete backward text was printed to console.
Now. You have some choices to make. To use a single UIViewController and (maybe) a series of UILabels and manage interactions within it, so when a key is typed, you present the next label as the input focus (and when delete is pressed, you move back) or do you create a series of UIControls which represent each digit and manage via some delegate call back process.
You may also need to implement the UITextInputTraits protocol, which will allow you to control the keyboard presented
You might also like to have a read through Responding to Keyboard Events on iOS, CustomTextInputView.swift and Showing the iOS keyboard without a text input which were just some of the resources I used to hobble this basic example together with.
you can use this extension for your second question:
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(
self,
action: #selector(limitLength),
for: UIControlEvents.editingChanged
)
}
}
#objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text,
prospectiveText.count > maxLength
else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
text = prospectiveText.substring(to: maxCharIndex)
selectedTextRange = selection
}
}
when you add this extension to your project you can see an extra attribute in "Attribute Inspector" tab and you can set the max length of UITextField.

UITextField placeholder text: adjust to fit

I have UITextField with longer text in it set as placeholder. What I want is for this placeholder text to adjust its font size when the width of field is too small.
I already tried this solution described in other posts (programmatically and in IB)
self.fieldTest.adjustsFontSizeToFitWidth = true
self.fieldTest.minimumFontSize = 10.0
What am I missing here?
You can create a subclass of UITextField:
class AutoSizeTextField: UITextField {
override func layoutSubviews() {
super.layoutSubviews()
for subview in subviews {
if let label = subview as? UILabel {
label.minimumScaleFactor = 0.3
label.adjustsFontSizeToFitWidth = true
}
}
}
}
Or you can just add some code in your view controller's viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
for subview in fieldTest.subviews {
if let label = subview as? UILabel {
label.minimumScaleFactor = 0.3
label.adjustsFontSizeToFitWidth = true
}
}
}
Here you go :
_myTextField.placeholder = #"SomeTextSomeTextSome";
UILabel *label = [_myTextField valueForKey:#"_placeholderLabel"];
label.adjustsFontSizeToFitWidth = YES;
Cheers!!
Based on answer 9: in Storyboard go to the identity inspector tab of the text field element, and under the "User Defined Runtime Attributes" section, add the following:
Here's a solution that depends on the undocumented fact that the UITextField has a child UILabel (actually UITextFieldLabel) to render the placeholder. The advantage of this solution over some others is that it degrades gracefully should Apple's implementation change. It also doesn't make assumptions about the existence of undocumented ivars.
Basically we extend UILabel via a category. If we see ourselves being parented to a UITextField then we turn on adjustFontSizeToFitWidth.
#interface UILabel (TS)
#end
#implementation UILabel (TS)
- (void) didMoveToSuperview
{
[super didMoveToSuperview];
if ( [self.superview isKindOfClass: [UITextField class]] ) {
self.adjustsFontSizeToFitWidth = YES;
}
}
#end
After reviewing the class reference for UITextField's, it seems that adjustsFontSizeToFitWidth only affects the the text property of the UITextField and not the placeholder property. While I don't know off the top of my head a way to get the placeholder to respond to adjustsFontSizeToFitWidth, I can suggest two hacky ideas that may give you the appearance that you want. Just be aware that I'm not near a Mac right now so I haven't tested these ideas:
1:
Since a placeholder is just text with a 70% gray color, you could set the label's text property to be whatever you need it to be, and then implement the UITextFieldDelegate's textFieldShouldBeginEditing method to clear the text and change the color back to normal. You would also have to implement the textFieldShouldClear and textFieldDidEndEditing methods to replace the pseudo-placeholder back in the UITextField and change the text color back to 70% gray.
2:
In viewWillAppear you could set the UITextField's text to what your placeholder should be, create a UIFont object and set it equal to the UITextField's font property, clear the UITextField's text, and set the placeholder to be an NSAttributedString with the font object as a property. Here's an example of what I mean:
-(void)viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
someTextField.adjustsFontSizeToFitWidth = YES;
someTextField.text = #"placeholderText";
UIFont *font = someTextField.font;
someTextField.text = nil;
NSDictionary *attributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
NSAttributedString *placeholderString= [[NSAttributedString alloc] initWithString:#"placeholderText" attributes:attributes];
someTextField.placeholder = placeholderString;
}
Edit: Just noticed the swift tag. I wrote my code in Objective-C, but you should be able to easily translate it to Swift.
Try using attributed placeholder instead of normal place holder
Try this
let attributedplaceholder = NSAttributedString(string: "placeholdertext", attributes: [NSFontAttributeName: UIFont(name: "FontName", size: 10)!])
self.fieldTest.attributedPlaceholder = attributedplaceholder
You can add additional attributes to the placeholder like textcolor and other
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
for subView in fieldTest.subviews{
if subView .isKind(of: UILabel.self){
let label = subView as! UILabel
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.2
}
}
}
Swift
Feel free to improve the extension - pretty sure there is a more elegant way to iterate over the subviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tfCountryCode.allSubviewsOfClass(UILabel.self).forEach {
$0.adjustsFontSizeToFitWidth = true
$0.minimumScaleFactor = 0.5
}
}
extension UIView {
func allSubviewsOfClass<K: UIView>(_ clazz: K.Type) -> [K] {
var matches = [K]()
if subviews.isEmpty { return matches }
matches.append(contentsOf: subviews.filter { $0 is K } as! [K])
let matchesInSubviews = subviews.flatMap { return $0.allSubviewsOfClass(clazz) }
matches.append(contentsOf: matchesInSubviews.flatMap { $0 })
return matches
}
}
My solution:
if let label = yourTextField.value(forKey: "placeholderLabel") as? UILabel {
label.adjustsFontSizeToFitWidth = true
}
And don't forget this:
yourTextField.minimumFontSize = 10 // 10 is an example, pass your minimumFontSize
Tested and run perfectly.
You can use these two solution:
1.If You have fixed font size if Textfield size is less than placeholder text:
let placeholderString = testTF.placeholder
print(placeholderString!)
let font = UIFont(name: (testTF.font?.fontName)!, size: 16)!
let fontAttributes = [NSFontAttributeName: font]
let size = (placeholderString! as NSString).sizeWithAttributes(fontAttributes)
print(size)
print(testTF.frame.size.width)
if(size.width > testTF.frame.size.width)
{
let font = UIFont(name: (testTF.font?.fontName)!, size: 4)!
let attributes = [
NSForegroundColorAttributeName: UIColor.lightGrayColor(),
NSFontAttributeName : font]
testTF.attributedPlaceholder = NSAttributedString(string: placeholderString!,
attributes:attributes)
}
else
{
let attributes = [
NSForegroundColorAttributeName: UIColor.lightGrayColor(),
NSFontAttributeName : font]
testTF.attributedPlaceholder = NSAttributedString(string: placeholderString!,
attributes:attributes)
}
2) If you want dynamic font size than you just check the above condition for width of textfield and placeholder text size.width. if the placeholder text size is greater than textfield size than create one label inside the textfield and set minimum font on that.
if(size.width > testTF.frame.size.width)
{
placeholder = UILabel(frame: CGRect( x: 0, y: 0, width: testTF.bounds.width, height: testTF.bounds.height))
placeholder.text = placeholderString
placeholder.numberOfLines = 1;
//placeholder.minimumScaleFactor = 8;
placeholder.adjustsFontSizeToFitWidth = true
placeholder.textColor = UIColor.grayColor()
placeholder.hidden = !testTF.text!.isEmpty
placeholder.textAlignment = .Center
testTF.addSubview(placeholder)
}
In Swift
yourTextField.subviews
.filter { $0 is UILabel }
.flatMap { $0 as? UILabel }
.forEach {
$0.adjustsFontSizeToFitWidth = true
$0.minimumScaleFactor = 0.5
}
it works :)
Try this: It's working fine without any issues:
yourTextField.placeholder = "Adjust placeHolder text for textFields iOS"
let label = yourTextField.value(forKey: "_placeholderLabel") as? UILabel
label?.adjustsFontSizeToFitWidth = true
The whole approach or shrinking font size to fit is misguided in the day and age of accessibility.
Firstly you have zero business specifying text size in the first place, let alone shrinking that further: you have to rely on the accessibility API.
Thus if the placeholder is likely to not fit it has to be placed
as a UILabel preceding the UITextField. The placeholders are supposed to be SHORT and fit without clippage.
To determine if it's clipped I guess you could use - (CGRect)placeholderRectForBounds:(CGRect)bounds; but then you are in
murky waters of using an API which Apple says you should only override (but not call yourself even though it's probably meaningful and safe
within the confines of didlayoutsubviews method[s])
If placeholder text is dynamic (server served) dump it into a UILabel.

Lag using runAction with a SKLabelNode on swift

I am having a lag issue with this function that is used a lot of times in my app...
plusOne(scorelabel.position,plus: 1)
And:
func plusOne(position: CGPoint, plus : Int) {
myLabel.setScale(1)
myLabel.text = "+"+String(plus)
myLabel.position = position
myLabel.hidden = false
let action1 = SKAction.scaleTo(2, duration: 0.5)
let action2 = SKAction.fadeOutWithDuration(0.5)
let actionGroup = SKAction.group([action1,action2])
myLabel.runAction(actionGroup,completion: {
self.myLabel.hidden = true
})
}
The first time I use the plusOne function, always make my app be freezed for a little time...
I do not know if I have been doing the things well... myLabel has been declared global but it is the same... always with lag on the first execution.
You need to set the font of your label with a fix font at start.
Like that:
let yourFont = UIFont(name: "yourfontName", size: 17)
var myLabel = SKLabelNode(fontNamed: yourFont?.fontName)
Otherwise, your font gets loaded at the first usage and not on app-start.

How to change the alignment of a UISearchBar according to the input language

I would like to be able to input text in either Left-to-Right and Right-to-Left languages into a UISearchBar. This means that once a user has started inputing text in right to left languages, I would like the text alignment to be to the right and vice versa.
The question is - Is there any way to catch these events of language switching? Thanks for your help.
Use this code in ViewDidLoad
searchbar.semanticContentAttribute = .forceRightToLeft
Yes there is. You want to subscribe to UITextInputCurrentInputModeDidChangeNotification. See the UITextInputMode Class Reference.
func customizeSearchBar() {
//For placeHolder to allign Right
searchBar.semanticContentAttribute = .forceRightToLeft
// for text field to align right
if let textfield = searchBarText.value(forKey: "searchField") as?
UITextField {
textfield.backgroundColor = UIColor.clear
textfield.textAlignment = .right
if let leftView = textfield.leftView as? UIImageView {
leftView.image = leftView.image?.withRenderingMode(.alwaysTemplate)
leftView.tintColor = UIColor.clear
}
}
}

Resources