Localized String with Interface Builder User Defined Runtime Attributes - ios

I am currently trying to create a localized accessibilityLabel in the storyboard (I am trying to avoid doing it programatically). It seems that whenever I use the Localized String option, the accessibilityLabels ends up being set to the localized string key that I have provided rather than the string itself. Does anyone have the solution to this problem? Any help would be greatly appreciated.

I guess you expect the localized string to be taken from Localizable.strings. The "Localized String" type doesn't work this way, it's just a marker to indicate that the value of the user defined runtime attribute will participate in the localization process when you use base localization. Please take a look at https://stackoverflow.com/a/24527990/2876231 for a lengthier explanation.

The attribute type needs to be Localizable String, and then you'd translate it in the .strings file using the following property:
"KLc-fp-ZVK.ibExternalUserDefinedRuntimeAttributesLocalizableStrings[0]" = "¡Hola!";
Unfortunately, it doesn't seem to work with a named attribute, but only with the long property above.
(Based on Andrew's answer here: Localize a view within a storyboard using "User Defined Runtime Attributes")

I did the customization of an attribute with the simple solution of localizing the attribute by code:
private struct AssociatedKeys {
static var someTagKey = "someTag"
}
#IBInspectable var someTag: String? {
get {
return NSLocalizedString(
objc_getAssociatedObject(self, &AssociatedKeys.someTagsKey) as? String ?? "", comment: "")
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.someTagsKey,
newValue as NSString?,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
And after that you can easily extract all the strings from the xib and storyboard files with an egrep:
egrep -ohr --include="*.xib" --include="*.storyboard" '<userDefinedRuntimeAttribute type="string" keyPath="someTag" value="[^"]+"/>' . >> extracted-strings.txt
And after that eliminate the tags in the strings file by the following sed and then you have to plain strings file for xcode ready:
sed -i -e 's/^<userDefinedRuntimeAttribute type="string" keyPath="someTag" value=\("[^"]*"\)\/>/\1 = \1;/g' extracted-strings.txt

Related

Any standard String in SWIFT similar to UIColor.systemgray

In Android, we can access some standard string resources provided by the platform, e.g. cancel, OK, delete etc. It is convenient since I don't have to store all the translation for those simple texts.
I know that Swift provides something similar to UIColor, e.g. UIColor.systemgray.
My question is: Is there something similar but for String. Thanks!
You can technically load localized strings from Apple's own frameworks. For instance, to load from UIKit, you'd use
let bundle = Bundle(for: UIView.self)
let string = bundle.localizedString(forKey: "Cancel", value: nil, table: nil)
When the current locale is German, this gives "Abbrechen".
The downside of this approach is that you'll only know whether or not a specific string exists in the framework at runtime.
The best workaround I can think of for this is to define the keys that you use yourself and continuously verify them with a unit test.
For example you could gather all your system-string keys in a case-iterable enumeration
public enum SystemStringKey: String, CaseIterable {
case cancel = "Cancel"
...
}
and use them to load the system strings via an extension on String
public extension String {
static var systemStringBundle: Bundle {
return Bundle(for: UIView.self)
}
static func systemString(for key: SystemStringKey) -> String {
return systemStringBundle.localizedString(forKey: key.rawValue, value: nil, table: nil)
}
}
In code you can then use the system strings like constants
label.text = .systemString(for: .cancel)
To verify their continued existence, you could use a unit test such as this
class SystemStringKeyTests: XCTestCase {
func testAllKeysExist() throws {
let path = try XCTUnwrap(String.systemStringBundle.path(forResource: "Localizable", ofType: "strings"))
let dict = try XCTUnwrap(NSDictionary(contentsOfFile: path))
for key in SystemStringKey.allCases {
XCTAssertNotNil(dict[key.rawValue])
}
}
}
This isn't 100% bulletproof as it could still happen that Apple removes a string in an iOS update and then any released app that relied on that string would show the untranslated English string until you ship an update yourself.
Update: I threw together a Swift package for conveniently exploring and accessing available strings from system bundles. There are some caveats though. See https://nosuchdomain.mooo.com/git/doc/swift-systemstrings for further info.
No, there are no such static properties in Swift

How do I assert that a text field is empty?

I have an empty text field on my UI, though it has a placeholder text (whose value is foo) set in the storyboard. In my UI test, I am trying to check that its text value starts out empty, but when I query it's value, it seems to be giving me the placeholder value instead:
func testTextFieldInitiallyEmpty {
let input = XCUIApplication().textFields["My Text Field"]
XCTAssertEqual(input.value as! String, "")
}
as the test fails with this message:
XCTAssertEqual failed: ("foo") is not equal to ("")
Of course, foo is the placeholder value, but it's not the text value of that text field. I would have expected that error message if I had written:
XCTAssertEqual(input.placeholderValue as! String, "")
input is a XCUIElement, which implements XCUIElementAttributes, so I don't see anything else that would do the trick here.
How do I check (assert) that the text field is empty?
Edit
After doing some further research and trying out the suggestions below for using the input's properties of accessibilityValue, label, and title, I have not found any solution that will give me the text field's text value when there is text, and an empty string when only the placeholder is visible.
This seems like either (a) a bug, or (b) a questionable design decision from the test framework to not provide that ability. At a minimum, the documentation for XCUIElementAttributes#value seems inadequate to me, as the only detail is:
The exact type of value varies based on the type of the element.
Still looking for a better solution...
You can compare to the XCUIElementAttributes's placeholderValue variable in addition to checking for a blank string
extension XCUIElement {
func noTextEntered() -> Bool {
return self.value as? String != "" && self.value as? String != placeholderValue
}
}
Then you can run XCAssert(input.noTextEntered(), "Unexpected text entered into field")
Just make sure your placeholder is not something a user would type in. This way you don't have to hardcode placeholder values to check against
Kind of ridiculous that this is actually the case it works and that it needs a workaround.
Anyway, my solution to get the value w/o the placeholder interfering, based on #Jason's answer.
extension XCUIElement {
var valueWithoutPlaceholder: String {
if let v = value as? String, v != placeholderValue {
return v
}
return ""
}
}
Be aware, if the input is actually the placeholder this would break!
Try using accessibilityValue property of input.
func testTextFieldInitiallyEmpty {
let input = XCUIApplication().textFields["My Text Field"]
XCTAssertEqual(input.accessibilityValue, "")
}
If you command+click the property, you can see the following..
/*
Returns a localized string that represents the value of the element, such as the value
of a slider or the text in a text field. Use only when the label of the element
differs from a value. For example: A volume slider has a label of "Volume", but a value of "60%".
default == nil
default on UIKit controls == values for appropriate controls
Setting the property will change the value that is returned to the accessibility client.
*/
public var accessibilityValue: String?

How to change language of ios app in runtime itself with Base internationalizations swift 3?

I have referred the below link as well as other links. I am able to change the language of the app after exiting.
Is there anyway I can change the language of the app with Base Internationalisation on the go?
For ex: If user selects English it should be reflected throughout the app.
manual language selection in an iOS-App (iPhone and iPad)
This is how I did it. I created an extension on String class and created a localized variable which gets the string values from my Localizable.strings files of a language
public extension String {
var localized: String {
// currentLanguageCode is e.g. "en" or "nl"
let path = Bundle.main.path(forResource: LocalizationService.shared.currentLanguageCode, ofType: "lproj")
let bundle = Bundle(path: path!)
return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
}
}
Then everywhere in my code when I want to assign localized string values I do:
titleLabel.text = "settings_label_userProfile".localized
When user changes the language e.g. in my settings section, LocalizationService sets a different language code and from then on localized property will return the values of that language code. You need to make sure that the UI is reloaded if it's still displaying the previous language strings.
EDIT:
This solution will only work if you set your localizable strings programatically.

Capitalized text in UILabel from visual editor in xcode

I am starter in iOS. I found out that there are ways around making the text bold, and changing font and font size etc from visual editor. But are there any ways to set the UILabel text All Capitalized from visual editor (STORYBOARD, NOT CODE). I searched but only found code (Swift/Objective C) based answers like this:
testDisplayLabel.text = testDisplayLabel.text.uppercased()
A legitimate question -- and a useless (if not arrogant) answer marked as Accepted. It is not unusual when you receive from a copywriter a LOT of text that is not All Caps. So the answer is -- you have to process it all programmatically.
EDIT Generally it is a good idea to wrap your text constants programmatically. First it gives you the opportunity to localize your app (even in the future):
extension String {
func localized (lang: String) -> String {
guard let path = Bundle.main.path (forResource: lang, ofType: "lproj") else { return "" }
guard let bundle = Bundle (path: path) else { return "" }
return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
}
func localized () -> String {
guard let loc = Locale.current.languageCode else { return "" }
return localized(lang: loc)
}
}
So whenever you need to display a string, you apply this method like this:
"My String".localized()
Likewise, your string should start with a common localisation, and then you make them capitalized when needed:
"This should be all caps".localized().uppercased()
By default "lowercase word" and "uppercase word" are unbound in Preferences -> Key Bindings -> Text Key Bindings. Just create a new set and bind them to something. Then you will be able to use the binding to capitalize the text.
Just turn ON caps lock and type text :) that's the way we do in storyboard, Other thing you can do is select a font that has only Uppercase character in it.

How to copy text to clipboard/pasteboard with Swift

I'm looking for a clean example of how to copy text to iOS clipboard that can then be used/pasted in other apps.
The benefit of this function is that the text can be copied quickly, without the standard text highlighting functions of the traditional text copying.
I am assuming that the key classes are in UIPasteboard, but can't find the relevant areas in the code example they supply.
If all you want is plain text, you can just use the string property. It's both readable and writable:
// write to clipboard
UIPasteboard.general.string = "Hello world"
// read from clipboard
let content = UIPasteboard.general.string
(When reading from the clipboard, the UIPasteboard documentation also suggests you might want to first check hasStrings, "to avoid causing the system to needlessly attempt to fetch data before it is needed or when the data might not be present", such as when using Handoff.)
Since copying and pasting is usually done in pairs, this is supplemental answer to #jtbandes good, concise answer. I originally came here looking how to paste.
iOS makes this easy because the general pasteboard can be used like a variable. Just get and set UIPasteboard.general.string.
Here is an example showing both being used with a UITextField:
Copy
UIPasteboard.general.string = myTextField.text
Paste
if let myString = UIPasteboard.general.string {
myTextField.insertText(myString)
}
Note that the pasteboard string is an Optional, so it has to be unwrapped first.
Copying text from the app to the clipboard:
let pasteboard = UIPasteboard.general
pasteboard.string = employee.phoneNumber
SWIFT 4
UIPasteboard.general.string = "TEXT"
in Swift 5 i can copy text to clipboard using
UIPasteboard.general.string = "Hello world"
then you can paste the text anywhere of your device
Write below the code where you want to Copying String or Text
UIPasteboard.general.string = "Dhaval Gevariya" // Put your String here
this is for read String from clipboard.
var readString = UIPasteboard.general.string
import UIKit.UIPasteboard
extension UIPasteboard {
static func pasteToClipboard(_ content: String) {
self.general.string = content
}
static func readFromClipboard() -> String? {
return self.general.string
}
}

Resources