SkyFloatingLabelTextField icon indicating copy to clipboard operation
SkyFloatingLabelTextField copied to clipboard

Localization

Open Maryom opened this issue 8 years ago • 22 comments

Hey,

Thanks for your awesome repo. I need to localize the fields to Arabic language, as you can see in the attached image, the fields turned to the right perfectly. However, the placeholders didn't change although I wrote: "YGo-es-Lf5.placeholder" = "كلمة المرور"; in Main.strings(Arabic) file.

screen shot 2017-05-11 at 12 08 23 pm

Any help?

Thanks.

Maryom avatar May 11 '17 09:05 Maryom

I don't see such problem on my App in RTL languages. img_1137 Do you have a sample code that reproduce this problem?

pichirichi avatar May 11 '17 12:05 pichirichi

Thanks for your reply, I set placeholder values in storyboard, Do you think that I have to set them programmatically ?

Maryom avatar May 11 '17 14:05 Maryom

I don't think it will make a different. Can you provide a sample code?

pichirichi avatar May 14 '17 05:05 pichirichi

Hey,

I set all the attributes in storyboard as you can see in the below image:

screen shot 2017-05-14 at 9 52 05 am

Then, I connected it: @IBOutlet weak var loginEmailInputView: SkyFloatingLabelTextField!

After that I wrote: "BcR-hT-edQ.placeholder" = "يرجى إدخال بريدك الإلكتروني."; in Main.strings(Arabic) file.

Any help?

Thank you so much.

Maryom avatar May 14 '17 06:05 Maryom

When is "BcR-hT-edQ.placeholder" = "يرجى إدخال بريدك الإلكتروني."; set? You have placeholder set the Storyboard and then set it again in code? Remove the line in your screenshot.

bogren avatar May 22 '17 10:05 bogren

@bogren Thanks for your reply. You have placeholder set the Storyboard and then set it again in code? I just set it in Storyboard for English language. Then I wrote this 👇🏼 line in Main.strings(Arabic) file to localize it: "BcR-hT-edQ.placeholder" = "يرجى إدخال بريدك الإلكتروني."; Still it doesn't work :( Any help?

Maryom avatar May 23 '17 07:05 Maryom

Ah now I understand your setup and your problem. Can you provide sample code?

bogren avatar May 23 '17 07:05 bogren

@bogren I set all the attributes in storyboard. Then, I connected it: @IBOutlet weak var loginEmailInputView: SkyFloatingLabelTextField!

After that I wrote: "BcR-hT-edQ.placeholder" = "يرجى إدخال بريدك الإلكتروني."; in Main.strings(Arabic) file.

Maryom avatar May 23 '17 07:05 Maryom

Does it work for other languages? Is other labels and test translated? If you create a sample project and upload here with the same setup I can take a look at the code.

bogren avatar May 23 '17 08:05 bogren

Does it work for other languages? I just need Arabic and English.

Is other labels and test translated? Yes all localization work perfectly, except SkyFloatingLabelTextField :(

Maryom avatar May 23 '17 08:05 Maryom

hmm.. 🤔 I'll try to setup a demo project.

bogren avatar May 23 '17 09:05 bogren

@bogren Thank you so much 🙏🏼

Maryom avatar May 23 '17 09:05 Maryom

@Maryom How are you getting on?

k0nserv avatar Jun 21 '17 11:06 k0nserv

@k0nserv Thanks. I got busy with other projects and I didn't check it again. but all the needed information are provided above, what extra information you need?

Maryom avatar Jun 21 '17 13:06 Maryom

Can you reproduce it in a small example project?

k0nserv avatar Jun 21 '17 13:06 k0nserv

I will try ASAP. Thank you.

Maryom avatar Jun 21 '17 14:06 Maryom

I had a same problem. when you open the attributes inspector, you can see two placeholders. don't input the first one, use the second Placeholder on the Text Field section. It simply worked for me.

neatree avatar Sep 26 '17 07:09 neatree

@neatree Thank you 🙏🏼

Maryom avatar Sep 26 '17 11:09 Maryom

I can confirm that this issue exists, and @neatree 's solution works perfectly

ebeem avatar Nov 30 '17 17:11 ebeem

Hello there , I have the same problem . when the app started and i choose Arabic language to be the app language, the skyfloating textfield still exists on the left not on the right. but i noticed that when i close the app and open it again everything works well at its place. Any Help please ? Thanks in advance

hendksoliman avatar Feb 11 '19 09:02 hendksoliman

I think we should start setting textAlignment to NSTextAlignmentNatural everywhere and get rid of the custom RTL stuff. Since iOS 9.0 this should be the solution. I can look at this, but if anyone wants to PR it I'd be happy

k0nserv avatar Feb 11 '19 11:02 k0nserv

Hey,

Thanks for your awesome repo. I need to localize the fields to Arabic language, as you can see in the attached image, the fields turned to the right perfectly. However, the placeholders didn't change although I wrote: "YGo-es-Lf5.placeholder" = "كلمة المرور"; in Main.strings(Arabic) file.

screen shot 2017-05-11 at 12 08 23 pm

Any help?

Thanks.

import UIKit import MOLH

@IBDesignable open class CustomTextField: UITextField { // swiftlint:disable:this type_body_length /** A Boolean value that determines if the language displayed is LTR. Default value set automatically from the application language settings. */ @objc open var isLTRLanguage: Bool = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight { didSet { updateTextAligment() } }

func isEnglishApp() -> Bool {
    if MOLHLanguage.isRTLLanguage() { return false } else { return true }}

fileprivate func updateTextAligment() {
    if isEnglishApp() {
        self.textAlignment = .left
        self.titleLabel.textAlignment = .left
    } else {
        self.textAlignment = .right
        self.titleLabel.textAlignment = .right
    }
}

// MARK: Animation timing

/// The value of the title appearing duration
@objc dynamic open var titleFadeInDuration: TimeInterval = 0.2
/// The value of the title disappearing duration
@objc dynamic open var titleFadeOutDuration: TimeInterval = 0.3

// MARK: Colors

fileprivate var cachedTextColor: UIColor?

/// A UIColor value that determines the text color of the editable text
@IBInspectable
override dynamic open var textColor: UIColor? {
    set {
        cachedTextColor = newValue
        updateControl(false)
    }
    get {
        return cachedTextColor
    }
}

/// A UIColor value that determines text color of the placeholder label
@IBInspectable dynamic open var placeholderColor: UIColor = UIColor.lightGray {
    didSet {
        updatePlaceholder()
    }
}

/// A UIFont value that determines text color of the placeholder label
@objc dynamic open var placeholderFont: UIFont? {
    didSet {
        updatePlaceholder()
    }
}

fileprivate func updatePlaceholder() {
    guard let placeholder = placeholder, let font = placeholderFont ?? font else {
        return
    }
    let color = isEnabled ? placeholderColor : disabledColor
    #if swift(>=4.2)
    attributedPlaceholder = NSAttributedString(
        string: placeholder,
        attributes: [
            NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: font
        ]
    )
    #elseif swift(>=4.0)
    attributedPlaceholder = NSAttributedString(
        string: placeholder,
        attributes: [
            NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.font: font
        ]
    )
    #else
    attributedPlaceholder = NSAttributedString(
        string: placeholder,
        attributes: [NSForegroundColorAttributeName: color, NSFontAttributeName: font]
    )
    #endif
}

/// A UIFont value that determines the text font of the title label
@objc dynamic open var titleFont: UIFont = .systemFont(ofSize: 13) {
    didSet {
        updateTitleLabel()
    }
}

/// A UIColor value that determines the text color of the title label when in the normal state
@IBInspectable dynamic open var titleColor: UIColor = .gray {
    didSet {
        updateTitleColor()
    }
}

/// A UIColor value that determines the color of the bottom line when in the normal state
@IBInspectable dynamic open var lineColor: UIColor = .lightGray {
    didSet {
        updateLineView()
    }
}

/// A UIColor value that determines the color used for the title label and line when the error message is not `nil`
@IBInspectable dynamic open var errorColor: UIColor = .red {
    didSet {
        updateColors()
    }
}

/// A UIColor value that determines the color used for the line when error message is not `nil`
@IBInspectable dynamic open var lineErrorColor: UIColor? {
    didSet {
        updateColors()
    }
}

/// A UIColor value that determines the color used for the text when error message is not `nil`
@IBInspectable dynamic open var textErrorColor: UIColor? {
    didSet {
        updateColors()
    }
}

/// A UIColor value that determines the color used for the title label when error message is not `nil`
@IBInspectable dynamic open var titleErrorColor: UIColor? {
    didSet {
        updateColors()
    }
}

/// A UIColor value that determines the color used for the title label and line when text field is disabled
@IBInspectable dynamic open var disabledColor: UIColor = UIColor(white: 0.88, alpha: 1.0) {
    didSet {
        updateControl()
        updatePlaceholder()
    }
}

/// A UIColor value that determines the text color of the title label when editing
@IBInspectable dynamic open var selectedTitleColor: UIColor = .blue {
    didSet {
        updateTitleColor()
    }
}

/// A UIColor value that determines the color of the line in a selected state
@IBInspectable dynamic open var selectedLineColor: UIColor = .black {
    didSet {
        updateLineView()
    }
}

// MARK: Line height

/// A CGFloat value that determines the height for the bottom line when the control is in the normal state
@IBInspectable dynamic open var lineHeight: CGFloat = 0.5 {
    didSet {
        updateLineView()
        setNeedsDisplay()
    }
}

/// A CGFloat value that determines the height for the bottom line when the control is in a selected state
@IBInspectable dynamic open var selectedLineHeight: CGFloat = 1.0 {
    didSet {
        updateLineView()
        setNeedsDisplay()
    }
}

// MARK: View components

/// The internal `UIView` to display the line below the text input.
open var lineView: UIView!

/// The internal `UILabel` that displays the selected, deselected title or error message based on the current state.
open var titleLabel: UILabel!

// MARK: Properties

/**
 The formatter used before displaying content in the title label.
 This can be the `title`, `selectedTitle` or the `errorMessage`.
 The default implementation converts the text to uppercase.
 */
open var titleFormatter: ((String) -> String) = { (text: String) -> String in
    if #available(iOS 9.0, *) {
        return text
    } else {
        return text
    }
}

/**
 Identifies whether the text object should hide the text being entered.
 */
override open var isSecureTextEntry: Bool {
    set {
        super.isSecureTextEntry = newValue
        fixCaretPosition()
    }
    get {
        return super.isSecureTextEntry
    }
}

/// A String value for the error message to display.
@IBInspectable
open var errorMessage: String? {
    didSet {
        updateControl(true)
    }
}

/// The backing property for the highlighted property
fileprivate var _highlighted: Bool = false

/**
 A Boolean value that determines whether the receiver is highlighted.
 When changing this value, highlighting will be done with animation
 */
override open var isHighlighted: Bool {
    get {
        return _highlighted
    }
    set {
        _highlighted = newValue
        updateTitleColor()
        updateLineView()
    }
}

/// A Boolean value that determines whether the textfield is being edited or is selected.
open var editingOrSelected: Bool {
    return super.isEditing || isSelected
}

/// A Boolean value that determines whether the receiver has an error message.
open var hasErrorMessage: Bool {
    return errorMessage != nil && errorMessage != ""
}

fileprivate var _renderingInInterfaceBuilder: Bool = false

/// The text content of the textfield
@IBInspectable
override open var text: String? {
    didSet {
        updateControl(false)
    }
}

/**
 The String to display when the input field is empty.
 The placeholder can also appear in the title label when both `title` `selectedTitle` and are `nil`.
 */
@IBInspectable
override open var placeholder: String? {
    didSet {
        setNeedsDisplay()
        updatePlaceholder()
        updateTitleLabel()
    }
}

/// The String to display when the textfield is editing and the input is not empty.
@IBInspectable open var selectedTitle: String? {
    didSet {
        updateControl()
    }
}

/// The String to display when the textfield is not editing and the input is not empty.
@IBInspectable open var title: String? {
    didSet {
        updateControl()
    }
}

// Determines whether the field is selected. When selected, the title floats above the textbox.
open override var isSelected: Bool {
    didSet {
        updateControl(true)
    }
}

// MARK: - Initializers

/**
 Initializes the control
 - parameter frame the frame of the control
 */
override public init(frame: CGRect) {
    super.init(frame: frame)
    init_SkyFloatingLabelTextField()
}

/**
 Intialzies the control by deserializing it
 - parameter aDecoder the object to deserialize the control from
 */
required public init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    init_SkyFloatingLabelTextField()
}

fileprivate final func init_SkyFloatingLabelTextField() {
    borderStyle = .none
    createTitleLabel()
    createLineView()
    updateColors()
    addEditingChangedObserver()
    updateTextAligment()
}

fileprivate func addEditingChangedObserver() {
    self.addTarget(self, action: #selector(HassanTxtField.editingChanged), for: .editingChanged)
}

/**
 Invoked when the editing state of the textfield changes. Override to respond to this change.
 */
@objc open func editingChanged() {
    updateControl(true)
    updateTitleLabel(true)
}

// MARK: create components

fileprivate func createTitleLabel() {
    let titleLabel = UILabel()
    titleLabel.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    titleLabel.font = titleFont
    titleLabel.alpha = 0.0
    titleLabel.textColor = titleColor

    addSubview(titleLabel)
    self.titleLabel = titleLabel
}

fileprivate func createLineView() {

    if lineView == nil {
        let lineView = UIView()
        lineView.isUserInteractionEnabled = false
        self.lineView = lineView
        configureDefaultLineHeight()
    }

    lineView.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
    addSubview(lineView)
}

fileprivate func configureDefaultLineHeight() {
    let onePixel: CGFloat = 1.0 / UIScreen.main.scale
    lineHeight = 2.0 * onePixel
    selectedLineHeight = 2.0 * self.lineHeight
}

// MARK: Responder handling

/**
 Attempt the control to become the first responder
 - returns: True when successfull becoming the first responder
 */
@discardableResult
override open func becomeFirstResponder() -> Bool {
    let result = super.becomeFirstResponder()
    updateControl(true)
    return result
}

/**
 Attempt the control to resign being the first responder
 - returns: True when successfull resigning being the first responder
 */
@discardableResult
override open func resignFirstResponder() -> Bool {
    let result = super.resignFirstResponder()
    updateControl(true)
    return result
}

/// update colors when is enabled changed
override open var isEnabled: Bool {
    didSet {
        updateControl()
        updatePlaceholder()
    }
}

// MARK: - View updates

fileprivate func updateControl(_ animated: Bool = false) {
    updateColors()
    updateLineView()
    updateTitleLabel(animated)
}

fileprivate func updateLineView() {
    guard let lineView = lineView else {
        return
    }

    lineView.frame = lineViewRectForBounds(bounds, editing: editingOrSelected)
    updateLineColor()
}

// MARK: - Color updates

/// Update the colors for the control. Override to customize colors.
open func updateColors() {
    updateLineColor()
    updateTitleColor()
    updateTextColor()
}

fileprivate func updateLineColor() {
    guard let lineView = lineView else {
        return
    }

    if !isEnabled {
        lineView.backgroundColor = disabledColor
    } else if hasErrorMessage {
        lineView.backgroundColor = lineErrorColor ?? errorColor
    } else {
        lineView.backgroundColor = editingOrSelected ? selectedLineColor : lineColor
    }
}

fileprivate func updateTitleColor() {
    guard let titleLabel = titleLabel else {
        return
    }

    if !isEnabled {
        titleLabel.textColor = disabledColor
    } else if hasErrorMessage {
        titleLabel.textColor = titleErrorColor ?? errorColor
    } else {
        if editingOrSelected || isHighlighted {
            titleLabel.textColor = selectedTitleColor
        } else {
            titleLabel.textColor = titleColor
        }
    }
}

fileprivate func updateTextColor() {
    if !isEnabled {
        super.textColor = disabledColor
    } else if hasErrorMessage {
        super.textColor = textErrorColor ?? errorColor
    } else {
        super.textColor = cachedTextColor
    }
}

// MARK: - Title handling

fileprivate func updateTitleLabel(_ animated: Bool = false) {
    guard let titleLabel = titleLabel else {
        return
    }

    var titleText: String?
    if hasErrorMessage {
        titleText = titleFormatter(errorMessage!)
    } else {
        if editingOrSelected {
            titleText = selectedTitleOrTitlePlaceholder()
            if titleText == nil {
                titleText = titleOrPlaceholder()
            }
        } else {
            titleText = titleOrPlaceholder()
        }
    }
    titleLabel.text = titleText
    titleLabel.font = titleFont

    updateTitleVisibility(animated)
}

fileprivate var _titleVisible: Bool = false

/*
 *   Set this value to make the title visible
 */
open func setTitleVisible(
    _ titleVisible: Bool,
    animated: Bool = false,
    animationCompletion: ((_ completed: Bool) -> Void)? = nil
    ) {
    if _titleVisible == titleVisible {
        return
    }
    _titleVisible = titleVisible
    updateTitleColor()
    updateTitleVisibility(animated, completion: animationCompletion)
}

/**
 Returns whether the title is being displayed on the control.
 - returns: True if the title is displayed on the control, false otherwise.
 */
open func isTitleVisible() -> Bool {
    return hasText || hasErrorMessage || _titleVisible
}

fileprivate func updateTitleVisibility(_ animated: Bool = false, completion: ((_ completed: Bool) -> Void)? = nil) {
    let alpha: CGFloat = isTitleVisible() ? 1.0 : 0.0
    let frame: CGRect = titleLabelRectForBounds(bounds, editing: isTitleVisible())
    let updateBlock = { () -> Void in
        self.titleLabel.alpha = alpha
        self.titleLabel.frame = frame
    }
    if animated {
        #if swift(>=4.2)
        let animationOptions: UIView.AnimationOptions = .curveEaseOut
        #else
        let animationOptions: UIViewAnimationOptions = .curveEaseOut
        #endif
        let duration = isTitleVisible() ? titleFadeInDuration : titleFadeOutDuration
        UIView.animate(withDuration: duration, delay: 0, options: animationOptions, animations: { () -> Void in
            updateBlock()
        }, completion: completion)
    } else {
        updateBlock()
        completion?(true)
    }
}

// MARK: - UITextField text/placeholder positioning overrides

/**
 Calculate the rectangle for the textfield when it is not being edited
 - parameter bounds: The current bounds of the field
 - returns: The rectangle that the textfield should render in
 */
override open func textRect(forBounds bounds: CGRect) -> CGRect {
    let superRect = super.textRect(forBounds: bounds)
    let titleHeight = self.titleHeight()

    let rect = CGRect(
        x: superRect.origin.x,
        y: titleHeight,
        width: superRect.size.width,
        height: superRect.size.height - titleHeight - selectedLineHeight
    )
    return rect
}

/**
 Calculate the rectangle for the textfield when it is being edited
 - parameter bounds: The current bounds of the field
 - returns: The rectangle that the textfield should render in
 */
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
    let superRect = super.editingRect(forBounds: bounds)
    let titleHeight = self.titleHeight()

    let rect = CGRect(
        x: superRect.origin.x,
        y: titleHeight,
        width: superRect.size.width,
        height: superRect.size.height - titleHeight - selectedLineHeight
    )
    return rect
}

/**
 Calculate the rectangle for the placeholder
 - parameter bounds: The current bounds of the placeholder
 - returns: The rectangle that the placeholder should render in
 */
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
    let rect = CGRect(
        x: 0,
        y: titleHeight(),
        width: bounds.size.width,
        height: bounds.size.height - titleHeight() - selectedLineHeight
    )
    return rect
}

// MARK: - Positioning Overrides

/**
 Calculate the bounds for the title label. Override to create a custom size title field.
 - parameter bounds: The current bounds of the title
 - parameter editing: True if the control is selected or highlighted
 - returns: The rectangle that the title label should render in
 */
open func titleLabelRectForBounds(_ bounds: CGRect, editing: Bool) -> CGRect {
    if editing {
        return CGRect(x: 0, y: 0, width: bounds.size.width, height: titleHeight())
    }
    return CGRect(x: 0, y: titleHeight(), width: bounds.size.width, height: titleHeight())
}

/**
 Calculate the bounds for the bottom line of the control.
 Override to create a custom size bottom line in the textbox.
 - parameter bounds: The current bounds of the line
 - parameter editing: True if the control is selected or highlighted
 - returns: The rectangle that the line bar should render in
 */
open func lineViewRectForBounds(_ bounds: CGRect, editing: Bool) -> CGRect {
    let height = editing ? selectedLineHeight : lineHeight
    return CGRect(x: 0, y: bounds.size.height - height, width: bounds.size.width, height: height)
}

/**
 Calculate the height of the title label.
 -returns: the calculated height of the title label. Override to size the title with a different height
 */
open func titleHeight() -> CGFloat {
    if let titleLabel = titleLabel,
        let font = titleLabel.font {
        return font.lineHeight
    }
    return 15.0
}

/**
 Calcualte the height of the textfield.
 -returns: the calculated height of the textfield. Override to size the textfield with a different height
 */
open func textHeight() -> CGFloat {
    guard let font = self.font else {
        return 0.0
    }

    return font.lineHeight + 7.0
}

// MARK: - Layout

/// Invoked when the interface builder renders the control
override open func prepareForInterfaceBuilder() {
    if #available(iOS 8.0, *) {
        super.prepareForInterfaceBuilder()
    }

    borderStyle = .none

    isSelected = true
    _renderingInInterfaceBuilder = true
    updateControl(false)
    invalidateIntrinsicContentSize()
}

/// Invoked by layoutIfNeeded automatically
override open func layoutSubviews() {
    super.layoutSubviews()

    titleLabel.frame = titleLabelRectForBounds(bounds, editing: isTitleVisible() || _renderingInInterfaceBuilder)
    lineView.frame = lineViewRectForBounds(bounds, editing: editingOrSelected || _renderingInInterfaceBuilder)
}

/**
 Calculate the content size for auto layout
 
 - returns: the content size to be used for auto layout
 */
override open var intrinsicContentSize: CGSize {
    return CGSize(width: bounds.size.width, height: titleHeight() + textHeight())
}

// MARK: - Helpers

fileprivate func titleOrPlaceholder() -> String? {
    guard let title = title ?? placeholder else {
        return nil
    }
    return titleFormatter(title)
}

fileprivate func selectedTitleOrTitlePlaceholder() -> String? {
    guard let title = selectedTitle ?? title ?? placeholder else {
        return nil
    }
    return titleFormatter(title)
}

} // swiftlint:disable:this file_length

extension UITextField { /// Moves the caret to the correct position by removing the trailing whitespace func fixCaretPosition() { // Moving the caret to the correct position by removing the trailing whitespace // http://stackoverflow.com/questions/14220187/uitextfield-has-trailing-whitespace-after-securetextentry-toggle

    let beginning = beginningOfDocument
    selectedTextRange = textRange(from: beginning, to: beginning)
    let end = endOfDocument
    selectedTextRange = textRange(from: end, to: end)
}

}

Hassan-iOS avatar Apr 30 '20 14:04 Hassan-iOS