I need an advice on how to show last text of array and change what I'm displaying. I have a CoreData with 16 text. I need to display on appear last to Strings. If I press left button It updates and display 7. On second tap - another 7. So, the max number of displayed phrases is 7 (7+7+2). I've figured out that I can use .suffix()
ForEach(MoodEntryController().savedMoods.suffix(number)) { mood in
Text(mood.mood!)
.foregroundColor(.white)
}
Also I'm updating number with these buttons:
Button {
if number != 0 {
number -= 1
}
} label: {
ZStack {
Circle()
.fill(Color("navigation"))
.frame(width: 26)
Image(systemName: "chevron.left")
.foregroundColor(.white)
.font(.system(size: 14))
}
}
Button {
number += 1
} label: {
ZStack {
Circle()
.fill(Color("navigation"))
.frame(width: 26)
Image(systemName: "chevron.right")
.foregroundColor(Color("inactive"))
.font(.system(size: 14))
}
}
So I need to understand how to display 7 Strings from array and if it more than 7, display new ones from the begging. Also I need an ability to navigate from one 7 to another.
Related
In my project I've got a view where I use a ForEach to load each line of my structured array. This array is filled via a Firebase download when the app is started but to be more responsive I would like to load only some last value. I've got a date value in each line and I would like for example the only 10 first lines where the first line is the most recent entry !
The number of last entry to show is saved via the #AppStorage in the user setting part of the app.
And I'm trying to implement a button "load more line" at the end of all my array lines.
My view is defined as follow :
//get the numbers of flights to load in my array or 10 by default
#AppStorage("flightToLoad") private var flightToLoad: Int = 10
#State var thisSessionFlightToLoad = flightToLoad
[...var body ...]
ScrollView{
VStack {
//First ForEach to set the Date value as Section Header :
ForEach(flightLibrary.uniqueDates, id: \.self) { date in
Section(header:
HStack{
Text(flightLibrary.frmt.string(from: date))
.font(.title3)
Spacer()
}
) {
//Second ForEach to show all the value of each line in my array
ForEach(flightLibrary.testFlight.filter({Calendar.current.isDate($0.date, inSameDayAs: date)})) { flight in
if flight <= thisSessionFlightToLoad{
ZStack {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color.white)
NavigationLink(
destination: {
// Code for destination view
}, label: {
LogbookCellView2(flight: flight)
}).accentColor(.black)
}
}else{
// Here I try to add a Button after the line allready loaded
Button{
thisSessionFlightToLoad = thisSessionFlightToLoad + 10
}label:{
Text("Load more flights")
}
}
}
}
}
}.padding(.horizontal, 8)
}
But this is totally not working ...
Hope to be clear in my question !
Thanks for your help !
My environment:
The code below is written in swift Playgrounds 4 on my company IPad.
Goal of the project:
I am trying to improve upon the classroom management tool of my school, which is basically a paper traffic light with a clothespin for each student. I want an App in which all of my 28 students are represented by a button each Those buttons are supposed to changes from green to yellow when tapped 4 times and to red if tapped thrice more. As a nice-to-have feature the total number ob button tabs (aka. warnings) should be displayed on the buttons. Also the buttons should be structured in a meaningful way (like the tables of my students; a 7 x 4 grid).
My progress so far:
I wrote a class (student), and created instances of that class to represent four of my favorite students. I packed them into an array (students) to easily create a button for each of them. In this class “state” is the amount of times the student has been warned (the button has been tapped) and “color” is supposed to be the color of the respective button.
As of this writing I need a variable (col) for handling of the color. I think that would be unnecessary but did not get the color change to work in a different way.
After structuring in a grid I create a button for each element in the array (students). On buttonPress the state and color are updated for the respective student and (for reasons unknown to me) col is updated as well.
The label of the button then displays the name, state and color of the respective student... or does it?
My problems at this point:
The state (as displayed on the buttons) updates only when the color changes and when it does it changes the color for all the buttons and not just the one. I would like the buttons to change their color individually and the label to update on each press of a button. Sadly I have not found the correct syntax to do so.
My questions:
How can I set a default color (green) within my class?
How do I persuade my buttons to change color individually?
How would I get my labels to update on buttonPress?
Thanks in advance!
Code
import SwiftUI
public class student{
public var first: String
public var last: String
public var state: Int
public var color: Color
public init(first: String, last: String, color: Color){
self.first = first
self.last = last
state = Int()
self.color = color
}
}
var John = student(first: "John", last: "Lennon", color: .green)
var Paul = student(first: "Paul", last: "McCartney", color: .green)
var Georg = student(first: "Georg", last: "Harrison", color: .green)
var Ringo = student(first: "Ringo", last: "Starr", color: .green)
var students = [John, Paul, Georg, Ringo]
struct ContentView: View {
#State private var col = Color.green
let columnLayout = Array(repeating: GridItem(), count: 7)
var body: some View {
NavigationView{
ScrollView{
LazyVGrid(columns: columnLayout){
ForEach(students, id: \.last) { student in
Button{
student.state += 1
//print(student.state)
if (student.state < 4) {
student.color = .green
}
else if (student.state >= 7){
student.color = .red
}
else {
student.color = .yellow
}
col = student.color
//print(col)
} label:{
ZStack{
RoundedRectangle(cornerRadius: 10, style: .continuous)
.foregroundColor(col)
.aspectRatio(2.0, contentMode: ContentMode.fit)
Text("\(student.first) - \(student.state)")
.foregroundColor(.black)
//.font(.largeTitle)
.scaledToFit()
}
}
}
}
}.navigationTitle("Classroom Management")
}.navigationViewStyle(.stack)
}
}
There are a couple of issues going on here. The first is that in SwiftUI, generally you want your model to be a struct, since SwiftUI Views respond well to changes in value types (like a struct) out-of-the-box.
public struct Student {
public var first: String
public var last: String
public var state: Int
public var color: Color
public init(first: String, last: String, color: Color){
self.first = first
self.last = last
state = Int()
self.color = color
}
}
var john = Student(first: "John", last: "Lennon", color: .green)
var paul = Student(first: "Paul", last: "McCartney", color: .green)
var georg = Student(first: "Georg", last: "Harrison", color: .green)
var ringo = Student(first: "Ringo", last: "Starr", color: .green)
Note that I've also changed your code a bit to use the Swift conventions of capitalizing type names and lowercasing variable names.
Next, since you're trying to change properties on the Students, you'll want to represent them with a #State array. Then, by using the new element binding syntax on the ForEach line (see the $ characters), you can get a reference to each Student that you can modify.
struct ContentView: View {
#State private var students = [john, paul, georg, ringo]
let columnLayout = Array(repeating: GridItem(), count: 7)
var body: some View {
NavigationView{
ScrollView{
LazyVGrid(columns: columnLayout){
ForEach($students, id: \.last) { $student in
Button{
student.state += 1
if (student.state < 4) {
student.color = .green
}
else if (student.state >= 7){
student.color = .red
}
else {
student.color = .yellow
}
} label:{
ZStack{
RoundedRectangle(cornerRadius: 10, style: .continuous)
.foregroundColor(student.color)
.aspectRatio(2.0, contentMode: .fit)
Text("\(student.first) - \(student.state)")
.foregroundColor(.black)
.scaledToFit()
}
}
}
}
}.navigationTitle("Classroom Management")
}.navigationViewStyle(.stack)
}
}
There are 2 questions that I have identified in your request that are the key to your problem.
How can I set a default color (green) within my class?
Just create a default value inside your class definition. Replace the existing init() of your class with:
public init(first: String, last: String, color: Color = .green) // Default value, you can even omit the colour in your initializer
How do I persuade my buttons to change color individually?
First, your view has a variable col that retains the same value throughout the view. That's the color of your button. Just delete it and use each student's color:
.foregroundColor(student.color)
Edit
Second, students is an array of structs, they will not publish changes. One workaround (promoted also by Apple due to the benefits of SwiftUI) is to create an additional view that will change the state every time the single student changes.
So, create another view called like "StudentButton", and inside that view you can have the #State property for the single student.
Here's the code that I tested and works (I have modified a little bit the layout but you can setup your view as you wish):
struct Example: View {
#State private var students = [john, paul, georg, ringo]
let columnLayout = Array(repeating: GridItem(), count: 4)
var body: some View {
NavigationView{
ScrollView{
LazyVGrid(columns: columnLayout){
ForEach(students, id: \.last) { student in
// Call another view here
StudentButton(student)
}
}
}.navigationTitle("Classroom Management")
}.navigationViewStyle(.stack)
}
}
// Here's the other view
struct StudentButton: View {
// This is the state var of each single student
#State private var student: Student
init(_ student: Student) {
self.student = student
}
var body: some View {
Button{
student.state += 1
if (student.state < 4) {
student.color = .green
}
else if (student.state >= 7){
student.color = .red
}
else {
student.color = .yellow
}
} label:{
// I changed this just to visualize better, but just do as you please
Text("\(student.first) - \(student.state)")
.foregroundColor(.black)
.padding()
.background {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.foregroundColor(student.color)
.aspectRatio(2.0, contentMode: .fit)
}
}
}
}
How can I reformat the code of GridStack in the view body so I may duplicate it per row, such as 1 row of 2 columns, 2 rows of 8 columns, 4 rows of 18 columns, 2 rows of 15 columns?, I am cutting out the holes with this Hypothesis to shape an Interactive Periodic Table, refer to image attached.
#jnpdx has provided an example for display function per cell coordinate, along with this i will need an ontapgesture to operate the overlay of data per cell so I may send information to other menu fields.
#jnpdx so now right before the roundedrectangle in this edit and turn on the display function commented out, i need to some how over lay the custom data per cell instead of hydrogen on every cell plus create an ontapgesture to send globally to other menu fields in the application?
struct GridStack<Content: View>: View {
let rows: Int
let columns: Int
let content: (Int, Int) -> Content
//func shouldDisplayAtCoord(row: Int, column: Int) { if row == 0 && column > 1 { return true } }
var body: some View {
VStack {
ForEach(0 ..< rows, id: \.self) { row in
HStack {
ForEach(0 ..< columns, id: \.self) { column in
ZStack{
RoundedRectangle(cornerRadius: 5)
.fill(Color.brown)
.frame(width: 40, height: 50)
Image("RadicalDeepscale30").opacity(0.4)
content(row, column)
}
}
}
}
}
}
init(rows: Int, columns: Int, #ViewBuilder content: #escaping (Int, Int) -> Content) {
self.rows = rows
self.columns = columns
self.content = content
}
}
// An example view putting GridStack into practice.
struct DETEView: View {
var body: some View {
VStack(alignment: .center) {
Text("DART Edge Table of Elements")
.font(.largeTitle)
.bold()
//.colorInvert()
.padding(.top, 20)
.shadow(radius: 3)
}
VStack(alignment: .center) {
HStack(alignment: .center){
VStack(alignment: .center) {
GridStack(rows: 9, columns: 18) { row, col in
VStack(alignment: .leading){
HStack(alignment: .top) {
VStack(alignment: .leading) {
Text("1")
.font(.system(size: 9))
// .bold()
//.shadow(radius: 1)
}
}
HStack(alignment: .center) {
VStack(alignment: .center){
Text("H")
.font(.system(size: 12))
.bold()
//.shadow(radius: 1)
Text("Hydrogen")
.font(.system(size: 7))
//.bold()
// .shadow(radius: 1)
//.padding(.top, 1)
}
}
}
}
}
}
}.frame(width: 950, height: 670, alignment: .top).padding(.top, 20)
}
}
This is a simple implementation using a similar strategy to what we discussed in the comments/chat. In this case, instead of using a function to determine whether the element should be displayed, it just looks it up in a table that lists the elements and stores them based on their coordinates on the grid.
struct GridStack<Content: View>: View {
let rows: Int
let columns: Int
let content: (Int, Int) -> Content
var body: some View {
VStack {
ForEach(0 ..< rows, id: \.self) { row in
HStack {
ForEach(0 ..< columns, id: \.self) { column in
content(row,column)
}
}
}
}
}
}
struct ElementCoordinate: Hashable {
var row : Int
var column: Int
}
struct Element {
var atomicNumber: Int
var name : String
}
let tableOfElements : [ElementCoordinate:Element] = [
ElementCoordinate(row: 1, column: 1):Element(atomicNumber: 1, name: "Hydrogen"),
ElementCoordinate(row: 1, column: 18):Element(atomicNumber: 2, name: "Helium"),
ElementCoordinate(row: 2, column: 1):Element(atomicNumber: 3, name: "Lithium"),
ElementCoordinate(row: 2, column: 2):Element(atomicNumber: 4, name: "Beryllium"),
ElementCoordinate(row: 2, column: 13):Element(atomicNumber: 5, name: "Boron"),
ElementCoordinate(row: 2, column: 14):Element(atomicNumber: 6, name: "Carbon"),
]
struct ContentView: View {
var body: some View {
GridStack(rows: 9, columns: 18) { row, column in
ZStack{
if let element = tableOfElements[ElementCoordinate(row: row + 1, column: column + 1)] {
RoundedRectangle(cornerRadius: 5)
.fill(Color.brown)
Image("RadicalDeepscale30").opacity(0.4)
Text(element.name)
}
}
.frame(width: 40, height: 50)
}
}
}
Note the + 1 on the row and column numbers when querying the grid, since row and column are zero-based in the Swift code, but my tableOfElements starts with an index of 1.
This should show you not only how to do the layout, but how to associate each coordinate with a specific element.
Obviously the implementation details may change to fit your own model, but this should give you an idea of how to get started.
I'm doing an Apple Watch app, using SwiftUI. The idea is to display different activity content in a single watch ContentView using four individual subviews within the ContentView Body View. I don't want to use a List to display the activity content, but rather multiple custom views within the ContentView.
I would like each individual subview to display unique content from my model.
My model is called QuadActivity and has the following content:
struct QuadActivity: Identifiable {
let id = UUID()
var activityImage: Image
var activityTitle: String
}
I currently have created an extension to QuadActivity as follows, to hold some hardcoded test data:
extension QuadActivity {
static func all() -> [QuadActivity] {
return [
QuadActivity(activityImage: activityImage1, activityTitle: "activity1"),
QuadActivity(activityImage: activityImage2, activityTitle: "activity2"),
QuadActivity(activityImage: activityImage3, activityTitle: "activity3"),
QuadActivity(activityImage: activityImage4, activityTitle: "activity4")]
}
}
My ContentView.swift Body view is made up of a VStack with 2 HStacks embedded. Each HStack contains 2 of my subviews with miscellaneous spacers and padding modifiers. Each of the subviews should display the content of one of the array elements from an instance property:
var activityArrayEntry = QuadActivity.all()
Thus HStack 1 should display activityImage1 and activity1 and activityImage2 and activity2. The other HStack should display the array elements for items 3 and 4.
I can't figure out how to access each of the activityArrayEntry array elements and display each one in one of the subviews.
I was thinking I could use a:
ForEach(activityArrayEntry) { activity in
VStack and embedded HStack code here}
and display the subview content by looping through the ForEach above.
However, since all my VStack and HStack and subview code is within the ForEach loop, the same array element activity content would be displayed for all subviews because the loop encompasses all the view information for a single pass of the loop. I want each subview to display one of the unique array element's content.
If I move the ForEach within the ZStack and HStack code for each HStack subview display section, it will loop through the array entries, but the loop won't encompass all the subviews code and I won't get all the subviews to display only the activity array content from 1 array element.
Maybe using a ForEach loop is not the way. Is there another way to access the individual array elements from my instance variable such that each unique array element is used only in 1 of the subviews?
Again, how do I get the overall ContentView.swift to display the four subviews within the ZStack and HStack structure so that each subview displays the activity content of only 1 of the array elements.
Here is my ContentView so far. Note a number of commented lines that I will eventually comeback to in order to use an Observed Object model approach from a #Published Observable Object of my model. This will eventually be (maybe) the approach instead of the function all() from my model that I'm using now with hardcoded data to test the data flow in my app... thus the original question/issue.
Note
Call to QuadView() is just a call to an extracted subview where I define the display of the subview (simple Image and Text):
import SwiftUI
struct ContentView: View {
// #ObservedObject var quadViewVM = QuadViewVM()
var activityArrayEntry = QuadActivity.all()
var body: some View {
ZStack {
HStack {
Divider()
.frame(height: 175.0)
}
.edgesIgnoringSafeArea(.horizontal)
ForEach(activityArrayEntry) { activity in
VStack {
HStack(alignment: .center) {
QuadView(activityTitle: "\(activity.activityTitle)", activityImage: activity.activityImage)
// NavigationLink(destination: QuadDetail(content: , newActivity: false)) {
//
// }
.frame(width: 85.0, height: 100.0)
.buttonStyle(PlainButtonStyle())
Spacer()
QuadView(activityTitle: "\(activity.activityTitle)", activityImage: activity.activityImage)
// NavigationLink(destination: QuadDetail()) {
//
// }
.frame(width: 85.0, height: 100.0)
.buttonStyle(PlainButtonStyle())
}
.padding(.horizontal, 15.0)
.padding(.bottom, -10.0)
Divider()
HStack(alignment: .center) {
QuadView(activityTitle: "\(activity.activityTitle)", activityImage: activity.activityImage)
// NavigationLink(destination: QuadDetail()) {
//
// }
.frame(width: 85.0, height: 100.0)
.buttonStyle(PlainButtonStyle())
Spacer()
QuadView(activityTitle: "\(activity.activityTitle)", activityImage: activity.activityImage)
// NavigationLink(destination: QuadDetail()) {
//
// }
.frame(width: 85.0, height: 100.0)
.buttonStyle(PlainButtonStyle())
}
.padding([.leading, .bottom, .trailing], 15.0)
.padding(.top, -10.0)
.padding(.top, 30.0)
.edgesIgnoringSafeArea(.horizontal)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Instead of static QuadActivity.all() you can store your data in the ViewModel:
class QuadViewVM: ObservableObject {
#Published var quadActivities: [QuadActivity] = [
QuadActivity(activityImage: activityImage1, activityTitle: "activity1"),
QuadActivity(activityImage: activityImage2, activityTitle: "activity2"),
QuadActivity(activityImage: activityImage3, activityTitle: "activity3"),
QuadActivity(activityImage: activityImage4, activityTitle: "activity4"),
]
}
In your ContentView you can create a grid by using two ForEach loops (as it's a 2D grid):
struct ContentView: View {
#ObservedObject var quadViewVM = QuadViewVM()
let columnCount = 2
var rowCount: Int {
quadViewVM.quadActivities.count / columnCount
}
var body: some View {
ZStack {
// Horizontal divider
VStack {
Divider()
}
.edgesIgnoringSafeArea(.horizontal)
// Vertical divider
HStack {
Divider()
}
.edgesIgnoringSafeArea(.vertical)
activityGrid
}
}
var activityGrid: some View {
VStack {
ForEach(0 ..< self.rowCount) { row in
HStack {
ForEach(0 ..< self.columnCount) { column in
self.quadView(row: row, column: column)
}
}
}
}
}
func quadView(row: Int, column: Int) -> some View {
let activity = quadViewVM.quadActivities[row * columnCount + column]
return QuadView(activity: activity)
.frame(width: 85.0, height: 100.0)
.buttonStyle(PlainButtonStyle())
}
}
The QuadView is extracted to another function to make it more readable and easier to apply view modifiers.
I'd also recommend passing the whole QuadActivity variable to the QuadView (instead of its single components) - specifically when you need them all:
struct QuadView: View {
let activity: QuadActivity
var body: some View {
...
}
}
Hello wonderful helpers!
I am trying to load a detailView with the correct information from an Array to my MotherView when a ForEach-button is being pressed.
I don't know how to pass on which card has been pressed, to remember the index when i then try to load my DetailView with the same information. It's the last "index" there which sets me off. If i replace that with [0] it will display, all good, but just the sample data from the first item. Anyone who can help a very frustrated fresh coder?
ZStack {
ScrollView(.horizontal) {
HStack {
Button(action: {self.showDetailView.toggle()}) {
ForEach(flowerData) { index in
FlowerCard(flower: self.flower)
}
}
}
}
if showDetailView {
FlowerDetailView(flower: self.flower[index]
}
}
Why don't use button (or even onTapGesture) for every card, like (scratchy)
#State private var clickedCard: Int? = nil
...
ForEach(flowerData) { index in
Button(action: {
self.clickedCard = index // << store in member
self.showDetailView.toggle()
}) {
FlowerCard(flower: self.flower)
}
}
...
if showDetailView && index != nil {
FlowerDetailView(flower: self.flower[index!]
}