Trouble Referencing UIButton using NSMutableAttributedString in Instantiated View Controller - swift

As soon as my view controller loads, I am presented with a button (gray background with white font) that displays the text “Sto 1”. This is called in viewWillLayoutSubviews and the title is set using a NSMutableAttributedString. “Sto” is short for store.
For my application, I would like the user to be able to select the Sto 1 button and be able to store a number that is presented on a UILabel. I am able to grab the current number being displayed but I’m unable to update the text inside my Sto 1 button using NSMutableAttributedString. In other words I want to go from the button showing “Sto 1” to displaying some number (e.g., 12).
Thank you all for any help you may be able to provide me. I am still relatively new to Swift and I have been trying to resolve this issue over the past week.
import UIKit
class ViewController: UIViewController {
var fontConstant = CGFloat()
var someRandomNumberDisplayedOnAUILabel = String(12)
#IBOutlet weak var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillLayoutSubviews() {
fontConstant = 1
let myString = "Sto 1"
let myAttributes = [NSAttributedString.Key.foregroundColor : UIColor(red: 251/255.0, green: 251/255.0, blue: 251/255.0, alpha: 1.0), NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10 * fontConstant)]
let mutableAttributedString = NSMutableAttributedString(string: myString, attributes: myAttributes)
myButton.setAttributedTitle(mutableAttributedString, for: .normal)
myButton.backgroundColor = UIColor(red: 94/255.0, green: 94/255.0, blue: 94/255.0, alpha: 1.0)
}
#IBAction func myButtonAction(_ sender: Any) {
let myAttributes = [NSAttributedString.Key.foregroundColor : UIColor(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 1.0), NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10 * fontConstant)]
let mutableAttributedString = NSMutableAttributedString(string: someRandomNumberDisplayedOnAUILabel, attributes: myAttributes)
myButton.setAttributedTitle(mutableAttributedString, for: .normal)
myButton.backgroundColor = UIColor(red: 251/255.0, green: 251/255.0, blue: 251/255.0, alpha: 1.0)
}}

Normally, you would use setTitle(:for:) to change the text on a UIButton. But since you're working with an NSMutableAttributedString you will need the setAttributedTitle(:for:) function. I think this might be what you're looking for:
myButton.setAttributedTitle(myNSMutableAttributedString, for: .normal)
Heads up, though. You might need to call this function for the different control states and not just .normal otherwise you might see different text for an instant as the button is highlighted. Here is a list of the control states.
EDIT:I would try referencing the sender in the IBAction instead of myButton. This might be a quick fix:
#IBAction func myButtonAction(_ sender: Any) {
let myAttributes = [NSAttributedString.Key.foregroundColor : UIColor(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 1.0), NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10 * fontConstant)]
let mutableAttributedString = NSMutableAttributedString(string: someRandomNumberDisplayedOnAUILabel, attributes: myAttributes)
guard let button = sender as? UIButton else {
print("Error: sender was not a button")
return
}
button.setAttributedTitle(mutableAttributedString, for: .normal)
button.backgroundColor = UIColor(red: 251/255.0, green: 251/255.0, blue: 251/255.0, alpha: 1.0)
}
EDIT #2:If you're losing the reference to your IBOutlet you might be able to work around that by assigning a selector to the button before you lose it. Try this:
override func viewDidLoad() {
super.viewDidLoad()
// Add the action to the button rather than holding on to the IBOutlet
myButton.addTarget(self, action: #selector(RideInProgressViewController.myAction(sender:)), for: .touchUpInside)
}
#objc private func myAction(sender: Any) {
guard let button = sender as? UIButton else {
print("Error: sender was not a button")
return
}
let myAttributes = [NSAttributedString.Key.foregroundColor : UIColor(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 1.0), NSAttributedString.Key.font : UIFont.systemFont(ofSize: 10 * fontConstant)]
let mutableAttributedString = NSMutableAttributedString(string: someRandomNumberDisplayedOnAUILabel, attributes: myAttributes)
button.setAttributedTitle(mutableAttributedString, for: .normal)
button.backgroundColor = UIColor(red: 251/255.0, green: 251/255.0, blue: 251/255.0, alpha: 1.0)
}

Related

Swift How to process multiple button UI elegantly?

In my viewController I need to process button UI
I think my approach to process UI is not efficient
What if someday I need to do more button?
Today I just happen to have only three.....
Just want user press one of three, and others keep original color
like make user feel which one they press in many button
Here's my #IBAction
#IBAction func btnPress (_ sender: UIButton) {
let clickedBackgroundColor = UIColor(red: 1, green: 153, blue: 202, a: 1)
let clickedTextColor = UIColor(red: 255, green: 255, blue: 255, a: 1)
let originBackgroundColor = UIColor(red: 216, green: 247, blue: 250, a: 1)
let originTextColor = UIColor(red: 0, green: 71, blue: 88, a: 1)
switch sender {
case btnA:
btnA.backgroundColor = clickedBackgroundColor
btnA.setTitleColor(clickedTextColor, for: .normal)
btnA.underline()
btnB.backgroundColor = originBackgroundColor
btnB.setTitleColor(originTextColor, for: .normal)
btnC.backgroundColor = originBackgroundColor
btnC.setTitleColor(originTextColor, for: .normal)
case btnB:
btnB.backgroundColor = clickedBackgroundColor
btnB.setTitleColor(clickedTextColor, for: .normal)
btnB.underline()
btnA.backgroundColor = originBackgroundColor
btnA.setTitleColor(originTextColor, for: .normal)
btnC.backgroundColor = originBackgroundColor
btnC.setTitleColor(originTextColor, for: .normal)
case btnC:
btnC.backgroundColor = clickedBackgroundColor
btnC.setTitleColor(clickedTextColor, for: .normal)
btnC.underline()
btnA.backgroundColor = originBackgroundColor
btnA.setTitleColor(originTextColor, for: .normal)
btnB.backgroundColor = originBackgroundColor
btnB.setTitleColor(originTextColor, for: .normal)
default:break
}
}
And My UIButton Extension for adding underline
I can only add underline but how to remove it if user click other button?
extension UIButton {
func underline() {
guard let text = self.titleLabel?.text else { return }
let attributedString = NSMutableAttributedString(string: text)
attributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: NSRange(location: 0, length: text.count))
self.setAttributedTitle(attributedString, for: .normal)
}
}
I'm really new, not good at questioning if need more info just ask for me, Thanks.
You simply can create an Outlet Collection for all buttons instead of explicitly creating an array of buttons and use that to set the button's properties as per the selection, i.e.
#IBOutlet var buttons: [UIButton]!
Usage:
#IBAction func btnPress (_ sender: UIButton) {
let clickedBackgroundColor = UIColor(red: 1, green: 153, blue: 202, a: 1)
let clickedTextColor = UIColor(red: 255, green: 255, blue: 255, a: 1)
let originBackgroundColor = UIColor(red: 216, green: 247, blue: 250, a: 1)
let originTextColor = UIColor(red: 0, green: 71, blue: 88, a: 1)
buttons.forEach { (button) in
button.backgroundColor = (button == sender) ? clickedBackgroundColor : originBackgroundColor
button.setTitleColor((button == sender) ? clickedTextColor : originTextColor, for: .normal)
if button == sender {
button.underline()
}
}
}
Note: Also, there is no need to traverse the array twice. One single traversal will work fine.

How to enable a button in different cases in swift?

I want to disable, and enable a button in different cases:
If the user comes to my share viewController the button will be disabled:
func handleShareAndAbortButton() {
shareButton.isEnabled = false
abortButton.isEnabled = false
let attributedShareButtonText = NSAttributedString(string: shareButton.currentTitle!, attributes: [NSAttributedString.Key.foregroundColor: UIColor(red: 0, green: 0, blue: 0, alpha: 0.2) ])
let attributedabortButtonText = NSAttributedString(string: abortButton.currentTitle!, attributes: [NSAttributedString.Key.foregroundColor: UIColor(red: 0, green: 0, blue: 0, alpha: 0.2) ])
shareButton.setAttributedTitle(attributedShareButtonText, for: .normal)
abortButton.setAttributedTitle(attributedabortButtonText, for: .normal)
}
The handleShareAndAbortButton will be called in viewDidLoad
If something changed (picture or text) I want to enable the button:
func imageDidChange() {
selectedText = postTextView.text!
let isText = selectedText
let isImage = selectedImage != nil
if isImage || isText != "Schreibe etwas..." && isText != nil {
shareButton.isEnabled = true
abortButton.isEnabled = true
let attributedShareButtonText = NSAttributedString(string: shareButton.currentTitle!, attributes: [NSAttributedString.Key.foregroundColor: UIColor(red: 255, green: 255, blue: 255, alpha: 1.0) ])
let attributedabortButtonText = NSAttributedString(string: abortButton.currentTitle!, attributes: [NSAttributedString.Key.foregroundColor: UIColor(red: 0, green: 0, blue: 0, alpha: 1.0) ])
shareButton.setAttributedTitle(attributedShareButtonText, for: .normal)
abortButton.setAttributedTitle(attributedabortButtonText, for: .normal)
}
}
I call the imageDidChange in viewWillAppear
Here I delete the placeholder after begin editing:
extension ShareViewController: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
if postTextView.textColor == UIColor.lightGray {
postTextView.text = nil
postTextView.textColor = UIColor.black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if postTextView.text.isEmpty {
postTextView.text = "Schreibe etwas..."
postTextView.textColor = UIColor.lightGray
}
}
}
When the user now enter some text, the placeholder "Schreibe etwas..." will be gone and the text will be also != nil. But its not working.
Update
I found out the problem. When I delete the isText != "Schreibe etwas..."
it works. But I need this. How can I compare to a String? If I don't have this code, the user is able to upload always with placeholder text.
Thanks in advance for your help!
You never call imageDidChange after you changed the text.
Use:
func textViewDidEndEditing(_ textView: UITextView) {
if postTextView.text.isEmpty {
postTextView.text = "Schreibe etwas..."
postTextView.textColor = UIColor.lightGray
}
imageDidChange()
}
I see that you’re using UITextField. You probably want to have just placeholder "Schreibe etwas...". So, in viewDidLoad or in Storyboard set placeholder of your TextField to "Schreibe etwas...". Then you don’t need TextField’s delegate methods for setting text and color of text in delegate methods.
Well, you need it, but not for this. You have to call imageDidChange in textFieldDidEndEditing
Now change declaring isText. You just passed reference for selectedText. Don’t do this, just check if there is text
let isText = !selectedText.isEmpty // don’t forget to !
also rewrite your conditions
if isImage || isText
... you don’t need to check if isText isn’tnil

Issues creating toggle button

I am trying to create a button that reads "Special 1" then when it is clicked, the button reads "USED" and it stays as "USED" and doesn't allow the reader to toggle it back.
For example, the Twitter "follow" button can be pressed and then the button is changed to "following". However on Twitter, the user can click the "following" button and it will change back to "follow".
For my app, I want the user to be able to click "Special 1" and then the button is changed to "USED" and it remains that way forever.
Below is the attached code that I have created so far. The problem with my code so far is that the button doesn't read "Special 1" or "USED" when it is selected, the only thing it contains is the colors. The colors change how I want them to, but I can't get the words to appear and change.
import UIKit
class SpecialOneBTN: UIButton {
var isOn = false
override init(frame: CGRect) {
super.init(frame: frame)
initButton()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initButton()
}
func initButton() {
layer.borderWidth = 2.0
layer.cornerRadius = frame.size.height/2
setTitleColor(UIColor(red: 255.0/255.0, green: 193.0/255.0, blue: 95.0/255.0, alpha: 1.0), for: .normal)
addTarget(self, action: #selector(SpecialOneBTN.buttonPressed), for: .touchUpInside )
}
#objc func buttonPressed() {
activateButton(bool: !isOn)
}
func activateButton(bool: Bool) {
isOn = bool
let color = bool ? UIColor(red: 102.0/255.0, green: 102.0/255.0, blue: 100.0/255.0, alpha: 1.0) : UIColor(red: 255.0/255.0, green: 193.0/255.0, blue: 95.0/255.0, alpha: 1.0)
let titleColor = bool ? UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0) : UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
let title = bool ? "USED" : "Special 1"
setTitle(title, for: .normal)
setTitleColor(titleColor, for: .normal)
backgroundColor = color
}
}

UISegmentControl underline not on top in some cases

I'm trying to create a segment control with an underline for the selected segment. I've found a few code snippets to do this but they seem to exhibit the same behavior where the underline is beneath the UISegment of the UISegment Control. In this particular case, I'm using a UITableView custom cell with a UISegmentControl that has constraints to the top, left, bottom and right corners of the cell. Only the 2nd segment shows up properly. Here is what the debug view hierarchy looks like:
Here is the code that I'm using:
extension UISegmentedControl{
func removeBorder(){
let backgroundImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: self.bounds.size)
self.setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
let deviderImage = UIImage.getColoredRectImageWith(color: UIColor.white.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
self.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.gray], for: .normal)
self.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)], for: .selected)
}
func addUnderlineForSelectedSegment(){
removeBorder()
let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
let underlineHeight: CGFloat = 2.0
let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
let underLineYPosition = self.bounds.size.height - 5.0
let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
let underline = UIView(frame: underlineFrame)
underline.backgroundColor = UIColor(red: 67/255, green: 129/255, blue: 244/255, alpha: 1.0)
underline.tag = 1
self.addSubview(underline)
}
func changeUnderlinePosition(){
guard let underline = self.viewWithTag(1) else {return}
let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
UIView.animate(withDuration: 0.1, animations: {
underline.frame.origin.x = underlineFinalXPosition
})
}
}
Anyone have any ideas what is causing this?
This feels a bit hacky but I changed the following line:
self.addSubview(underline)
To the following:
for each in self.subviews {
each.addSubview(underline)
Now, it works properly. Even in the debug view hierarchy, I only see one underline UIView. Strange.
Still interested in any other answers that might be a better solution.

update/replace text in textview

I have a TextField in the first view controller where the user enter his name. In the second view controller i Have a form in textview where the user entered details to be replaced the specific word in the specific place.
I cannot able to find the right guidance from the various searches, where I can only find to format the text/color, etc. My object is to replace the existing text with user filled data.
import UIKit
class SandyaViewController: UIViewController, UITextFieldDelegate {
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "SViewController" {
if let nextVC = segue.destination as? SPViewController {
nextVC.yourName = self.nameTextField.text!
nextVC.address = self.addressTextField.text!
}
}
}
}
class SPViewController: UIViewController {
var yourName: String!
var address: String!
#IBOutlet weak var formTextView: UITextView!
myTextView.text = "This is to certify , that I yourname residing at home address....."
let attributedString = NSMutableAttributedString(string: "This is to certify , that I your name residing at home address .........\n")
let attributes1: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.foregroundColor: UIColor(red: 153/255, green: 51/255, blue: 255/255, alpha: 1.0),
NSAttributedStringKey.font: UIFont(name: "HelveticaNeue-Bold", size: 15)!,
NSAttributedStringKey.backgroundColor: UIColor(red: 255/255, green: 255/255, blue: 0/255, alpha: 1.0)
]
attributedString.addAttributes(attributes1, range: NSMakeRange(28, 9))
let attributes3: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.foregroundColor: UIColor(red: 230/255, green: 0/255, blue: 0/255, alpha: 1.0),
NSAttributedStringKey.backgroundColor: UIColor(red: 255/255, green: 255/255, blue: 0/255, alpha: 1.0)
]
attributedString.addAttributes(attributes3, range: NSMakeRange(51, 12))
Here I want the replace the attributes1 value with yourName and attributes3 value with address
myTextView.text = "This is to certify , that I attributes1 residing at home attributes3....."
I assume the I have detailed properly
Thanks