How to create two column list in swiftUI using List view? - drag-and-drop

I am new to SwiftUI. I need to create a list that is displaying in two columns. Its more likely a collectionView with two items per row. The condition is I need to create it using "List" because I need to add a "drag and drop to reorder" functionality on this list.
So far I have implemented it using HStack and VStack but in this case there is no option to drag and reorder the list.
Here is the code I have done so far:
ZStack{
Button(action: {
}) {
HStack{
Image(systemName: "person.crop.circle.fill")
Text(font)
.font(.system(size: 13 ))
.foregroundColor(.black)
.frame(minWidth: UIScreen.main.bounds.size.width/4, maxWidth: UIScreen.main.bounds.size.width/4)
.padding(.horizontal)
}
.padding(10)
}
.foregroundColor(Color.black)
.background(Color.white)
.cornerRadius(8)
.background(
RoundedRectangle(cornerRadius: 5)
.fill(Color.white)
.shadow(color: .gray, radius: 2, x: 0, y: 2)
)
}
My question is: how can I create collection view type of two column list in SwiftUI Using List??
IF my implementation using HStack and Stack sounds good then: Is there any way to implement drag and drop to reorder list functionality on it??
Using LazyVGrid is not possible for me because my requirement is ios13.
Any help will be appreciated.
Thanks!!
Update:
I solved it using UICollectionView wrapped in UIViewRepresentable in swiftui.

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

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.

Controlling Text view layout with SwiftUI

I have the following SwiftUI Text view:
Text("Hello, world. foo bar foobar foo hello world")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.red)
which renders like this (screenshots from Xcode preview, iPhone 13 Pro):
Adding a single character to the string causes the view to render as follows:
There is clearly space for "hello" on the first line, but the layout engine breaks the lines presumably where it feels is best. Is there any way to control this, to get the text to flow as far as it can on each line, within the constraints of the view?
You could perhaps use an overlay, with the Text having a fixed size horizontally. This will mean the Text will only take 1 line, and will not wrap to the next line because of the text being too long.
Code:
Text("")
.frame(maxWidth: .infinity)
.overlay(alignment: .leading) {
Text("Hello, world. foo bar foobar foo hello world more words etc etc")
.fixedSize(horizontal: true, vertical: false)
}
.padding()
.background(Color.red)
I did not find any problem on my end with your current code, it can take more characters not only single, however, but You can also use this .lineLimit() to control more flexibility:
see my output image:
`Text("Hello, world. foo bar foobar fooooooooo, hello world")
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
.padding()
.background(Color.red)`[![output][1]][1]

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")

SwiftUI Form Picker with text and Image

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)")
}
}
}