.actionSheet(isPresented: $showingExampleSentences) {
ActionSheet(title: Text("Example Sentence"), message: Text(sentence_foreign_language + "\n" + .sentence_english), buttons: [.default(Text("OK"))]);
}
I've got a SwiftUI action sheet where it displays two lines of text, but by default they are centre-aligned. Is there a way to make it aligned to the top-left?
Related
I want to have a NavigationLink in between 2 Text elements in SwiftUI. Concatenating them together isn't working since Swift apparently only accepts concatenating elements of the same type.
Example
Expected outcome: Read our Terms & Conditions to learn more.
What I've tried:
Text("Read our ") + NavigationLink("Terms & Conditions") { TermsPage() } + Text(" to learn more.")
The error I got
Cannot convert value of type 'NavigationLink<Label, TermsPage>' to expected argument type 'Text'
I know that you can embed links in Markdown format
Text("Read our [Terms & Conditions](example.com/terms) to learn more.")
But that will open a webpage which isn't the desired behavior. I have a page inside the app that I want to navigate into. I just need the link to be inline with the text.
you could try something simple like this approach using a HStack:
#State var showTerms = false
//....
NavigationStack {
HStack {
Text("Read our")
Text("Terms & Conditions").foregroundColor(.blue)
.onTapGesture { showTerms = true }
Text("to learn more.")
}.fixedSize()
NavigationLink("", destination: TermsPage(), isActive: $showTerms)
}
This example is pretty contrived, but it illustrates the behavior. I know you can use .accessibilityIdentifier to uniquely identify a control, but I'm just trying to better understand the interplay between XCUIElement and XCUIElementQuery.
Let's say you have an app like this:
import SwiftUI
struct ContentView: View {
#State var showRedButton = true
var body: some View {
VStack {
if showRedButton {
Button("Click me") {
showRedButton = false
}
.background(.red)
}
else {
HStack {
Button("Click me") {
showRedButton = true
}
.background(.blue)
Spacer()
}
}
}
}
}
And you are UI testing like this:
import XCTest
final class MyAppUITests: XCTestCase {
func testExample() throws {
let app = XCUIApplication()
app.launch()
print(app.debugDescription)
// At this point, the Element subtree shows a single Button:
// Button, 0x14e40d290, {{162.3, 418.3}, {65.3, 20.3}}, label: 'Click me'
let btn = app.buttons["Click me"]
btn.tap() // <-- This tap makes the red button disappear and shows the blue button
print(app.debugDescription)
// Now, the Element subtree shows a single Button that has a different ID
// and different x-y coordinates:
// Button, 0x15dc12e50, {{0.0, 418.3}, {65.3, 20.3}}, label: 'Click me'
btn.tap() // <-- This tap now works on the blue button?? Without requerying?
print(app.debugDescription)
// The red button reappears, but with a different ID (which makes sense).
}
}
Why does the second tap work, even though it's a different control? This must mean that SwiftUI is automatically re-running the XCUIElementQuery to find the button that matches "Click me". Apparently the variable btn isn't linked to the control with the ID 0x14e40d290. Does this mean XCUIElement actually represents an XCUIElementQuery? I expected it to require me to explicitly re-run the query like this,
btn = app.buttons["Click me"]
prior to running the 2nd tap, or the tap would've said that btn was no longer available.
The final print of the Element subtree shows that the red button has a different ID now. This makes sense, because when SwiftUI redraws the red button, it's not the same instance as the last time. This is explained well in the WWDC videos. Nevertheless, at the moment I connected the variable "btn" to the control, I thought there was a tighter affiliation. Maybe UI testing has to behave this way because SwiftUI redraws controls so frequently?
On SwiftUI's TextField you can set an action for the return/submit button of the keyboard using the .onSubmit() modifier. How do you achieve the same with TextEditor? (.onSubmit() doesn't seem to work.)
You can use on change for the bound variable for the TextEditor like this:
TextEditor(text: $text)
.onChange(of: text) { _ in
if !text.filter({ $0.isNewline }).isEmpty {
print("Found new line character")
}
}
Realize that TextEditor does not have a submit button, per se. It is meant to accept an unlimited amount of all kinds of text.
Another way to know when the user created a new line:
TextEditor(text: $text)
.onChange(of: text) { string in
for char in string
{
if char() == "\n"
{
print("Found new line character")
}
}
}
Yrb answer and this one works well but once there's a new line found and every time a new character is added to the TextEditor you're doing the same action: print("Found new line character")
If you want to know every time user presses enter or creates a new line this one is a better solution for you:
TextEditor(text: $text)
.onChange(of: text) { string in
if string.last == "\n"
{
print("Found new line character")
}
}
Here is your solution:
onCommit: TextField calls onCommit closure when the user presses the Return key.
I have a navigation title that is too large on some smaller devices.
I've read many ways to set the titleTextAttributes and largeTitleTextAttributes of the UINavigationBar.appearance() however when setting the paragraph style to word wrap, it seems to remove the standard ... clipping and have the text continue off the edge of the screen without wrapping:
init() {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byWordWrapping
UINavigationBar.appearance().largeTitleTextAttributes = [
.paragraphStyle: paragraphStyle
]
}
I want to maintain the SwiftUI behaviour where the title is shown as large text until the view is scrolled up and it moves to the navigation bar, so getting the .toolbar directly won't help.
I also don't want to just specify a smaller font as I only want it to shrink or wrap if necessary.
Has anyone managed to achieve this?
You can add this line on the initializer of the view where yo have the issue
UILabel.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).adjustsFontSizeToFitWidth = true
Example:
struct YourView: View {
init() {
UILabel.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).adjustsFontSizeToFitWidth = true
}
var body: some View {
NavigationView {
Text("Your content")
.navigationBarTitle("Very very large title to fit in screen")
}
}
}
As far as I read about conditional Views this code should work. But it doesn't.
struct Consts {
static let myCondition = false //no difference if true or false
}
struct ContentView: View {
#State var toggle: Bool = false
var body: some View {
List() {
Text("Short Text is in first line")
Text("Second Line Text is a little longer but not much")
if Consts.myCondition {
Text("This is a conditional text. As in: when the user hasn't purchased the option he / she don't need a hint how to use this feature.")
// } else {
// Text("else doesn't help either.")
}
Toggle("I also have a toggle but it has nothing to do with this.", isOn: $toggle)
Text("Here we have a longer Text. Dont know what to type any more.")
Text("More text which is longer than a few lines.")
Text("Not so long Text")
}
.navigationTitle("Hints & Settings")
}
}
It compiles without warnings or errors. It loads up and displays fine, on simulator and on device. But every time I scroll the List upwards from the end, as soon as this if condition { Text() } should become visible the app crashes with
Fatal error: file SwiftUI, line 0
2021-03-07 06:36:26.548126+0100 WTFif WatchKit Extension[23163:641812] Fatal error: file SwiftUI, line 0
This is not limited to watchOS. It reproduces the same way in iOS, just the Texts have to be longer so that the if condition { Text() } becomes invisible when scrolling.
I have worked around the error with an array, conditional ranges and two ForEach() blocks.
struct ContentView: View {
let myHints = ["Short Text is in first line",
"Second Line Text is a little longer but not much",
"This is a conditional text. As in: when the user hasn't purchased the option he / she don't need to hint how to use this feature.",
"Here we have a longer Text. Dont know what to type any more.",
"More text which is longer than a few lines.",
"Not so long Text"]
var myUpperRange: ClosedRange<Int> {
if Consts.myCondition {
return 0...1
} else {
return 0...2
}
}
var myLowerRange: ClosedRange<Int> {
return 3...5
}
#State var toggle: Bool = false
var body: some View {
List() {
ForEach (myUpperRange, id: \.self) { i in
Text(myHints[i])
}
Toggle("I also have a toggle but it has nothing to do with this.", isOn: $toggle)
ForEach (myLowerRange, id: \.self) { i in
Text(myHints[i])
}
}
.navigationTitle("Hints & Settings")
}
}
My question basically is: am I not getting it or did I find a bug in Xcode / SwiftUI? Should my code above work? What could I have done different to make it work with the simple list of Texts?
Background: I also have a TabView with an if condition { MyTab() } which works without crashing. Do I have to worry that this might crash in the same way? Should I work around this as well before shipping?
PS: I am using Xcode 12.4 (12D4e)
Apparently this has been fixed by iOS 15 Beta 4: on my test device I had the error prior to the Beta 4 update, using a test app compiled with Xcode 13 Beta 3. After the update to iOS 15 Beta 4 the error is gone.
So I think it’s reasonable to say that the update to iOS 15 Beta 4 did fix the error.