SwiftUI Form Picker with text and Image - swift

I am trying to create a form picker that shows the currently selected image resource at the top level and when the user selects it to show the detail, I want it to show all of the image resources available.
Here is the relevant section of code:
Picker("Background image:", selection: $task.background) {
ForEach(0 ..< backgroundImages.count) {
Image("Background\($0)").resizable().frame(width: 100, height: 35, alignment: .center)
Text("Background\($0)")
}
}
The problem with this is that in the detail screen I get:
The image is blank and the image and text appear on 2 different rows.
I have tried wrapping the Image and Text lines in an HStack, but that gives a compile time error on some other line. Any suggestions would be helpful.

The following should compile & work well (tested with replaced system images, Xcode 11.2)
Picker("Background image:", selection: $task.background) {
ForEach(0 ..< backgroundImages.count) { i in
HStack {
Image("Background\(i)").resizable().frame(width: 100, height: 35, alignment: .center)
Text("Background\(i)")
}
}
}

Related

Swift: Changing the length of a ForEach loop and updating the view during use

I've seen some similar questions but I'm still struggling to sort this one out. I am making a word game that involves typing a word between 4-6 letters long. The game board is generated in its own view file using a ForEach loop that currently iterates 6 times. On the 3rd round, I want to change the game board to be 7 spaces long instead of 6, and during the final round I want to make the game board 8 spaces long. Initially I tried setting the upper limit to equal the value of a variable or return value of a function and it works "in theory" but depending on what I try, either the game crashes on round 3 or the board just stays 6 spaces long.
I've included the GuessView code as it exists below
struct GuessView: View {
#Binding var guess: Guess
var body: some View {
HStack(spacing: 3) {
ForEach(0...5, id: \.self) { index in
Text(guess.guessLetters[index])
.foregroundColor(.primary)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 50, alignment: .center)
.background(Color.systemBackground)
.font(.system(size: 35, weight: .heavy))
//.border(Color(.secondaryLabel))
.overlay(
Rectangle()
.frame(height: 3)
.foregroundColor(.pink),
alignment: .bottom
)
}
}
}
}
The majority of the logic exits in a Swift file that called WordDataModel. It's an Oberservable Object and essentially contains a class with all of the relevant functions and variables that I pull from. So for instance, when I was trying to make the upper limit of the ForEach loop a variable that changed I had that variable stored/changing in the WordDataModel function and had GuessView looking like this
WordDataModel:
var boardLength: Int = 5
GuessView:
ForEach(0...dm.boardLength, id: \.self) { index in
Text(guess.guessLetters[index])
.foregroundColor(.primary)
But when I update the value in WordDataModel or try to bring the variable over using #Binding or #State it doesn't seem to work.
Hopefully I've provided enough data, any suggestions are much appreciated thank you!
Use #Published for a property, it creates a publisher for type.
#Published var boardLength: Int

toolbar text item gets cropped

There is a very weird behavior happening with the toolbar, I have the following:
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
Imagegoeshere...
.frame(width: 20, height: 20)
Text("Polkadot")
// Text(coinInfo?.name ?? "-")
}
}
}
Just like that it displays correct the image and on the right the name "Polkadot" (that's for testing), if I replace that Text("Polkadot") with the real value (which contains the exact text with no spaces) it crops it: Text(coinInfo?.name ?? "-")
This is how it looks with the testing text:
and this is how it looks with the real value yet the same exact text:
Any idea what could be causing this?
By accident just now I found the solution, still I have no idea why this happens, this is how it got fixed:
Replace the HStack with LazyHStack

maxWidth infinity failure SwiftUI

I have the following code:
Button(action: {
}, label: {
Text("Save".uppercased())
.foregroundColor(.white)
.font(.headline)
.background(Color.accentColor)
.frame(height: 55)
.frame(maxWidth: .infinity)
.cornerRadius(10)
})
}
.padding(14)
I've checked it over and am clearly missing something because the max width is not working whatsoever. The button is still tightly confined around the "SAVE" text. I have also tried manually adjusting the width but this hasn't changed anything.
Any suggestions? I am running XCode 13.
Order matters a lot in view modifiers ;)
I suppose you want this:
Text("Save".uppercased())
.frame(maxWidth: .infinity)
.frame(height: 55)
.background(Color.accentColor)
.cornerRadius(10)
.foregroundColor(.white)
.font(.headline)
The Text itself is only as tall & wide as it needs to be, so first the frames to define the size, then the background color for that area, then the corner radius.
Foreground color and font can go anywhere.
You can see and check many of the (also if working) effects in the preview, where you can select single lines of code and see the resulting frame.

SwiftUI Form Label above Text field Section Spacing

I am attempting to create a form in SwiftUI that has the Label above the text input field all the time.
A good example is looking at a contact on an iPhone 12. There are two standalone inputs (among others). Mobile and Notes respectively.
They look and space just how I would like mine.
No matter how I change the following code I always have a large space at the top and the fields themselves have huge spaces in between the Sections.
Form {
Section() {
VStack(alignment: .leading) {
Text("Field1")
TextField("...", text: $Field1)
}
.padding(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
}
Section() {
VStack(alignment: .leading) {
Text("Field2")
TextField("...", text: $Field2)
}
.padding(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
}
}
If the Form tag here is the issue is it better to remove it and do it all manually. Apple do seem to want you to use the Form tag for cross compatibility. In my case its for iPads and iPhones.
As I said in my comments, you could also put your field title in as the Section header. Also, this is your UI, make it how you want. My only comment about it was to think of the usability when designing it. Your answer gives what looks to be a large target for the user, but in reality is only half that size. I am not saying it is wrong for your app, only that you should consider it.
Form {
Section(header: Text("Field1") ) {
TextField("Required", text: $Field1)
// With padding that is equivalent to your padding.
.padding(.vertical, 10)
// .padding(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
}
Section(header: Text("Field2") ) {
TextField("Optional", text: $Field2)
// Without padding...
}
Section(header: Text("Field3") ) {
TextField("Optional", text: $Field2)
// With padding that is determined by the system.
.padding(.vertical)
}
}
Obviously, this gives you a different look. As you will notice, I gave you three different .padding() looks. One is yours, instead using .vertical (sets .top and .bottom to be the same) with the same constant. The next is no padding around the field. The last in allowing the system to choose your padding.
My first answer to my own question. This is what I came up with (but decided not to use).
It's pretty much what I wanted in the look.
It seems that Apple really do want to force you to do it "their way".
It is difficult to alter the Form view format without everything you try having some unrequired knock on effect.
I agree with some of the comments that this does look correct but it doesn't have the best UI experience.
Form {
Section() {
VStack(alignment: .leading) {
Text("Field1")
.font(.headline)
TextField("Required", text: $Field1)
}
.padding(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
VStack(alignment: .leading) {
Text("Field2")
.font(.headline)
TextField("Optional", text: $Field2)
}
.padding(EdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0))
}
Section {
NavigationLink(destination: SomeView()) {
Text("Next")
.padding()
.foregroundColor(Color.blue)
}
}
}
.navigationBarTitle("Main Title")
My second answer to my own question. This is what I came up with.
I decided to go down the route of a separate label above a text input control using the header as the label. This does have a different look.
This is simply because the original look I was after didn't have the best UI experience.
It is possible to add extra code but it starts to get far to over complicated.
Form {
Section(header: Text("Field1") // Label
.font(.headline)
.foregroundColor(.black)
// The padding top is larger on the first control to make it stand out more from the navigation bar title.
.padding(EdgeInsets(top: 30, leading: 5, bottom: 0, trailing: 0))) {
TextField("Required", text: $Field1)
}
.textCase(nil) // I dont want the label to be all uppercase.
Section(header: Text("Field2") // Label
.font(.headline)
.foregroundColor(.black)
.padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 0))) {
TextField("Required", text: $Field1)
}
.textCase(nil)
Section {
NavigationLink(destination: SomeView()) {
Text("Next")
.padding()
.foregroundColor(Color.blue)
}
}
}
.navigationBarTitle("Main Title")

WatchOS ScrollView Doesn't Wrap Text Properly

Right now I am able to see the text I want from my 'articles' if I set a frame with a desired width and height.
If the height is long enough it will show all of the article 'body' but with tons of space. I would like to have it where the frame can adjust it's size based on the size of the text I have so that the scrollview can scroll properly based on the desired text frames for each article body.
import SwiftUI
let articles = [
Article (id: 0, title: "Trump as President", body: "Some very short string for the article at the moment."),
Article (id: 1, title: "Obama as President", body: "He may not have been the worst but was he every really the best? Could he have done more or less from what we know?"),
Article (id: 2, title: "Tanner Fry as President", body: "Who knows how well that would work as if Tanner Fry was the president. However we do know a little bit about him from his experience as a programmer. I mean who can just pick up different langauges just off the bat and start creating code for apps to run on their watch and/or phone? Not too many people know how to do that but you know who does? Tanner Fry does, that's right.")
]
var height = 0
struct ContentView: View {
var body: some View {
// Watch res = 448 - 368
ScrollView(.vertical) {
VStack(spacing: 10){
Text("Your News").font(.title)
ForEach(0..<articles.count) {index in
Text(articles[index].title)
Text(articles[index].body).frame(width: 170, height: 170)
// Text(articles[index].body).lineLimit(50).padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
// Height needs to be variable based on the amount of text in the
// articles description. OR find a wrapper
// We're talking about the frame for the body of text
}
}
}
}
}
I am able to scroll all of my content if my height for the frame of the article.body is long enough. Otherwise it truncates the text. Is there any way to make the height more variable to the text length so that the watchOS works properly when scrolling via the ScrollView? Am I missing something?
Thank you for your time, much appreciated.
Define .lineLimit(x) to what you want to be the maximum of line the Text is able to expand. Then add .fixedSize(horizontal: false, vertical: true) to secure that the size is not shrinking back due the SwiftUI layout engine. See below.
struct ContentView: View {
var body: some View {
// Watch res = 448 - 368
ScrollView(.vertical) {
VStack(spacing: 10){
Text("Your News").font(.title)
ForEach(0..<articles.count) {index in
Text(articles[index].title)
Text(articles[index].body)
.lineLimit(nil)
.multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
// Text(articles[index].body).lineLimit(50).padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
// Height needs to be variable based on the amount of text in the
// articles description. OR find a wrapper
// We're talking about the frame for the body of text
}
}
}
}
}