Vertical alignment of HStack not working and not intuitive - swiftui

I have this code:
HStack (alignment:.center) {
Text("50")
ProgressView("", value: 50, total: 100)
Text("100")
Text("mg")
}
the result is
I want this
I have tried everything like adding alignment center to HStack, adding the progress view inside a VStack, etc
Why Apple never manage to make things related to interface design work intuitively?

There is another way like not using "", and it works as you want.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Text("50")
ProgressView(value: 50, total: 100) // <<: Here
Text("100")
Text("mg")
}
}
}
The Version that you used is also good, but has another usage like this down code. And it needs the ProgressView pushed down for Text, actually, in your code ProgressView thinks that "" is the information to show! that is why you see that.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Text("50")
ProgressView("This part is for information to help", value: 50, total: 100)
Text("100")
Text("mg")
}
}
}

Related

Remove space created on List items in view - SwiftUI [duplicate]

I'm having an issues with a List inside a NavigationView since iOS 14 update.
Here is a simple breakdown of the code - I've striped everything that doesn't show the issue
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
}
}
}
}
This produces the following result:
I cant work out why the list is hovering in the center of the navigation view like that. As far as I can tell this should produce a listview that takes up all avaliable space (with the exception of the top where navigationbar would be).
Indeed when run on iOS 13.5 that is the result I get as pictured below:
I've had a read through the documentation but cant work out why this behaviour is suddenly happening.
Any help would be greatly appreciated.
Thanks
Problem
It looks like the default styles of a List or NavigationView in iOS 14 may in some cases be different than in iOS 13.
Solution #1 - explicit listStyle
It's no longer always the PlainListStyle (as in iOS 13) but sometimes the InsetGroupedListStyle as well.
You need to explicitly specify the listStyle to PlainListStyle:
.listStyle(PlainListStyle())
Example:
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
.listStyle(PlainListStyle()) // <- add here
}
}
}
}
Solution #2 - explicit navigationViewStyle
It looks like the NavigationView's default style can sometimes be the DoubleColumnNavigationViewStyle (even on iPhones).
You can try setting the navigationViewStyle to the StackNavigationViewStyle (as in iOS 13):
.navigationViewStyle(StackNavigationViewStyle())
Example:
struct ContentView: View {
var views = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(views, id: \.self) { view in
VStack {
Text("\(view)")
}
.background(Color.red)
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle()) // <- add here
}
}

swiftui 2.0 Image Gallery onTapgesture only shows the first image in the array

I am creating a reusable gallery view for an app and am having difficulties when any picture is tapped it suppose to become full screen but only the first picture in the array is shown every time no matter the picture tapped. Below is my code, thanks.
import SwiftUI
struct ReusableGalleryView: View {
let greenappData: GreenAppNews
let gridLayout: [GridItem] = Array(repeating: GridItem(.flexible()), count: 3)
#State private var fullscreen = false
#State private var isPresented = false
var body: some View {
VStack{
ScrollView{
LazyVGrid(columns: gridLayout, spacing: 3) {
ForEach(greenappData.GreenAppGallery, id: \.self) { item in
Image(item)
.resizable()
.frame(width: UIScreen.main.bounds.width/3, height: 150)
.onTapGesture {
self.isPresented.toggle()
print(" tapping number")
}
.fullScreenCover(isPresented: $isPresented) {
FullScreenModalView( imageFiller: item)
}
.background(Color.gray.opacity(0.5))
}
}
.padding(.horizontal)
}
}
}
}
This is an example of the json data:
{
"id" : "1",
"GreenAppGallery" : [
"Picture-1",
"Picture-2",
"Picture-3",
"Picture-4",
"Picture-5",
"Picture-6",
"Picture-7",
"Picture-8",
"Picture-9",
"Picture-10"
]
},
fullScreenCover, like sheet tends to create this type of behavior in iOS 14 when using isPresented:.
To fix it, you can change to the fullScreenCover(item: ) form.
Not having all of your code, I'm not able to give you an exact version of what it'll look like, but the gist is this:
Remove your isPresented variable
Replace it with a presentedItem variable that will be an optional. Probably a datatype that is in your gallery. Note that it has to conform to Identifiable (meaning it has to have an id property).
Instead of toggling isPresented, set presentedItem to item
Use fullScreenCover(item: ) { presentedItem in FullScreenModalView( imageFiller: presentedItem) } and pass it your presentedItem variable
Move the fullScreenCover so that it's attached to the ForEach loop rather than the Image
Using this system, you should see it respond to the correct item.
Here's another one of my answers that covers this with sheet: #State var not updated as expected in LazyVGrid

How to allow Text to grow enough to display entire contents without truncation?

I am trying to create a SwiftUI view that represents a chat conversation in a series of vertically arranged bubbles, like in Messages.
I'm having trouble figuring out how to display very long messages. I would like to display such messages simply as very large bubbles that display all of the text. For example, like Messages does this:
The problem I run into is that the Text view, on which my bubbles are based, pretty much does its own thing. This can lead to the entire text being displayed correctly for small messages, long messages being broken into several lines but still displayed in full, other long messages being reduced to a single line with an ellipse.
Consider the following code to create a sequence of messages:
import SwiftUI
struct MessageView: View {
var body: some View {
Text("This is a very long message. Can you imagine it will ever be displayed in full on the screen? Because I can‘t. I can tell you, the one other time I wrote a message this long was when we went to the picnic and uncle Bob whipped out his cigars and I had to vent on the family WhatsApp group.")
}
}
struct ContentView: View {
var body: some View {
VStack {
ForEach(0..<100, id: \.self) { i in
MessageView()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It results in this undesirable layout:
I have experimented with fixedSize and frame after reading several SwiftUI guides; this makes it possible to make bubbles large, but then they have a fixed size and/or won't grow when the text to display is even longer than expected.
How can I tell the MessageViews – or rather the Text views inside them – that they're free to take up as much space as they need vertically to render their text contents in full?
I found one interesting decision, using List and removing dividers:
struct ContentView: View {
init() {
UITableView.appearance().tableFooterView = UIView()
UITableView.appearance().separatorStyle = .none
}
var body: some View {
List {
ForEach(0..<50, id: \.self) { i in
VStack(alignment: .trailing, spacing: 20) {
if i%2 == 0 {
MessageView().lineLimit(nil)
} else {
MessageViewLeft()
}
}
}
}
}
}
and I made 2 structs, for demonstration:
struct MessageView: View {
var body: some View {
HStack {
Text("This is a very long message. Can you imagine it will ever be displayed in full on the screen? Because I can‘t. I can tell you, the one other time I wrote a message this long was when we went to the picnic and uncle Bob whipped out his cigars and I had to vent on the family WhatsApp group.")
Spacer(minLength: 20)
}
}
}
struct MessageViewLeft: View {
var body: some View {
HStack {
Spacer(minLength: 20)
Text("This is a very long message. Can you imagine it will ever be displayed in full on the screen? Because I can‘t. I can tell you, the one other time I wrote a message this long was when we went to the picnic and uncle Bob whipped out his cigars and I had to vent on the family WhatsApp group.")
}
}
}
the result is:
P.S. the answer should be .lineLimit(nil), but there is some bug with this in TextField. Maybe with Text this bug continues too
P.P.S. I wad hastened with the answer =(. You can set your VStack List into ScrollView:
var body: some View {
VStack {
ScrollView {
ForEach(0..<50, id: \.self) { i in
VStack(alignment: .trailing, spacing: 20) {
MessageView().padding()
}
}
}
}
}
and the result is:
Try to set your MessageView in a ScrollView.

How to create slot-machine metaphor in SwiftUI Picker?

Is it possible to create a slot machine with swiftUI to show two sets of values?
In UIKit, UIPickerView provides the option to have multiple components in your picker view. SwiftUI's Picker does not. However, you can use more than one Picker in an HStack instead. The perspective may look slightly different than a UIPickerView with multiple components in some instances, but to me it looks perfectly acceptable.
Here's an example of a slot machine with 4 pickers side by side and a button that "spins" the slot-machine when tapped (note that I disabled user interaction on the pickers so they can only be spun using the button):
enum Suit: String {
case heart, club, spade, diamond
var displayImage: Image {
return Image(systemName: "suit.\(self.rawValue).fill")
}
}
struct ContentView: View {
#State private var suits: [Suit] = [.heart, .club, .spade, .diamond]
#State private var selectedSuits: [Suit] = [.heart, .heart, .heart, .heart]
var body: some View {
VStack {
HStack(spacing: 0) {
ForEach(0..<self.selectedSuits.count, id: \.self) { index in
Picker("Suits", selection: self.$selectedSuits[index]) {
ForEach(self.suits, id: \.self) { suit in
suit.displayImage
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.clipped()
.disabled(true)
}
}
Button(action: self.spin) {
Text("Spin")
}
}
}
private func spin() {
self.selectedSuits = self.selectedSuits.map { _ in
self.suits.randomElement()!
}
}
}
This is just an example, and could no doubt be improved, but it's a decent starting point.
Keep in mind that this code does throw a warning in Xcode Beta 5 -
'subscript(_:)' is deprecated: See Release Notes for migration path.
I haven't had a chance to look into this, but the example still works and should help you with what you're trying to achieve.

Why does binding to the Picker not work anymore in swiftui?

When I run a Picker Code in the Simulator or the Canvas, the Picker goes always back to the first option with an animation or just freezes. This happens since last Thursday/Friday. So I checked some old simple code, where it worked before that and it doesn't work for me there, too.
This is the simple old Code. It doesn't work anymore in beta 3, 4 and 5.
struct PickerView : View {
#State var selectedOptionIndex = 0
var body: some View {
VStack {
Text("Option: \(selectedOptionIndex)")
Picker(selection: $selectedOptionIndex, label: Text("")) {
Text("Option 1")
Text("Option 2")
Text("Option 3")
}
}
}
}
In my newer code, I used #ObservedObject, but also here it doesn't work.
Also I don't get any errors and it builds and runs.
Thank you for any pointers.
----EDIT----- Please look at the answer first
After the help, that I could use the .tag() behind all Text()like Text("Option 1").tag(), it now takes the initial value and updates it inside the view. If I use #ObservedObject like here:
struct PickerView: View {
#ObservedObject var data: Model
let width: CGFloat
let height: CGFloat
var body: some View {
VStack(alignment: .leading) {
Picker(selection: $data.exercise, label: Text("select exercise")) {
ForEach(data.exercises, id: \.self) { exercise in
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise))
}
}
.frame(width: width, height: (height/2), alignment: .center)
}
}
}
}
Unfortunately it doesn't reflect changes on the value, if I make these changes in another view, one navigationlink further. And also it doesn't seem to work with the my code above, where I use firstIndex(of: exercise)
---EDIT---
Now the code above works if I change
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise))
into
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise)!)
because it couldn't work with an optional.
The answer summarized:
With the .tag() behind the Options it works. It would look like following:
Picker(selection: $selectedOptionIndex, label: Text("")) {
ForEach(1...3) { index in
Text("Option \(index)").tag(index)
}
}
If you use a range of Objects it could look like this:
Picker(selection: $data.exercises, label: Text("")) {
ForEach(0..<data.exercises.count) { index in
Text("\(data.exercises[index])").tag(index)
}
}
I am not sure if it is intended, that .tag() is needed to be used here, but it's at least a workaround.
I found a way to simplify the code a bit without the need of operating on indicies and tags.
At first, make sure to conform your model to Identifiable protocol like this (this is actually a key part, as it enables SwiftUI to differentiate elements):
public enum EditScheduleMode: String, CaseIterable, Identifiable {
case closeSchedule
case openSchedule
public var id: EditScheduleMode { self }
var localizedTitle: String { ... }
}
Then you can declare viewModel like this:
public class EditScheduleViewModel: ObservableObject {
#Published public var editScheduleMode = EditScheduleMode.closeSchedule
public let modes = EditScheduleMode.allCases
}
and UI:
struct ModeSelectionView: View {
private let elements: [EditScheduleMode]
#Binding private var selectedElement: EditScheduleMode
internal init?(elements: [EditScheduleMode],
selectedElement: Binding<EditScheduleMode>) {
self.elements = elements
_selectedElement = selectedElement
}
internal var body: some View {
VStack {
Picker("", selection: $selectedElement) {
ForEach(elements) { element in
Text(element.localizedTitle)
}
}
.pickerStyle(.segmented)
}
}
}
With all of those you can create a view like this:
ModeSelectionView(elements: viewModel.modes, selectedElement: $viewModel.editScheduleMode)