SwiftUI: Uppercase a localized string for a view, e.g. `Text`? - swiftui

Using Xcode beta 6 and SwiftUI, creating a Text view using a >localized< string, how can I uppercase it without having to make the localized string value uppercased? I guess I want a ViewModifier for changing case, but it does not seem to exist?
Text("SignIn.Body.Instruction".uppercased()) will uppercase the localization key, so that will not work, as instructed in this SO question about uppercasing a non-localized string.

It is now possible with all new textCase ViewModifier (Xcode 12 Beta 3), like so:
Text("SignIn.Body.Instruction")
.textCase(.uppercase)
Or lowercase:
Text("SignIn.Body.Instruction")
.textCase(.lowercase)

How about using the smallCaps modifier for Font? E.g., Text("Hello World").font(Font.body.smallCaps()) will render "HELLO WORLD", but the underlying string will retain its proper localized capitalization for the purposes of accessibility.

Localise it manually and then uppercase it.
extension String {
func toUpperCase() -> String {
return localized.uppercased(with: .current)
}
private var localized : String {
return NSLocalizedString( self, comment:"")
}
}
Text("SignIn.Body.Instruction".toUpperCase())

Related

SwiftUI Localization Issue

I have a project with two targets representing two different final products. Until now the localization was shared between the two targets, but now I have just one string to be localized differently according to the active target. In order to avoid duplicating the Localizable.string file ad tweak the file target membership, I decided to create two different Localizable-Ext.string files containing just the string to be translated differently for each target.
I'm using the SwiftUI Text initializer accepting a LocalizedStringKey parameter which automatically looks up the corresponding translation inside the file. This is what has to be done in most cases. I noticed that this initializer also accepts a tableName parameter corresponding to the .string filename where the match should be taken.
What I'm trying to achieve is to have a custom Text initializer which takes the string key, looks up for it inside the default Localizable.string file and, if no match is found, falls back to the right file extension (string table) and search for it there. Apparently this is tough to achieve since I cannot manage to get the key value from the LocalizedStringKey instance.
I think you need something like
extension Text {
public init<S>(looking text: S) where S : StringProtocol {
let text = String(text)
var translated = NSLocalizedString(text, comment: "")
// returns same if no translation, so ...
if translated == text {
// ... continue in other table
translated = NSLocalizedString(text, tableName: "Localizable-Ext",
bundle: .main, value: text, comment: "")
}
// already translated, so avoid search again
self.init(verbatim: translated)
}
}

How to highlight links with Attributedstring? [duplicate]

Given the markdown string "**Line 1**\n\nLine 2" I expect an output of
Line 1
Line 2
Instead I get
Line 1Line 2
Surely this isn't a limitation of markdown or AttributedString. What am I missing?! How do I specify multiple paragraphs if not with two blank lines?
struct DemoView_Previews: PreviewProvider {
static var previews: some View {
Text(try! AttributedString(markdown: "**Line 1**\n\nLine 2"))
}
}
As discovered via the Apple Developer forums, .inlineOnlyPreservingWhitespace is needed:
Text(try! AttributedString(markdown: "**Line 1**\n\nLine 2",
options: AttributedString.MarkdownParsingOptions(interpretedSyntax:
.inlineOnlyPreservingWhitespace)))
And, of course, for those that may come along this answer later, it's worth mentioning that if you don't need to use AttributedString directly or aren't passing a variable to Text, you can use the string literal with markdown directly:
Text("**Line 1**\n\nLine 2")
Ok it is how it works
Text(try! AttributedString(markdown: "**Line 1**\nLine 2", options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)))
But if you load text from plist it doesn't work with placing there \n What you need to do is add Enter + Option

SwiftUI conditional modifier addition

Based on a bool, I would like to add one more modifier to a Text in SwiftUI.
Ideally, I would do something like that:
Text(text)
if(true) {
.bold()
}
.foregroundColor(Color.black)
.frame(alignment: .leading)
which throws errors - the only "uncomplicated" alternative I can think of is to, depending on the bool value, create 2 different Texts. However, this results in a lot of code duplication. What can I do instead?
I've also tried declaring the Text as a let variable to access it later in the code however this prevents the element from showing up.
What IS possible is the following setup:
let title = Text("text")
.foregroundColor(Color.black)
and then in the body do
if(true) {
title
.bold()
}
However, if I add one more modifier to the declaration, it tells me Property definition has inferred type 'some View', involving the 'some' return type of another declaration
Using conditional modifiers is not recommended by Apple, as it breaks the View's identity once the condition flips. An easy alternative for your usecase would be the ternary operator:
Text(text)
.fontWeight(condition ? .bold : .regular)
.foregroundColor(Color.black)
Ditto on what Florian S said, you should use a ternary as inline conditionals on view modifiers can lead to many issues, but... they can be useful sometimes as well, so if you want to use inline conditional operations on view modifiers do this.
Add an some extensions to view.. you don't need both of these but depending on how you want to to use it, each has their strengths
extension View {
#ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if (condition) {
transform(self)
} else {
self
}
}
#ViewBuilder func `ifInline`<Content: View>(_ condition: #autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
if condition() {
transform(self)
} else {
self
}
}
}
then, on the view you want to use the extension with do something like this
ForEach(values: self.multiDevice ? devices : device) { device in
Group {
ForEach(values: ColorScheme.allCases) { scheme in
self.viewToPreview
.background(Color.backgroundColor)
.colorScheme(scheme)
.previewDevice(PreviewDevice(rawValue: device))
.previewDisplayName("\(displayName) - \(scheme.previewName) - \(device)")
.if(self.wrapped) { view in
view.previewLayout(.sizeThatFits)
}
}
}
}
To use the second extension, the '.if' would turn into a '.ifInline'.
A small note, this use case is from a GenPreviews class I make in my projects to more easily show canvas previews on various devices and color schemes with a descriptive title I can provide a name for from the Provider and some bools I can pass to show either one device or multiple from two lists of options I include as well as wrapping or showing the view on a device preview.
The reason I bring this up is because this use case not only isn't used in production runtime, but isn't even included when compiling a release... which goes back to my first statement that I agree with Florian S. I have used inline conditionals on view modifiers for running code before, but it is not good practice and shouldn't be done unless circumstances require and permit it. A ternary operator for your situation would be the best approach.

SwiftUI automatic string localization question

I've been working on a SwiftUI app's localization, and I faced a localization-related situation which I don't quite understand (given, I'm not too proficient in SwiftUI yet, to begin with)
As far as I understand, at least in iOS 14, SwiftUI pretty much automatically applies localization to all "normal" strings (as long as I have proper localization files set up - which I do). However, I have two instances of the same string literal - one gets automatic localization treatment. The other does not.
So here's the situation I'm trying to figure out.
I have the following code:
NavigationView {
NavigationLink(destination: CalendarSettingsView()) {
SettingsNavLinkView(label: "Calendar") // <- this doesn't get localized
}
}
And SettingsNavLinkView is set up as the following (just the skeleton related to question):
struct SettingsNavLinkView: View {
var label:String
var body: some View {
Text(label) // <- localized "Calendar" is expected to be passed here
}
}
In addition, CalendarSettingsView defines its title as in:
struct CalendarSettingsView: View {
var body: some View {
ScrollView {
//some code
}
.navigationBarTitle("Calendar", displayMode: .inline) // <- "Calendar" here does get localized
}
}
I do have key entry for "Calendar" in my localization files.
What is happening (and what I don't understand) is for the SettingsNavLinkView(label: "Calendar") component, the "Calendar" is NOT getting localized, HOWEVER, for CalendarSettingsView component (and related use case: .navigationBarTitle("Calendar", displayMode: .inline)) the "Calendar" string DOES get localized.
Both of these instances seem like the very same String to me, so I'm just trying to figure out what's going on here.
I did solve the issue by modifying the SettingsNavLinkView by specifically adding LocalizedStringKey initialization like below:
struct SettingsNavLinkView: View {
var label:String
let localizedLabel = LocalizedStringKey(label) // <-- NEW
var body: some View {
Text(localizedLabel) // <-- UPDATED to use localizedLabel instead of label
}
}
But why did I have to do that? Why wasn't the "Calendar" string automatically localized at the point when it was passed to the SettingsNavLinkView as per this code SettingsNavLinkView(label: "Calendar")?
A bug in SwiftUI localization? My incomplete understanding of how localization works?
I would prefer not having to resort to LocalizedStringKey for "simple strings"… But I'm not sure if what I'm asking for is even valid from the perspective of how "automatic" localization really works.
Any thoughts appreciated. Thanks!
Because different Text constructors are inferred for literal string and for variable string and that is documented in SwiftUI API
/// Creates a text view that displays a stored string without localization.
///
/// Use this intializer to create a text view that displays — without
/// localization — the text in a string variable.
///
/// Text(someString) // Displays the contents of `someString` without localization.
///
/// SwiftUI doesn't call the `init(_:)` method when you initialize a text
/// view with a string literal as the input. Instead, a string literal
/// triggers the ``Text/init(_:tableName:bundle:comment:)`` method — which
/// treats the input as a ``LocalizedStringKey`` instance — and attempts to
/// perform localization.
///
/// By default, SwiftUI assumes that you don't want to localize stored
/// strings, but if you do, you can first create a localized string key from
/// the value, and initialize the text view with that. Using a key as input
/// triggers the ``Text/init(_:tableName:bundle:comment:)`` method instead.
///
/// - Parameter content: The string value to display without localization.
public init<S>(_ content: S) where S : StringProtocol
as they said no localization.
but next with localization:
/// Creates a text view that displays localized content identified by a key.
///
/// Use this intializer to look for the `key` parameter in a localization
/// table and display the associated string value in the initialized text
/// view. If the initializer can't find the key in the table, or if no table
/// exists, the text view displays the string representation of the key
/// instead.
///
/// Text("pencil") // Localizes the key if possible, or displays "pencil" if not.
///
/// When you initialize a text view with a string literal, the view triggers
/// this initializer because it assumes you want the string localized, even
/// when you don't explicitly specify a table, as in the above example. If
/// you haven't provided localization for a particular string, you still get
/// reasonable behavior, because the initializer displays the key, which
/// typically contains the unlocalized string.
///
/// If you initialize a text view with a string variable rather than a
/// string literal, the view triggers the ``Text/init(_:)-9d1g4``
/// initializer instead, because it assumes that you don't want localization
/// in that case. If you do want to localize the value stored in a string
/// variable, you can choose to call the `init(_:tableName:bundle:comment:)`
/// initializer by first creating a ``LocalizedStringKey`` instance from the
/// string variable:
///
/// Text(LocalizedStringKey(someString)) // Localizes the contents of `someString`.
///
/// If you have a string literal that you don't want to localize, use the
/// ``Text/init(verbatim:)`` initializer instead.
///
/// - Parameters:
/// - key: The key for a string in the table identified by `tableName`.
/// - tableName: The name of the string table to search. If `nil`, use the
/// table in the `Localizable.strings` file.
/// - bundle: The bundle containing the strings file. If `nil`, use the
/// main bundle.
/// - comment: Contextual information about this key-value pair.
public init(_ key: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil)
I would recommend to use the following (tested with Xcode 12.1 / iOS 14.1)
struct SettingsNavLinkView: View {
var label: String
var body: some View {
Text(LocalizedStringKey(label)) // << inline !!
}
}

actionscript find and convert text to url [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
How do I linkify text using ActionScript 3
I have this script that grabs a twitter feed and displays in a little widget. What I want to do is look at the text for a url and convert that url to a link.
public class Main extends MovieClip
{
private var twitterXML:XML; // This holds the xml data
public function Main()
{
// This is Untold Entertainment's Twitter id. Did you grab yours?
var myTwitterID= "username";
// Fire the loadTwitterXML method, passing it the url to your Twitter info:
loadTwitterXML("http://twitter.com/statuses/user_timeline/" + myTwitterID + ".xml");
}
private function loadTwitterXML(URL:String):void
{
var urlLoader:URLLoader = new URLLoader();
// When all the junk has been pulled in from the url, we'll fire finishedLoadingXML:
urlLoader.addEventListener(Event.COMPLETE, finishLoadingXML);
urlLoader.load(new URLRequest(URL));
}
private function finishLoadingXML(e:Event = null):void
{
// All the junk has been pulled in from the xml! Hooray!
// Remove the eventListener as a bit of housecleaning:
e.target.removeEventListener(Event.COMPLETE, finishLoadingXML);
// Populate the xml object with the xml data:
twitterXML = new XML(e.target.data);
showTwitterStatus();
}
private function addTextToField(text:String,field:TextField):void{
/*Regular expressions for replacement, g: replace all, i: no lower/upper case difference
Finds all strings starting with "http://", followed by any number of characters
niether space nor new line.*/
var reg:RegExp=/(\b(https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/ig;
//Replaces Note: "$&" stands for the replaced string.
text.replace(reg,"$&");
field.htmlText=text;
}
private function showTwitterStatus():void
{
// Uncomment this line if you want to see all the fun stuff Twitter sends you:
//trace(twitterXML);
// Prep the text field to hold our latest Twitter update:
twitter_txt.wordWrap = true;
twitter_txt.autoSize = TextFieldAutoSize.LEFT;
// Populate the text field with the first element in the status.text nodes:
addTextToField(twitterXML.status.text[0], twitter_txt);
}
If this
/(\b(https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/ig
is your regexp for converting text to urls, than i have some remarks.
First of all, almost all characters in chacacter classes are parsed literally.
So, here
[-A-Z0-9+&##\/%?=~_|!:,.;]
you say to search any of this characters (except /).
Simple regexp for url search will look similar to this
/\s((https?|ftp|file):\/\/)?([-a-z0-9_.:])+(\?[-a-z0-9%_?&.])?(\s+|$)/ig
I'm not sure, if it will process url borders right, but \b symbol can be a dot, so i think \s (space or linebreak) will suit better.
I`m not sure about ending (is it allowed in actionscript to use end-of-string symbol not at the end of regexp?)
And, of course, you have to tune it to suit your data.