Right, I'm new to swift(ui) and have been experimenting - my current issue is to do with a basic card app.
I have generated a deck of card objects (in this case 10 items) and first displayed them all in a stack using a ForEach loop - no problem there.
I then went on to pull out one card from the array to display on it's own nd this is where everything comes to a crashing halt. I suspect it's something simple and obvious but how do I get a single card from my deck for display?
struct Card: Identifiable, Equatable{
let id = UUID()
var factor1: Int = 0
var factor2: Int = 0
var product: Int = 0
var possibleAnswers: [Int] = []
}
struct CardView: View{
let card: Card
var body: some View{
VStack {
VStack { // Actual Card
Spacer()
Text("\(card.factor1) X \(card.factor2)")
.font(.title)
.foregroundColor(.black)
// .bold()
.multilineTextAlignment(.center)
.padding(.horizontal)
Spacer()
}
.padding()
.frame(width: 300, height: 200)
.background(
Rectangle()
.foregroundColor(cardColor(of: card))
)
HStack {
Spacer()
ForEach(card.possibleAnswers, id:\.self) { answer in
Text("\(answer)")
.foregroundColor(.black)
.frame(width: 75, height: 50)
.background(
Rectangle()
.foregroundColor(cardColor(of: card))
)
Spacer()
}
}
.padding()
}
}
func cardColor(of card: Card) -> Color {
return Color(red:228/255, green: 229/255, blue: 229/255)
}
}
class Deck: ObservableObject {
#Published var numberCards = 10
#Published var cards = [Card]()
func buildDeck() {
cards.removeAll()
let cardCount = 1...self.numberCards
for _ in cardCount {
// Set up new card
var myCard = Card()
myCard.factor1 = .random(in: 1...12)
myCard.factor2 = .random(in: 1...12)
myCard.product = myCard.factor1 * myCard.factor2
myCard.possibleAnswers.append(myCard.product)
for _ in 0...2{
myCard.possibleAnswers.append(Int.random(in: 1..<145))
}
myCard.possibleAnswers.shuffle()
// add card to the deck
self.cards.append(myCard)
}
}
var count: Int {
return cards.count
}
}
struct ContentView: View {
#StateObject var deck = Deck()
var body: some View {
VStack {
Button(action: {
deck.buildDeck()
}) { // button text
Text("Click to add card. Current Number of cards in deck: \(deck.count)")
}
Spacer()
ZStack {
// ForEach(deck.cards) { card in
if let card = deck.cards[0] {
CardView(card: card)
}
// }
}
Spacer()
}
}
}
From the code provided, it looks like this error was caused by accessing an index that an array doesn't even have in the first place.
In this case, you're accessing the first element of cards while in buildDeck function you removed all the elements which may be the cause of the error while it's:
let card = deck.cards[0]
CardView(card: card)
You can try to use if let to make sure the element is present before accessing it.
if let card = deck.cards.first {
CardView(card: card)
}
Related
I'm trying to put some data from "ForEach" side by side but I don't know how to do it in a right way...
This is what've got
import SwiftUI
struct ContentView: View {
#StateObject private var vm = notaViewModel()
#State var puntajeMaximo: String
#State var notaMaxima: String
#State var notaMinima: String
#State var notaAprobacion: String
#State var notaExigencia: String
var body: some View {
ScrollView {
// puntajeMaximo = Maximun score you can get
ForEach(0...vm.puntajeMaximo, id: \.self) { score in
HStack {
if vm.puntajeMaximo / 2 >= score {
Text("\(score) -> \(vm.getAverage(puntos: Float(score)))")
.frame(width: 100, height: 50)
.background(Color("textFieldBackground"))
.cornerRadius(5)
.foregroundColor(.red)
}
if vm.puntajeMaximo / 2 < score {
Text("\(score) -> \(vm.getAverage(puntos: Float(score)))")
.frame(width: 100, height: 50)
.background(Color("textFieldBackground"))
.cornerRadius(5)
}
}
}
}
.onAppear {
vm.setParameters(pMax: puntajeMaximo, nMaxima: notaMaxima, nMinima: notaMinima, nAprobacion: notaAprobacion, nExigencia: notaExigencia)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(puntajeMaximo: "30", notaMaxima: "70", notaMinima: "10", notaAprobacion: "40", notaExigencia: "60")
}
}
As you can see, I have a column with numbers (score) from 0 to 30 and a Float next to it, the first 15 are red and I need them to be at the left and the others, from 15 to 30 to be at the right. I've been trying with HStack, Vstack, Grid and cannot get the answer
Please help, this is driving me crazy
1. Why you have taken String instead of Number when you need to compare the value.
2. We have two different way which I have shared below from which 2 one is appropriate answer according to me still you can choose any one as per your requirement.
ANSWER 1
var body: some View {
HStack{
VStack{
ScrollView(showsIndicators: false) {
ForEach(0...puntajeMaximo, id: \.self) { score in
HStack {
if puntajeMaximo / 2 >= score {
Text("\(score) -> \(score).\(score)")
.frame(width: 100, height: 50)
.background(.white)
.cornerRadius(10)
.foregroundColor(.red)
}
}
}
}
}.frame(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.height - 100)
.background(Color.orange)
VStack{
ScrollView {
ForEach(0...puntajeMaximo, id: \.self) { score in
HStack {
if puntajeMaximo / 2 < score {
Text("\(score) -> \(score).\(score)")
.frame(width: 100, height: 50)
.background(.white)
.cornerRadius(12)
}
}
}
}
}.frame(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.height)
.background(Color.yellow)
}
}
Answer 1 Output Image
Here you can check scroll hidden and some other difference in two scrollview
ANSWER 2 (Preferred)
// Need to create Item in List which requires to conform Identifiable protocol
struct ListItem: Identifiable {
let id = UUID()
let score: Int
}
struct ContentViews: View {
// #StateObject private var vm = notaViewModel()
#State var puntajeMaximo: Int
init(puntajeMaximo: Int) {
self.puntajeMaximo = puntajeMaximo
getData()
}
var leftData: [ListItem] = []
var rightData: [ListItem] = []
var body: some View {
HStack{
VStack{
List(leftData) { data in
Text("\(data.score) -> \(data.score).\(data.score)")
.frame(width: 100, height: 50)
.background(.gray.opacity(0.3))
.cornerRadius(10)
.foregroundColor(.red)
.listRowBackground(Color.clear)
}
}.frame(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.height - 100)
.background(Color.orange)
VStack{
List(rightData) { data in
Text("\(data.score) -> \(data.score).\(data.score)")
.frame(width: 100, height: 50)
.background(.gray.opacity(0.3))
.cornerRadius(10)
.foregroundColor(.black)
}
}.frame(width: UIScreen.main.bounds.width/2, height: UIScreen.main.bounds.height)
.background(Color.yellow)
}
}
// Must be called in init or before rendering the view
mutating func getData() {
for score in 0...puntajeMaximo {
if (puntajeMaximo / 2 >= score) {
leftData.append(ListItem(score: score))
} else if puntajeMaximo / 2 < score {
rightData.append(ListItem(score: score))
}
}
}
}
struct ContentViews_Previews: PreviewProvider {
static var previews: some View {
ContentViews(puntajeMaximo: 30)
}
}
Answer 2 Output Image
Required
A mutating method that separates the list for left and right view
A struct that conforms Identifiable protocol to pass in ListView as a row.
You can make changes to design as per you requirement.
As this method runs loop only 1 time and separates the list instead of Answer 1 which runs loop twice for both view
As the title says I am trying to create a grid of random numbers that must be clicked in ascending order and when pressed the background changes color so they're no longer visible.
I figured out how to create the grid from 0 to what ever size grid I want (5x5, 10x10, etc...)
I want the background to be white to start and then when the button is pressed it changes to black
The two biggest issues I'm having are all of the buttons turning black after I press on one button and the numbers randomize every time I press a button.
Any one have any ideas?
import SwiftUI
struct ContentView: View {
#State var buttonbackg = Color.white
#State var gridsize = 100
var body: some View {
//make grid
let cellCount = (gridsize/10)
let r = numbrand(min: 00, max: ((gridsize/10) * (gridsize/10) - 1))
ZStack{
Rectangle()
.ignoresSafeArea()
.background(Color.black)
VStack{
Text("Concentration Grid")
.font(.title)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.padding(.bottom)
Spacer()
}
VStack(spacing:3) {
Spacer()
ForEach(1...cellCount, id:\.self) { i in
HStack(spacing:3) {
Spacer()
ForEach(1...cellCount, id:\.self) { c in
let a = r()
ZStack {
Button(action:{
if (self.buttonbackg == .white) {
self.buttonbackg = .black
}
}){
Text("\(a)")
.foregroundColor(Color.black)
.frame(width: 28, height: 28)
.background(buttonbackg)
.cornerRadius(5)
.multilineTextAlignment(.center)
.scaledToFit()
.padding(.all, -2)
Spacer()
}
Spacer()
}
}
}
Spacer()
}
Spacer()
}
}
}
//^grid
func numbrand(min: Int, max:Int) -> () -> Int{
//let array: [Int] = Array(min...max)
var numbers: [Int] = []
return {
if numbers.isEmpty {
numbers = Array(min...max)
}
let index = Int(arc4random_uniform(UInt32(numbers.count)))
return numbers.remove(at: index)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Every-time you click a button, view redraws itself and thus your randomArray is created again
You need to create a Struct here which holds the property whether the
button is selected or not.
Give it a try this way:
struct concentrationGame: Hashable {
var id = UUID()
var number: Int = 0
var isSelected: Bool = false
}
class RandomNumbers: ObservableObject {
#Published var randomNumbers : [concentrationGame]
init() {
self.randomNumbers = (1...100).map{_ in arc4random_uniform(100)}.map({ concentrationGame(number: Int($0)) })
}
}
struct ContentView: View {
#ObservedObject var randomNumbers = RandomNumbers()
private var gridItemLayout = [GridItem(.adaptive(minimum: 50))]
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: gridItemLayout, spacing: 20) {
ForEach($randomNumbers.randomNumbers, id: \.self) { $randomNumbers in
Button(String(randomNumbers.number)) { randomNumbers.isSelected.toggle() }
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 50)
.foregroundColor(.white)
.background( randomNumbers.isSelected ? .white : .black)
.cornerRadius(10)
}
}
}
.padding()
.ignoresSafeArea(edges: .bottom)
.navigationTitle("Concentration Grid")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm trying to build a comment thread. So top level comments can all have nested comments and so can they and so on and so forth. But I'm having issues around scrolling and also sometimes when expanding sections the whole view just jumps around, and can have a giant blank space at the bottom. The code looks like this:
struct ContentView: View {
var body: some View {
VStack {
HStack {
Text("Comments")
.font(.system(size: 34))
.fontWeight(.bold)
Spacer()
}
.padding()
CommentListView(commentIds: [0, 1, 2, 3], nestingLevel: 1)
}
}
}
struct CommentListView: View {
let commentIds: [Int]?
let nestingLevel: Int
var body: some View {
if let commentIds = commentIds {
LazyVStack(alignment: .leading) {
ForEach(commentIds, id: \.self) { id in
CommentItemView(viewModel: CommentItemViewModel(commentId: id), nestingLevel: nestingLevel)
}
}
.applyIf(nestingLevel == 1) {
$0.scrollable()
}
} else {
Spacer()
Text("No comments")
Spacer()
}
}
}
struct CommentItemView: View {
#StateObject var viewModel: CommentItemViewModel
let nestingLevel: Int
#State private var showComments = false
var body: some View {
VStack {
switch viewModel.viewState {
case .error:
Text("Error")
.fontWeight(.thin)
.font(.system(size: 12))
.italic()
case .loading:
Text("Loading")
.fontWeight(.thin)
.font(.system(size: 12))
.italic()
case .complete:
VStack {
Text(viewModel.text)
.padding(.bottom)
.padding(.leading, 20 * CGFloat(nestingLevel))
if let commentIds = viewModel.commentIds {
Button {
withAnimation {
showComments.toggle()
}
} label: {
Text(showComments ? "Hide comments" : "Show comments")
}
if showComments {
CommentListView(commentIds: commentIds, nestingLevel: nestingLevel + 1)
}
}
}
}
}
}
}
class CommentItemViewModel: ObservableObject {
#Published private(set) var text = ""
#Published private(set) var commentIds: [Int]? = [0, 1, 2, 3]
#Published private(set) var viewState: ViewState = .loading
private let commentId: Int
private var viewStateInternal: ViewState = .loading {
willSet {
withAnimation {
viewState = newValue
}
}
}
init(commentId: Int) {
self.commentId = commentId
fetchComment()
}
private func fetchComment() {
viewStateInternal = .complete
text = CommentValue.allCases[commentId].rawValue
}
}
Has anyone got a better way of doing this? I know List can now accept a KeyPath to child object and it can nest that way, but there's so limited design control over List that I didn't want to use it. Also, while this code is an example, the real code will have to load each comment from an API call, so List won't perform as well as LazyVStack in that regard.
Any help appreciated - including a complete overhaul of how to implement this sort of async loading nested view.
I want to allow users to change the order of photos in the list. Tinder has something like that. I'm trying to add a drag gesture to the item but nothing happened.
import SwiftUI
struct Row: Identifiable {
let id = UUID()
let cells: [Cell]
}
struct Cell: Identifiable {
let id = UUID()
let imageURL: String
#State var offset = CGSize.zero
}
struct PhotosView: View {
#State var rows = [Row(cells: [Cell(imageURL: "gigi"), Cell(imageURL: "hadid"), Cell(imageURL: "gigihadid")]), Row(cells: [Cell(imageURL: "gigi"), Cell(imageURL: "hadid")])]
var body: some View {
List {
ForEach(rows) { row in
HStack(alignment: .center, spacing:15) {
ForEach(row.cells) { cell in
Image(cell.imageURL)
.resizable()
.scaledToFill()
.frame(maxWidth: UIScreen.main.bounds.width / 3.6)
.clipped()
.cornerRadius(10)
.offset(x: cell.offset.width, y: cell.offset.height)
.gesture(
DragGesture()
.onChanged { gesture in
cell.offset = gesture.translation
}
.onEnded { _ in
if abs(cell.offset.width) > 100 {
// remove the card
} else {
cell.offset = .zero
}
}
)
}
if row.cells.count < 3 {
ZStack{
RoundedRectangle(cornerRadius: 10)
.fill(Color(UIColor.systemGray6))
Button(action: {}, label: {
Image(systemName: "person.crop.circle.fill.badge.plus")
.font(.title)
.foregroundColor(Color(UIColor.systemGray3))
})
}
}
}.padding(.vertical)
}.buttonStyle(PlainButtonStyle())
}.padding(.top, UIScreen.main.bounds.height / 8)
.navigationBarHidden(true)
}
}
What view looks like:
I only want users can change the order of photos via drag gesture.
I'm making an iOS app and I want to display some sort of default image (or just blank) in a row view, and then update them to new selected images by users. How the app currently looks like
How can I update the images in the cells to be the new ones users select?
This is how I am creating the row views and cells
import SwiftUI
struct Row: Identifiable {
let id = UUID()
let cells: [Cell]
}
extension Row {
}
extension Row {
static func all() -> [Row] {
var resultRow = [Row]()
for _ in 1...10 {
resultRow.append(Row(cells: [Cell(imageURL: "camera"), Cell(imageURL: "camera")]))
}
return resultRow
}
}
struct Cell: Identifiable {
let id = UUID()
let imageURL: String
}
And my UIView looks like this
import SwiftUI
struct PhotoUIView: View {
#State private var showImagePicker: Bool = false
#State private var image: Image? = nil
let rows = Row.all()
var body: some View {
VStack{
List {
ForEach(rows) { row in
HStack(alignment: .center) {
ForEach(row.cells) { cell in
Image(cell.imageURL)
.resizable()
.scaledToFit()
}
}
}
}.padding(EdgeInsets.init(top: 0, leading: -20, bottom: 0, trailing: -20))
image?.resizable()
.scaledToFit()
if (image == nil) {
Text("Upload photo to begin quick design")
}
Button("Select photo to upload") {
self.showImagePicker = true
}.padding()
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(10)
Button("Begin Quick Design") {
print("Here we upload photo and get result back")
}.padding()
.background(Color.green)
.foregroundColor(Color.white)
.cornerRadius(10)
}.sheet(isPresented: self.$showImagePicker) {
PhotoCaptureView(showImagePicker: self.$showImagePicker, image: self.$image)
}
}
}
struct PhotoUIView_Previews: PreviewProvider {
static var previews: some View {
PhotoUIView()
}
}
So you can do is, creaate an array of images and add those selected images with name like image_0, image_1, and so on. And then add the images in the cell using the array eg a[i]