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]
Related
I want to separate the views, and I created a struct view to get only the image to make the code more cleaner, please I want to ask how can I pass the selected image to the next view, to make the code more cleaner and Can use this struct view everywhere in the project.
I put in second view PickAPhoto() but I'm not sure how to get the data from it.
Many Thanks,
struct PickAPhoto: View {
#State var imgSelected: UIImage = UIImage(named: "addCameraImg")!
#State var showAddPhotoSheet = false
// phppicker begin
#State private var showPhotoSheet = false
// phppicker end
#State private var sourceType: UIImagePickerController.SourceType = .camera
#State var image: Image? = nil
var body: some View {
Image(uiImage: imgSelected)
.resizable()
.cornerRadius(4)
// .frame(width: 200 , height: 200)
.padding(.top)
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 200, maxHeight: 200)
.transition(.slide)
HStack {
Spacer()
Button(action: {showAddPhotoSheet.toggle()}) {
Label("Take a photo", systemImage: "camera")
.foregroundColor(Color("BrandPrimary"))
}
.sheet(isPresented: $showAddPhotoSheet){
ImagePicker(imageSelected: $imgSelected, sourceType: $sourceType)
// ImageViewPicker()
}
Button(action: { showPhotoSheet = true }) {
Label("Choose photo", systemImage: "photo.fill")
.foregroundColor(Color("BrandPrimary"))
}
.fullScreenCover(isPresented: $showPhotoSheet) {
PhotoPicker(filter: .images, limit: 1) { results in
PhotoPicker.convertToUIImageArray(fromResults: results) { (imagesOrNil, errorOrNil) in
if let error = errorOrNil {
print(error)
}
if let images = imagesOrNil {
if let first = images.first {
print(first)
// image = first
imgSelected = first
}
}
}
}
.edgesIgnoringSafeArea(.all)
}
Spacer()
}
}
}
struct SecondVIew: View {
var body: some View {
PickAPhoto()
}
func getImage(){
// I want to get the image here
}
}
Move the imgSelected source of truth state up in the View hierarchy and pass a write-access binding down.
struct SecondVIew: View {
#State var imgSelected: UIImage = UIImage(named: "addCameraImg")!
var body: some View {
PickAPhoto(imgSelected: $imgSelected)
}
}
struct PickAPhoto: View {
#Binding var imgSelected: UIImage
I have a row, that when tapped, toggles the .fullScreenCover. This all works fine - but the issue is the cover only gets passed the data from the first item in the array.
Let's say I have a row that displays 4 items from an array. When I tap on for example the third item in the row, this toggles the .fullScreenCover - but it shows the first item of the array. In retrospect, this should show the item tapped (in this case the third item).
struct viewOne: View{
var allRecipes = [RecipeItem]
#State var showRecipeModal = false
var body : some View {
VStack {
self.generateRowView()
}
}
private func generateRowView -> some View {
return ZStack {
ForEach(allRecipes, id: \.self) { recipe in
self.item(image: recipe.recipeImage, title: recipe.recipeTitle)
}
}
}
func item(image: String, title: String) -> some View {
VStack{
WebImage(url: URL(string: image))
.resizable()
.frame (width: 110, height:130)
.cornerRadius(15)
HStack{
Text(title).bold()
.padding(.leading, 20)
Spacer()
}
.fullScreenCover(isPresented: $showRecipeModal){
RecipeControllerModal(name: title, image: image) // << only displays first item.
}
}
.onTapGesture {
showRecipeModal = true
}
}
}
//display view One
struct RecipeFullListView: View {
#ObservedObject var rm = RecipeLogic()
var body: some View {
ZStack{
VStack{
Text("Recipes")
ViewOne(allRecipes: rm.recipes) //<< provides recipes to view One
}
}
}
}
struct RecipeItem{
var recipeTitle, recipeImage: String
}
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 am trying to implement grid layout in collectionView. This is my current view
instead of 1 item per collection I would like to show 3 items
this is my productView
struct ProductSearchView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var model: SearchResultViewModel
var body: some View{
NavigationView {
List {
ForEach(0 ..< Global.productArry.count) { value in
Text(Global.productArry[value].name)
CollectionView(model: self.model, data: Global.productArry[value])
}
}.navigationBarTitle("Store")
}
}
}
and this is my collection view
struct CollectionView: View {
#ObservedObject var model: SearchResultViewModel
let data: Product
var body: some View {
VStack {
HStack {
Spacer()
AsyncImage(url: URL(string: self.data.productImageUrl)!, placeholder: Text("Loading ...")
).aspectRatio(contentMode: .fit)
Spacer()
}
VStack {
Spacer()
Text(self.data.name)
Spacer()
}
HStack {
Text("Aisle: \(self.data.location_zone)\(String(self.data.aisleNo))").bold()
Text("$\(String(self.data.productPrice))")
}
}.onAppear(perform:thisVal)
}
func thisVal (){
print(self.data.productImageUrl)
}
}
how can I implement a grid of three items ?
Use a LazyVGrid. The following example is very simple and easy to read and you can change it to anything you need.
struct ContentView: View {
let data = (1...100).map { "Item \($0)" }
let columns = [
GridItem(.adaptive(minimum: 80))
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: \.self) { _ in
Circle().scaledToFill()
}
}
.padding(.horizontal)
}
}
}
The code snippet below is what you need if you are using SwiftUI version 1 which came with Xcode 11. It is a custom implementation which simply use a combination of ScrollView, VStack and HStack as well as a little maths to calculate the frame size depending on the number of items.
import SwiftUI
struct GridView<Content, T>: View where Content: View {
// MARK: - Properties
var totalNumberOfColumns: Int
var numberRows: Int {
return (items.count - 1) / totalNumberOfColumns
}
var items: [T]
/// A parameter to store the content passed in the ViewBuilder.
let content: (_ calculatedWidth: CGFloat,_ type: T) -> Content
// MARK: - Init
init(columns: Int, items: [T], #ViewBuilder content: #escaping(_ calculatedWidth: CGFloat,_ type: T) -> Content) {
self.totalNumberOfColumns = columns
self.items = items
self.content = content
}
// MARK: - Helpers
/// A function which help checking if the item exist in the specified index to avoid index out of range error.
func elementFor(row: Int, column: Int) -> Int? {
let index:Int = row * self.totalNumberOfColumns + column
return index < items.count ? index : nil
}
// MARK: - Body
var body: some View {
GeometryReader { geometry in
ScrollView{
VStack {
ForEach(0...self.numberRows,id: \.self) { (row) in
HStack {
ForEach(0..<self.totalNumberOfColumns) { (column) in
Group {
if (self.elementFor(row: row, column: column) != nil) {
self.content(geometry.size.width / CGFloat(self.totalNumberOfColumns), self.items[self.elementFor(row: row, column: column)!])
.frame(width: geometry.size.width / CGFloat(self.totalNumberOfColumns), height: geometry.size.width / (CGFloat(self.totalNumberOfColumns)), alignment: .center)
}else {
Spacer()
}
}
}
}
}
}
}
}
}
}
Usage: It is generic ready to use with any view of your choice. For instance the Preview code below uses the system images.
struct GridView_Previews: PreviewProvider {
static var previews: some View {
GridView(columns: 3, items: ["doc.text.fill","paperclip.circle.fill", "globe","clear.fill","sun.min.fill", "cloud.rain.fill", "moon","power"], content: { gridWidth,item in
Image(systemName: item)
.frame(width: gridWidth, height: gridWidth, alignment: .center)
.border(Color.orange)
})
.padding(10)
}
}
Output
You can change the code to fit your needs for example paddings etc.
NOTE: Use this method only if you don't have lots of views as it is not very performant. If you have a lot of views to scroll I suggest you use the newer and simple build-in GridItem from Apple which was introduced this year at WWDC. But you will have to use Xcode 12 which currently is available as Beta version.
put for loop logic in HStack like givern below.
HStack {
ForEach(0..<3) { items in
Spacer()
AsyncImage(url: URL(string: self.data.productImageUrl)!, placeholder: Text("Loading ...")
).aspectRatio(contentMode: .fit)
Spacer()
}.padding(.bottom, 16)
}
I want to create a grid of square buttons that I can click/tap to toggle between black and white.
As a half-way point I am creating a row of buttons that do this - see code below.
But when I click on one of them all the buttons toggle together.
I can't see why this is because I have a state variable for each Cell?
struct ContentView: View {
var body: some View {
ZStack {
Color.green
.edgesIgnoringSafeArea(.all)
RowOfCellsView(string: "X.X.X.X")
}
}
}
struct Cell: Identifiable {
var id: Int
var state: Bool
}
struct RowOfCellsView: View {
var string: String
var cells: [Cell] {
string.map { Cell(id: 1, state: $0 == ".") }
}
var body: some View {
HStack {
ForEach(cells) { cell in
CellView(isBlack: cell.state, symbol: "Q")
}
}
}
}
struct CellView: View {
#State var isBlack: Bool
#State var symbol: String
var body: some View {
Button(action: { self.isBlack.toggle() }) {
Text("")
.font(.largeTitle)
.frame(width: 40, height: 40)
.aspectRatio(1, contentMode: .fill)
}
.background(isBlack ? Color.black : Color.white)
}
}
Aha - just noticed that I have the id set to 1 for all Cells - that's the problem!
So needs to have a way to have a different id for each Cell.
For example could do:
string.enumerated().map { Cell(id: $0.0, state: $0.1 == ".") }