SwiftUI Picker - # Stars based on ForEach iteration - swiftui

I'm trying to implement a picker which represents a star rating however I'm struggling to implement this in that each row id displaying only 1 star. Here's the code I have so far...
Picker("Rating", selection: $rating) {
ForEach(1..<6, id: \.self) { rating in
HStack {
ForEach(1..<rating, id: \.self) { star in
Image(systemName: "star")
}
}
}
}
What I'm trying to achieve is a picker in which the first row will show 1 star, next row 2 stars, 3, 4 etc.
I'm convinced I'm butchering this code and there is most likely an easy solution but I have googled far and wide and yet I haven't stumbled across a solution. Any help would be greatly appreciated.

I have tried all things related to images but multiple images are not work in a single row so I have added star emoji as a string.
var body: some View {
Picker("Rating", selection: $ratingg) {
ForEach(1..<6, id: \.self) { rating in
Text(String(repeating: "✩", count: rating))
}
}
}

struct Picker: View {
#Binding var selection: Int
var body: some View {
VStack(alignment: .leading) {
ForEach(1..<6, id: \.self) { rating in
HStack {
ForEach(1..<(rating + 1), id: \.self) { star in
Image(systemName: "star")
}
}
.padding(.vertical, 4)
.onTapGesture {
selection = rating
print("rating now is", selection)
}
}
}
}
}

Related

List Is scrolling only in single row

I am trying to display a list of enums with a section head.
I am iterating through 2 enums to display them in the same row.
The problem I am having is that the complete list is only showing in one row instead of each individual item displaying on its own row.
Why is this happening?
struct DisplayRow: View {
var body: some View {
List(0..<flaps.allCases.count, id: \.self) {index in
HStack {
Text(flaps.allCases[index].rawValue)
.frame(maxWidth:.infinity, alignment: .leading)
Text(woundscode.allCases[index].rawValue)
}
.padding(.all)
.border(Color.gray)
}
}
struct ContentView: View {
var body: some View {
List {
Section(header: Text("Flaps")) {
DisplayRow()
}
Section(header: Text("test")) {
Text("2")
Text("3")
}
}
I placed the 2nd section just as test.
Thanks in advance.
screenshot

How to recreate the grid (in screenshot) in SwiftUI without breaking navigation?

I am trying to recreate a layout similar to the Reminders app. Looking at it makes me think it was built with SwiftUI. I also believe Apple mentioned so in one of the WWDC videos (can't remember which one).
This above screenshot seems to be a List, with a LazyVGrid as the first View inside the List. Tapping on each of the items in the LazyVGrid, such as Today, Scheduled, All and Flagged, navigates to the relevant screen, which means they are all NavigationLinks. Also note that the LazyVGrid has 2 columns.
And then there is another section "My Lists" which has rows which look like regular list rows in a List with style .insetGrouped. Also, every item in this Section is a NavigationItem, and thus comes with the disclosure indicator on the right as usual. Recreating this is trivial, so it has been left out from the MRE.
I am having trouble recreating the first section, which has that LazyVGrid. I faced 3 problems (as mentioned in the image), of which I have been able to solve the first one only. The other two problems remain. I want to know if this MRE can be fixed, or is my entire approach incorrect.
I am including a minimum reproducible example below.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
RemindersView()
}
}
}
struct RemindersView: View {
private var columns: [GridItem] = [GridItem(.adaptive(minimum: 150))]
private var smartLists: [SmartList] = SmartList.sampleLists
var body: some View {
NavigationView {
List {
Section(header: Text("Using LazyVGrid")) {
grid
}
Section(header: Text("Using HStack")) {
hstack
}
}
.navigationTitle("Store")
}
.preferredColorScheme(.dark)
}
private var grid: some View {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(smartLists) { smartList in
// This use of **ZStack with an EmptyView with opacity 0** is a hack being used to avoid the disclosure indicator on each item in the grid
ZStack(alignment: .leading) {
NavigationLink( destination: SmartListView(list: smartList)) {
EmptyView()
}
.opacity(0)
SmartListView(list: smartList)
}
}
}
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear)
}
private var hstack: some View {
ScrollView(.horizontal) {
HStack {
ForEach(smartLists) { smartList in
NavigationLink(destination: SmartListView(list: smartList)) {
SmartListView(list: smartList)
}
.buttonStyle(.plain)
}
}
}
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear)
}
}
struct RemindersView_Previews: PreviewProvider {
static var previews: some View {
RemindersView()
}
}
struct SmartList: Identifiable {
var id: UUID = UUID()
var title: String
var count: Int
var icon: String
var iconColor: Color
static var sampleLists: [SmartList] {
let today = SmartList(title: "Today", count: 5, icon: "20.circle.fill", iconColor: .blue)
let scheduled = SmartList(title: "Scheduled", count: 12, icon: "calendar.circle.fill", iconColor: .red)
let all = SmartList(title: "All", count: 77, icon: "tray.circle.fill", iconColor: .gray)
let flagged = SmartList(title: "Flagged", count: 5, icon: "flag.circle.fill", iconColor: .orange)
return [today, scheduled, all, flagged]
}
}
struct SmartListView: View {
var list: SmartList
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .center) {
Image(systemName: list.icon)
.renderingMode(.original)
.font(.title)
.foregroundColor(list.iconColor)
Spacer()
Text("\(list.count)")
.font(.system(.title, design: .rounded))
.fontWeight(.bold)
.padding(.horizontal, 8)
}
Text(list.title)
.font(.system(.headline, design: .rounded))
.foregroundColor(.secondary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.gray.opacity(0.25))
)
.padding(2)
.frame(minWidth: 150)
}
}
EDIT 1: Adding video demo of what editing the dynamic Grid looks like and how the Grid has dynamic grid items (via the Edit button at the top right): https://imgur.com/a/TV0kifY

SwitUI - VStack - Jumping when text entered

I am developing a basic passcode entry screen, consisting off a top Stack to display currently entry, then some HStack's displaying the numbers
VStack(){
HStack(spacing: 20){
ForEach(codes,id: \.self){i in
Text("*")
}
}
HStack(){
<Number 1 - 3>
}
HStack(){
<Number 4 - 6>
}
HStack(){
<Number 7 - 9>
}
HStack(){
<Number 0>
}
}
This issue im facing is when there is no passcode entered the top HStack dosnt use up any space, so has a vertical height of 0, when I enter a code, it forces the whole view to jump a little as the view resizes.
How can I stop that so
If I'm being honest, it was quiet fun to build ! 😄 Don't forget to mark this answer as the right one if it solved your issue. ✅
PROBLEM
The jumping effect is due to SwiftUI updating all views positions based on available space calculated based on your content (passcode digits). The font, font weight, text size, etc… all has an effect on the available space left for other views.
SOLUTION
To avoid that, you need to a predefined frame that will let the parent view know that your digits will never take more space. Doing so, each update won't effect the position of any other view because the allocated top space would always be size you specified and not the digits sizes (or absence).
CODE
import SwiftUI
import Combine
// Using Combine to manage digits and future network calls…
class PasscodeManager: ObservableObject {
let codesQuantity = 4
#Published var codes = [Int]()
}
struct PasscodeView: View {
#StateObject private var manager = PasscodeManager()
var body: some View {
VStack {
Spacer()
// Dots placeholders and passcode digits
selectedCodes
Spacer()
// Numberpad
PasscodeLine(numbers: 1...3) { add(number: $0) }
PasscodeLine(numbers: 4...6) { add(number: $0) }
PasscodeLine(numbers: 7...9) { add(number: $0) }
PasscodeLine(numbers: 0...0) { add(number: $0) }
Spacer()
}
.padding()
}
var selectedCodes: some View {
let minDots = manager.codes.count == manager.codesQuantity ? 0:1
let maxDots = manager.codesQuantity - manager.codes.count
return HStack(spacing: 32) {
ForEach(manager.codes, id: \.self) { Text("\($0)") }
if maxDots != 0 {
ForEach(minDots...maxDots, id: \.self) { _ in
Circle().frame(width: 12)
}
}
}
.font(.title.bold())
// Setting a default height should fix your problem. 🙂
.frame(height: 70)
}
func add(number: Int) {
guard manager.codes.count < manager.codesQuantity else { return }
manager.codes.append(number)
}
}
struct PasscodeLine: View {
let numbers: ClosedRange<Int>
var select: (Int) -> Void
var body: some View {
HStack {
ForEach(numbers, id: \.self) { number in
Spacer()
Button(action: { select(number) },
label: {
Text("\(number)")
.font(.title)
.fontWeight(.medium)
.foregroundColor(Color(.label))
.padding(32)
.background(Color(.quaternarySystemFill))
.clipShape(Circle())
})
}
Spacer()
}
}
}
RESULT

Multiple Picker views: Inconsistent, cycle drop down lists

I have a form with three different picker views. When I run the app and click on one of the pickers, the drop-down content is populated from another picker and it spontaneously cycles through the other two picker contents before returning to the main view. I am gogin to kick myself when someone points to something very fundamental and basic.... but here is the code . And thanks in advance!
var body: some View {
Form{
VStack {
HStack {
Text("PaO2")
TextField("mmHg", text: $PaO2)
.keyboardType(.numberPad)
Spacer()
Text("O2(%)")
TextField("%", text: $FiO2)
.keyboardType(.numberPad)
}
Toggle("Mechnical Ventilation", isOn: $MV)
HStack {
Text("Platelets")
TextField("(x1000)", text: $Platelets)
.keyboardType(.numberPad)
}
Picker(selection: $GCSSelected, label: Text("Glasgow Coma Scale")) {
ForEach(0..<GCS.count){ index1 in
Text(self.GCS[index1]).tag(index1)
}
}
Spacer()
Picker(selection: $HDSelected, label: Text("MAP/use of vasoactive Rx")){
ForEach(0..<HD.count){ index2 in
Text(self.HD[index2]).tag(index2)
}
}
HStack{
Text("Bilirubin")
TextField("mg/dL", text: $Bili)
.keyboardType(.numbersAndPunctuation)
}
Picker(selection: $RenalSelected, label: Text("Creatinine or Urine output")){
ForEach(0..<Renal.count){ index3 in
Text(self.Renal[index3]).tag(index3)
}
}
}
}
}
}
The issue is due to used single view for entire Form content, but you should not, so
var body: some View {
Form{
VStack { // << remove this container and let every picker be in own row

SwiftUI: Prevent view being inside VStack to expand

I have such VStack with list inside it
VStack(alignment: .leading, spacing: 16) {
Text("Contacts")
.font(.custom("AvenirNext-DemiBold", size: 20))
.foregroundColor(Color("DarkTitle"))
.padding(8).layoutPriority(1)
List(self.contacts) { contact in
ContactOption(contact: contact)
.padding(.horizontal, 4)
} //.frame(height: 240)
}
The problem with this code is that List tries to expand content as much as it can here taking up entire screen in spite of having just 4 contacts.
I can set this height to fixed value using frame(height: 240)
I consider wether there is possibility to enforce List to wrap its content like Text() view does.
i.e. if there is 4 rows in List wrap content to display just this 4 rows, if there is 8 rows expand to this 8 rows. Then I could set some max height ex. 400 above which List could not expand anymore and then it will be scrollable.
ok, i tried a bit and i am not sure whether you can use it or not, but check this out: (just tap on add and remofe to see how the list gets bigger and smaller)
struct ContactOption : View {
var contact: String
var body: some View {
Text(contact)
}
}
struct ListView : View {
var contacts: [String]
var body : some View {
// List(self.contacts, id: \.self) { contact in
// ContactOption(contact: contact)
// .padding(.horizontal, 4)
// }
List {
ForEach (contacts, id: \.self) { contact in
Text (contact)
}
}
}
}
struct ContentView: View {
#State var contacts = ["Chris", "Joe", "Carla", "another"]
var body: some View {
VStack() {
HStack {
Button("Add") {
self.contacts.append("dust")
}
Button("Remove") {
self.contacts = self.contacts.dropLast()
}
}
Text("Contacts")
.foregroundColor(Color.blue)
.padding(8).layoutPriority(1)
Form {
ListView(contacts: contacts)
Section(footer: Text("hi")) {
Text("hi")
}
}
Divider()
Text("end list")
.foregroundColor(Color.orange)
}
}
}