maxWidth infinity failure SwiftUI - swift

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.

Related

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

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]

How to create two column list in swiftUI using List view?

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.

Conflicts with custom alignment guides in SwiftUI

In my project I have to use custom alignment guides with nested views. It is quite complex, but I'll try to simplify my case in the following way:
There are 3 possible views: a circle, an upContainer and a downContainer. The two containers are just arrays of other circles/containers. The downContainer aligns only first array to the current line (where there are, for example, the other circles), regardless of what is inside the second array; the upContainer does the opposite. Here's an image to visualize them:
I want to be able to build complex views dynamically using this three elements. Thus I create a data model (which is a simple enum) with nested associated types:
enum Data: Hashable {
case circle
case upContainer([Data], [Data])
case downContainer([Data], [Data])
}
In order to manually align the three elements I create a custom SwiftUI alignment in this way:
extension VerticalAlignment {
struct MyAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[VerticalAlignment.center]
}
}
static let myAlignment = VerticalAlignment(MyAlignment.self)
}
I want the circles in a specific container to have the same color, so I create an extension to quickly generate a random color:
extension Color {
static var random: Color {
return Color(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1)
)
}
}
Now I am able to create the views corresponding to the three elements:
struct CirclesView: View {
var circles: [Data]
var color: Color = Color.random
var body: some View {
HStack(alignment: .myAlignment) {
ForEach(circles, id: \.self) { value in
switch value {
case .circle:
Circle()
.frame(width: 20, height: 20)
.foregroundColor(color)
case .upContainer(let firstData, let secondData):
UpContainerView(container: (firstData, secondData))
case .downContainer(let firstData, let secondData):
DownContainerView(container: (firstData, secondData))
}
}
}
}
}
struct UpContainerView: View {
var container: ([Data], [Data])
var color = Color.random
var body: some View {
VStack(alignment: .leading) {
CirclesView(circles: container.0, color: color)
CirclesView(circles: container.1, color: color)
.alignmentGuide(.myAlignment) { $0[VerticalAlignment.center] }
}
}
}
struct DownContainerView: View {
var container: ([Data], [Data])
var color = Color.random
var body: some View {
VStack(alignment: .leading) {
CirclesView(circles: container.0, color: color)
.alignmentGuide(.myAlignment) { $0[VerticalAlignment.center] }
CirclesView(circles: container.1, color: color)
}
}
}
At this point the problem should be solved. For example, if I wanted to render this image:
I should be able to do it by writing:
struct ContentView : View {
var myData: [Data] = [.circle, .circle, .downContainer(
[.circle, .circle, .circle, .downContainer(
[.circle, .circle, .circle, .circle], [.circle]
)], [.circle, .circle, .upContainer(
[.circle, .circle], [.circle, .circle, .circle, .circle]
)]
)]
var body: some View {
CirclesView(circles: myData)
}
}
However this is the result:
As you can see, the first container (the brown one) is not rendered as downContainer, in fact it is in centrally aligned to the first two circles green circles. This is because even if we set explicit alignment guides for only one of the two arrays, SwiftUI takes into account also the alignment guides of the subviews of the other array (even if we want to ignore them) and there is a conflict.
struct DownContainerView: View {
var container: ([Data], [Data])
var color = Color.random
var body: some View {
VStack(alignment: .leading) {
CirclesView(circles: container.0, color: color)
.alignmentGuide(.myAlignment) { $0[VerticalAlignment.center] }
//We set the explicit alignment guide only for the first array, I am looking for a way to ignore completely the alignment guides of the second one and to calculate the alignment based only on the first one.
CirclesView(circles: container.1, color: color)
}
}
}
To solve the conflict we should simply ignore the alignment guides of the other array.
The actual question becomes "How can we ignore those alignment guides, which are implicitly and automatically calculated by SwiftUI?"
Note: I now that a different solution could be to use PreferenceKeys, but I believe this problem should be solved using only alignment guides, since it is a mere alignment problem. I also thought that a solution could be to dynamically create a new custom alignment guide for every subcontainer, but I don't how to do it (if it is possible in Swift).
First up some background info that might help:
EVERY view has EXACTLY ONE alignment which is an x and y value in the coordinate system of the view. Note there is no name (like .top or .leading) mentioned here, those are just ways to tell the view which x an y value to return as the alignment guide for this layout.
If you don't tell the view which alignment to use, it defaults to .center.
The parent view uses the child alignment guides by calculating where that coordinate is in its own coordinate system and places all the children so the alignments are all coincident. Some parents may ignore the child value and place the child where it wants (such as a HStack which ignores the x value of its children and places the children side by side.)
The OP asks if the problem can be solved with layout alone, or are preferences required?
The answers are: it can be done with layout alone depending on how sophisticated you need to be, but you may need preferences depending on how sophisticated you need to be.
Looking at the OPs example we see that a CirclesView is an alias for a HStack and both up and down containers are aliases for a VStack containing two CirclesViews.
Also the test case only has CirclesViews with Circles and either an UpContainerView or a DownContainerView (never both up and down, which gives a false impression of how well the layout is working, it is not even as good as the OP thinks).
So unrolling the test data, using the HStacks and VStacks directly, using Text with fixed colours so we can tell what is what and putting it all into one view so we can play with alignment gives:
struct SOAlignment: View {
var body: some View {
HStack { //CirclesView (solid gray)
Text("C")
.foregroundColor(Color.blue)
.overlay(Rectangle().strokeBorder(style: StrokeStyle(lineWidth: 1.0, dash: [5,5])))
Text("C")
.foregroundColor(Color.blue)
.overlay(Rectangle().strokeBorder(style: StrokeStyle(lineWidth: 1.0, dash: [5,5])))
VStack(alignment: .leading) { //DownContainerView (black dashes)
HStack { //CirclesView (top gray dashes)
Text("C")
.foregroundColor(Color.red)
Text("C")
.foregroundColor(Color.red)
Text("C")
.foregroundColor(Color.red)
VStack(alignment: .leading) { //DownContainerView (yellow)
HStack {
Text("C")
Text("C")
Text("C")
Text("C")
}
HStack {
Text("C")
}
}
.foregroundColor(Color.yellow)
}
.foregroundColor(Color.red)
.overlay(Rectangle()
.inset(by: 1.0)
.strokeBorder(style: StrokeStyle(lineWidth: 0.5, dash: [2.5,2.5]))
.foregroundColor(Color.gray))
HStack { //CirclesView (bottom gray dashes)
Text("C")
.foregroundColor(Color.red)
Text("C")
.foregroundColor(Color.red)
VStack(alignment: .leading) { //UpContainerView (pink)
HStack {
Text("C")
Text("C")
}
HStack {
Text("C")
Text("C")
Text("C")
Text("C")
}
}
.foregroundColor(Color.lightPink)
}
.overlay(Rectangle()
.inset(by: 1.0)
.strokeBorder(style: StrokeStyle(lineWidth: 0.5, dash: [2.5,2.5]))
.foregroundColor(Color.gray))
}
.overlay(Rectangle().strokeBorder(style: StrokeStyle(lineWidth: 1.0, dash: [5,5])))
}
.border(Color.gray.opacity(0.5))
.padding()
}
}
I have added .leading horizontal alignment so the output is as close to the circle example as possible, but these play no role so can be ignored. We are only interested in the vertical alignment.
We get the following output.
I have put borders around the top level views. The top CirclesView is solid gray, it contains a blue Circle, a blue Circle and a DownContainerView all with black dashes, and the DownContainerView contains two CirclesViews with gray dashes.
None of the views have an alignment, by which I mean every view has exactly one alignment which has the default .center value. It is worth pausing for a second here—we are trying to align circles and they all have alignment guides at their centres but the centres of the blue circles do not align with any of the centres of the other coloured circles! The centres of the two black dashed blue circles are of course aligned with the centre of the black dashed DownContainerView/VStack.
The fact that the circles have been substituted for text hints at a possible solution. This is precisely the same problem that Apple solved with .firstTextBaseline and .lastTextBaseline. We want our top three red circles to align with the top row of the yellow down container, and we want the bottom two red circles to align with the bottom row of the pink up container. We can achieve this by putting .firstTextBaseline on the top gray dashed CirclesView and .lastTextBaseline on the bottom gray dashed CirclesView. Now that those are aligned we can also put .firstTextBaseline on the solid gray CirclesView.
e.g.
HStack(alignment: .firstTextBaseline) { //CirclesView (solid gray)
And we get...
Hooray problem solved! Not quite: we put the alignment property on the parent CirclesView/HStack not the child Up/DownContainer/VStack. The point is illustrated by changing the solid gray view alignment to .lastTextBaseline
HStack(alignment: .lastTextBaseline) { //CirclesView (solid gray)
The black dashed DownContainerView has been turned into a black dashed UpContainerView without any changes to itself!
This is what I hinted at previously about having only one type of container in a CirclesView in the test example data. We need to ensure that CirclesViews with both up and down containers work but with this solution the property on the CirclesView (first or last text baseline) turns all the immediate child container views into one type (either up or down).
What we need here is a custom alignment and the OP’s custom alignment is perfect. So let’s use it and replace all the text baselines with myAlignments.
e.g.
HStack(alignment: .myAlignment) { //CirclesView (solid gray)
We get the same as before with all centre alignments because myAlignment defaults to .center. The black dashed down container still doesn’t know how to choose between all the myAlignments of its children without some help from the developer. We need to explicitly tell the DownContainerView/VStack how to calculate the value of myAlignment which is half a circle hight from its top and then give it that alignment guide. Similarly the UpContainerView/VStack needs a .myAlignment guide half a circle height above its .bottom.
VStack(alignment: .leading) { //DownContainerView
.
.
.
}
.alignmentGuide(.myAlignment) { d in
d[.top] + halfCircleHeight
}
and
VStack(alignment: .leading) { //UpContainerView
.
.
.
}
.alignmentGuide(.myAlignment) { d in
d[.bottom] - halfCircleHeight
}
Now critically the CirclesView specifies what alignment it wants with (alignment: .myAlignment) and the Up/DownContainerView returns where that is with .alignmentGuide(.myAlignment)
This is the result:
Now the eagle eyed amongst you will have noticed that the top row blue, red and yellow circles do not line up exactly, and similarly the bottom red and pink circles do not line up exactly. This is because I have used a constant
private let halfCircleHeight = 6.0
for my calculations (half the default system font height.) Text has a little bit of white space above and below the line that I have not accounted for, and variations in font size would also misalign things slightly.
This is where preferences would come in. If you have different sized Circles or .padding or some other effect you may need to get the Up/DownContainerViews to interrogate their child view preferences to get the correct value to use in their alignment guide calculation. If this was wanted the child would use an anchorPreference to record its centre and the Up/DownContainerView would choose between all the child view preferences and use the appropriate one in its alignment guide calculation. (This is left as an exercise for the reader.) By the way, an anchorPreference is simply a preference that carries with it information about the coordinate system it is defined. The child can set the value in its coordinate system (its own centre) and the parent can read the value in its coordinate system (where that is in the parent view).
In summary there are no conflicts with alignment guides because there is only one value and therefore nothing to conflict with. Hopefully this explains why the OPs two green circles .myAlignment which have defaulted to .center aligns with the VStack (in the DownContainerView) .myAlignment which has also defaulted to .center.

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