thii / FontAwesome.swift

Use FontAwesome in your Swift projects
MIT License
1.57k stars 266 forks source link

icon + text = text becomes icon if text match icon name #258

Open Slodin opened 4 years ago

Slodin commented 4 years ago

Hi,

In my toolbar item, I have set everything according to the doc page, it was working fine because I was making my app in a different language than English. However, now my app requires English as an alternative language option and this problem occurred.

`let barItemFontAttr = [ NSAttributedString.Key.font: UIFont.fontAwesome(ofSize: 15, style: .solid) ]

refreshOrders.setTitleTextAttributes(barItemFontAttr, for: .normal) refreshOrders.setTitleTextAttributes(barItemFontAttr, for: .selected) refreshOrders.title = "(String.fontAwesomeIcon(name: .sync)) Sync" //Don't work refreshOrders.title = "(String.fontAwesomeIcon(name: .sync)) 同步" //Works`

Expected: Sync Icon + "Sync"

Got: Sync Icon + Sync Icon

How am I suppose to do this?

MatrixSenpai commented 4 years ago

This is another problem I'm attempting to solve. When typing the name of an icon in English, the font auto-translates it to the appropriate icon, rather than just using Unicode. I have not yet been able to figure out why.

Are you initializing your UIBarButtonItem in Storyboard or inside a class?

Slodin commented 4 years ago

This is another problem I'm attempting to solve. When typing the name of an icon in English, the font auto-translates it to the appropriate icon, rather than just using Unicode. I have not yet been able to figure out why.

Are you initializing your UIBarButtonItem in Storyboard or inside a class?

It is a UIBarButtonItem in the Storyboard, but used in a viewcontroller class. I thought String.fontAwesomeIcon(name) is used to translate the icon into a string, but it seems to affect the entire title anyways with or without calling that function. It's almost as if it's matching anything that contains certain key words. I was trying to write "Routes Prev", it became: route Icon + "s Prev".

MatrixSenpai commented 4 years ago

You're correct, and this is a source of investigation at the moment. I have yet to determine what it is about the font that causes actual text to be translated into an FA icon. Currently, when using String.fontAwesomeIcon(name:), it's basically just the same as using FontAwesome.x_icon_name.unicode Until I can solve this issue, my recommendations are:

Here's a short code snippet to get you started with FA+NSAttributedString

let button = UIButton() // or link to one defined in your Storyboard
let label = UILabel()

// Note this MUST be Mutable for us to be able to apply an attribute
let string = NSMutableAttributedString(string: "Sync " + .fontAwesomeIcon(name: .chevronRight))

// If your icon is the first char in the text, use range: NSRange(location: 0, length: 1)
string.addAttribute(.font, value: UIFont.fontAwesome(ofSize: 18, style: .regular), 
                           range: NSRange(location: string.length - 1, length: 1))

button.setAttributedTitle(string, for: .normal)
label.attributedText = string

Hope this helps!

Slodin commented 4 years ago

Thanks for your reply, I did figure out a similar thing to yours for labels and buttons. However, UIBarButtonItem is a different story since it doesn't take attributed strings. Then I realized I can just stuff a button or label into the UIBarButtonItem...

So I figured a workaround for UIBarButtonItem, I didn't look into this problem until earlier since I had to do many more translations and this problem only happens at a few places for my project...I hate IOS localization lol. It's a super roundabout way...But I don't have any better ideas rn

View Controller

let routePreviewButton = UIBarButtonItem()! //linked from storyboard
let routeButton = UIButton()
//Add your button target, users will touch this instead of the UIBarButtonItem now in my testing
routeButton.addTarget(self, action: #selector(navToRoutes), for: .touchUpInside)
let routeIconTextAttr = generalHelper.getFAIconWithText(name: .map, size: 15, style: .solid, text: "Routes Prev.")
routeButton.setAttributedTitle(routeIconTextAttr, for: .normal)
routeButton.setAttributedTitle(routeIconTextAttr, for: .selected)
routeButton.sizeToFit()
routePreviewButton.customView = routeButton

GeneralHelper.getFAIconWithText

let icon = NSMutableAttributedString(string: String.fontAwesomeIcon(name: name), attributes: [.font: UIFont.fontAwesome(ofSize: CGFloat(size), style: style)])
let textToAppend = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: CGFloat(size))])
icon.append(textToAppend)
return icon

This approach still requires coders to adjust the button's appearance, but it's a basic start for me to at least get the bar buttons working.

Slodin commented 4 years ago

So If anyone needs this. Here is a BarButtonItem that works with this library in its current state. I'm still new to swift as I just came from Android development since our company is short-handed in the swift side. So there might be better ways to do this, but this works for me.

import UIKit
import FontAwesome_swift

@IBDesignable class FAIconBarButtonItem: UIBarButtonItem {
    //MARK: - Variable Declearation
    private let button = UIButton(type: .system)

    @IBInspectable private var size: CGFloat = 15 {
        didSet {
            button.titleLabel?.font = UIFont.systemFont(ofSize: size)
            setButtonIcon(icon: icon)
        }
    }

    @IBInspectable private var text: String? = "Default" {
        didSet {setButtonTitle(title: text)}
    }

    @IBInspectable private var normalColor: UIColor = .systemBlue {
        didSet {
            setButtonTitleColor(normalColor: normalColor)
            setButtonIcon(icon: icon)
        }
    }

    private var icon: FontAwesome = .powerOff {
        didSet {setButtonIcon(icon: icon)}
    }

    @IBInspectable private var iconAdapter: String {
        get {
            return self.icon.rawValue
        }
        set(iconString) {
            self.icon = FontAwesome(rawValue: iconString) ?? .powerOff
        }
    }

    //Used to provide addTarget action
    var touchUpInside: ((_ button: UIButton)->())?

    //MARK: - Init
    private override init(){
        super.init()
        target = self
        customView = setup()
    }

    required init?(coder aDecorder: NSCoder) {
        super.init(coder: aDecorder)
        customView = setup()
    }

    //MARK: - Private Functions
    private func setup() -> UIButton {
        button.isEnabled = true
        button.isUserInteractionEnabled = true
        button.titleLabel?.font = UIFont.systemFont(ofSize: size)
        setButtonTitleColor(normalColor: normalColor)
        setButtonIcon(icon: icon)
        setButtonTitle(title: text)
        button.tintColor = normalColor
        button.addTarget(self, action: #selector(touchUpInside(_:)), for: .touchUpInside)
        button.sizeToFit()
        return button
    }

    private func setButtonTitle(title: String?){
        button.setTitle(title?.localized(), for: .normal)
    }

    private func setButtonTitleColor(normalColor: UIColor){
        button.setTitleColor(normalColor, for: .normal)
    }

    private func setButtonIcon(icon: FontAwesome){
        //All changes of icon, color, and size is made in this
        let normalIconImage = UIImage.fontAwesomeIcon(name: icon, style: .solid, textColor: normalColor, size: CGSize(width: size, height: size))
        button.tintColor = normalColor
        button.setImage(normalIconImage, for: .normal)
    }

    @objc private func touchUpInside(_ sender: UIButton){
        touchUpInside?(sender)
    }

    //MARK: - Exposed Functions
    func setTitleAndIcon(title: String, icon: FontAwesome) {
        setIcon(icon: icon)
        setTitle(title: title)
    }

    func setTitle(title: String){
        self.text = title
    }

    func setIcon(icon: FontAwesome){
        self.icon = icon
    }

    func setColor(color: UIColor){
        self.normalColor = color
    }
}

Usage:

  1. Make your barButtonItem in your storyboard
  2. Use this FAIconBarButtonItem class
  3. You can customize a few things in the inspector

How to get button's action when pressed after linking your FABarButtonItem outlet In your ViewConroller

barButtonItem.touchUpInside = {
   //whatever you want to do
   //If you need other interaction methods, modify FABarButtonItem and use "touchUpInside" as an example.
}
//Change title
setTitle(title: String)

//Change icon
setIcon(icon: FontAwesome)

//Change title and icon
setTitleAndIcon(title: String, icon: FontAwesome)

//Change color
setColor(color: UIColor)

It should also work with code init but it's not in my use case so I didn't care, you can modify my code to get it working if it's not already.