How to set controllers in view in SwiftUI - swift

I'm having a problem with set Image and Text. I would like to have Image on top of the view, then have a break and put several Text controls one below the other, but everytime my Image is almost on the center of the View. Where is the problem? Below is my code:
import SwiftUI
struct ScanWithPhotoFromLibrary: View {
#State var cupWidth: String = ""
#State var cupHeight: String = ""
#State var stemLength: String = ""
#State var leafWidth: String = ""
#State var leafLength: String = ""
#State private var showSheet: Bool = false
#State private var showImagePicker: Bool = false
#State private var sourceType: UIImagePickerController.SourceType = .camera
#State private var userImage: UIImage?
#State private var flowerName: String = ""
#EnvironmentObject var env: ImagePickerCoordinator
var body: some View {
NavigationView{
ZStack{
VStack(){
GeometryReader { geo in
Image(uiImage: self.userImage ?? UIImage(named: "flower_logo")!)
.resizable()
.aspectRatio( contentMode:.fill)
.edgesIgnoringSafeArea(.top)
.frame(width: geo.size.width, height: 350)
}
Spacer()
Text("Enter dimensions in centimeters [cm]")
.bold()
.font(.system(size:22))
HStack(alignment: .top){
Text("Cup width: ")
.alignmentGuide(.leading, computeValue: { d in d[.trailing] })
.font(.system(size: 20))
TextField("0.00", text: $cupWidth)
.font(.system(size:20))
.keyboardType(.numberPad)
.foregroundColor(.green)
}.padding(.horizontal, 30)
}
}
}
.navigationBarTitle(Text(flowerName).foregroundColor(.blue), displayMode: .inline)
.navigationBarItems(trailing:
HStack {
Button("Library") {
self.showImagePicker = true
self.sourceType = .photoLibrary
print("Library tapped!")
}
}
)
.sheet(isPresented: $showImagePicker) {
ImagePicker(image: self.$userImage, isShown: self.$showImagePicker, flowerName: self.$flowerName, sourceType: self.sourceType)
}
}
}
struct ScanWithPhotoFromLibrary_Previews: PreviewProvider {
static var previews: some View {
ScanWithPhotoFromLibrary()
}
}
At the moment it looks like this:

The GeometryReader is consuming all available space. A resizable Image will try to fit in the available space by default, so if you set the height of its frame, GeometryReader is not necessary. You'll probably want to use content mode fit instead of fill as well. I also don't see why you would want to ignore the safe area edges here; this is a logo, not a background and it should probably not be obscured by a notch.
VStack {
Image(uiImage: self.userImage ?? UIImage(named: "flower_logo")!)
.resizable()
.aspectRatio(contentMode:.fit)
.frame(height: 350)
Spacer()
Text("Enter dimensions in centimeters [cm]")
.bold()
.font(.system(size:22))
...
}

Related

#State variable in onTapGesture not getting updated

with the code below I was expecting when the image in VStack was tapped, it shows another image in the full screen cover but the imageName variable does not seem to get set to jugg as in the new full screen it has only a gray background
struct TestView: View {
#State var imageName = ""
#State var showFullscreen = false
var body: some View {
VStack {
Image("drow")
.resizable()
.scaledToFit()
.frame(width: 100)
.onTapGesture {
self.imageName = "jugg"
self.showFullscreen = true
}
}
.fullScreenCover(isPresented: $showFullscreen) {
ZStack {
Color.gray.ignoresSafeArea()
Image(imageName)
.resizable()
.scaledToFit()
.frame(width: 380)
}
}
}
}
as mentioned in the comments, use the .fullScreenCover(item: ..) version
of the fullScreenCover, such as:
struct ImageName: Identifiable {
let id = UUID()
var name = ""
}
struct TestView: View {
#State var imageName: ImageName?
var body: some View {
VStack {
Image("drow").resizable().scaledToFit().frame(width: 100)
.onTapGesture {
imageName = ImageName(name: "drow")
}
}
.fullScreenCover(item: $imageName) { img in
ZStack {
Color.gray.ignoresSafeArea()
Image(img.name).resizable().scaledToFit().frame(width: 380)
}
}
}
}
Seems to only work if you create a separate SwiftUI view and pass in the imageName as a #Binding variable
struct TestView: View {
#State var imageName = ""
#State var showFullscreen = false
var body: some View {
VStack {
Image("drow")
.resizable()
.scaledToFit()
.frame(width: 100)
.onTapGesture {
imageName = "jugg"
showFullscreen = true
}
}
.fullScreenCover(isPresented: $showFullscreen) {
CoverView(imageName: $imageName)
}
}
}
struct CoverView: View {
#Binding var imageName: String
var body: some View {
ZStack {
Color.gray.ignoresSafeArea()
Image(imageName)
.resizable()
.scaledToFit()
.frame(width: 380)
}
}
}

SwiftUI each item displays it own detailView when it is selected in a list

I'm trying to make each item display it own detailView in a list using SwiftUI. But for now, I got stuck because it only display the same detailView for any item. Would anyone know how to do that?
This is the code I have for now:
struct PastryListView: View {
#State private var isShowingDetailView = false
#State private var selectedPastry : Pastry?
#State private var selection: Int? = nil
var body: some View {
ZStack {
NavigationView {
List(MockData.pastries) { Pastry in
HStack {
Image(Pastry.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 180, height: 200)
VStack {
Text(Pastry.name)
.font(Font.custom("DancingScript-Regular", size: 30))
.fontWeight(.medium)
}
.padding(.leading)
}
.onTapGesture {
selectedPastry = Pastry
isShowingDetailView = true
}
}
.navigationTitle("🥐 Pastries")
}
if isShowingDetailView { Pastry2DetailView(isShowingDetailView2: $isShowingDetailView, pastry: MockData.samplePastry2)
}
}
} }
there are many ways to achieve what you want, this is just one way:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
PastryListView()
}
}
}
struct Pastry: Identifiable {
var id: String = UUID().uuidString
var name: String
var image: UIImage
}
struct PastryListView: View {
#State private var pastries: [Pastry] = [
Pastry(name: "pastry1", image: UIImage(systemName: "globe")!),
Pastry(name: "pastry2", image: UIImage(systemName: "info")!)]
var body: some View {
NavigationView {
List(pastries) { pastry in
NavigationLink(destination: PastryDetailView(pastry: pastry)) {
HStack {
Image(uiImage: pastry.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 80, height: 80)
VStack {
Text(pastry.name)
.font(Font.custom("DancingScript-Regular", size: 30))
.fontWeight(.medium)
}
.padding(.leading)
}
}
}.navigationTitle("🥐 Pastries")
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct PastryDetailView: View {
#State var pastry: Pastry
var body: some View {
Text("🥐🥐🥐🥐 " + pastry.name + " 🥐🥐🥐🥐")
}
}

Get a specific id in a modal

I'm still learning on the job and my question may seem stupid.
I've got a list of movies and on the tap I want to show card of the selected movie.
So I've got my ResultsView
var results:[DiscoverResult]
#State private var resultsCount:Int = 0
#State private var isPresented:Bool = false
#EnvironmentObject private var genres:Genres
var body: some View {
ScrollView {
ForEach (results){ result in
Button(action: {
isPresented.toggle()
}, label: {
ZStack {
ZStack {
KFImage(URL (string: baseUrlForThumb + result.posterPath)).resizable().scaledToFill()
.frame( height: 150)
.mask(Rectangle().frame( height: 150))
Rectangle().foregroundColor(.clear) // Making rectangle transparent
.background(LinearGradient(gradient: Gradient(colors: [.clear, .clear, .black]), startPoint: .top, endPoint: .bottom))
}.frame( height: 150)
// Titre du film
VStack(alignment: .center) {
Spacer()
Text(result.title)
.fontWeight(.bold)
.foregroundColor(.white)
.multilineTextAlignment(.center)
// Genres du film
Text(genres.generateGenresList(genreIDS: result.genreIDS)).font(.caption).foregroundColor(.white).multilineTextAlignment(.center)
} .padding()
}.padding(.horizontal)
})
.sheet(isPresented: $isPresented, content: {
MovieView(isPresented: $isPresented, movieId: result.id)
})
.navigationTitle(result.title)
}
}
}
}
And my MovieView
import SwiftUI
struct MovieView: View {
#Binding var isPresented: Bool
var movieId:Int
var body: some View {
VStack {
Text(String(movieId))
.padding()
Button("Fermer") {
isPresented = false
}
}
}
}
But the movie card still the same even list element selected.
I think that the 'result.id' is overwrite at every loop but i don't know how to fix it.
Sorry for my english mistakes.
thank for your purpose.
Instead of using isPresented for .sheet you can use .sheet(item:, content:) and pass the whole result object
.sheet(item: $selecteditem( { result in
MovieView(item: result)
}
To make this work you need a new property (you can remove isPresented)
#State private var selectedItem: DiscoverResult?
and you need update your MovieView struct
struct MovieView: View {
let result: DiscoverResult
var body: some View {
//...
}
}
or pass only the movie id to your MovieView if you prefer that.

Why My second view cannot jump back to the root view properly

My App currently has two pages, first page has a circle plus button which could lead us to a second page. Basically, I have a save button which after clicking it, we could get back to the rood page. I followed this link for going back to root view. I tried the most up voted code, his code works perfectly. I reduced his code to two scene (basically the same scenario as mine), which also works perfectly. But then I don't know why my own code, pasted below, doesn't work. Basically my way of handling going back to root view is the same as the one in the link.
//
// ContentView.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/22/20.
//
import SwiftUI
import UIKit
#if canImport(UIKit)
extension View {
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
#endif
struct ContentView: View {
#EnvironmentObject private var fridge : Fridge
private var dbStartWith=0;
#State var pushed: Bool = false
#State private var selection = 1;
#State private var addFood = false;
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
NavigationLink(destination: AddFoodView(pushed: self.$pushed),isActive: self.$pushed) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
}.isDetailLink(false) )
}
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(1)
Text("random tab")
.font(.system(size: 30, weight: .bold, design: .rounded))
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("profile")
}
.tag(0)
}.environmentObject(fridge)
}
}
struct FoodView: View{
var body: some View{
NavigationView{
Text("food destination view ");
}
}
}
struct AddFoodView: View{
#Binding var pushed : Bool
#EnvironmentObject private var fridgeView : Fridge
#State private var name = ""
#State private var count : Int = 1
#State private var category : String = "肉类";
#State var showCategory = false
#State var showCount = false
var someNumberProxy: Binding<String> {
Binding<String>(
get: { String(format: "%d", Int(self.count)) },
set: {
if let value = NumberFormatter().number(from: $0) {
self.count = value.intValue;
}
}
)
}
var body: some View{
ZStack{
NavigationView{
VStack{
Button (action: {
self.pushed = false ;
//let tempFood=Food(id: fridgeView.index!,name: name, count: count, category: category);
//fridgeView.addFood(food: tempFood);
} ){
Text("save").foregroundColor(Color.blue).font(.system(size: 18,design: .default)) }
}.navigationBarTitle("Three")
}
ZStack{
if self.showCount{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCount=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $count,label: EmptyView()) {
ForEach(1..<100){ number in
Text("\(number)").tag("\(number)")
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
.offset(x:10,y:-10)
Spacer()
}
if self.showCategory{
let categoryArr = ["肉类","蔬菜类","饮料类","调味品类"]
ZStack{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCategory=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $category,label: EmptyView()) {
ForEach(0..<categoryArr.count){ number in
Text(categoryArr[number]).tag(categoryArr[number])
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
Spacer()
}.offset(x:10,y:20)
}
}
}.animation(.easeInOut)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you read my code carefully, there are some variables are missing referencing. That's because I pasted part of the code that relates to my issue.
Food Class
//
// Food.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/23/20.
//
import Foundation
class Food: Identifiable {
init(id:Int, name: String, count: Int, category: String){
self.id=id;
self.name=name;
self.count=count;
self.category=category;
}
var id: Int
var name: String
var count: Int
var category: String
}
Fridge class
//
// Fridge.swift
// refridgerator_app
//
// Created by Mingtao Sun on 12/27/20.
//
import Foundation
class Fridge: ObservableObject{
init(){
db=DBhelper();
let result = setIndex(database: db!);
self.index = result.1;
self.container=result.0;
}
var db:DBhelper?
var index : Int?
#Published var container : [Food]?;
func setIndex(database: DBhelper) -> ([Food],Int){
let foodList : [Food] = database.read();
var index=0;
for food in foodList{
index = max(food.id,index);
}
return (foodList,(index+1));
}
func updateindex(index: inout Int){
index=index+1;
}
func testExist(){
if let data = db {
print("hi")
}
else{
print("doesnt exist")
}
}
func addFood(food:Food){
self.db!.insert(id: self.index!, name: food.name, count:food.count, category: food.category);
self.container!.append(food);
}
}
Because you implemented a new NaviagtionView in AddFoodView. Simply remove this and it should work. Look at the link you provided. There is no NavigationView in the child.
Correct me if Im wrong but the core code parts here that produce this issue are as follows:
Here you start:
struct ContentView: View {
#State var pushed: Bool = false
// Deleted other vars
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
// Here you navigate to the child view
NavigationLink(destination: AddFoodView(pushed: self.$pushed),isActive: self.$pushed) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
}.isDetailLink(false) )
}
Here you land and want to go back to root:
struct AddFoodView: View{
#Binding var pushed : Bool
// Deleted the other vars for better view
var body: some View{
ZStack{
NavigationView{ // <-- remove this
VStack{
Button (action: {
// here you'd like to go back
self.pushed = false;
} ){
Text("save").foregroundColor(Color.blue).font(.system(size: 18,design: .default)) }
}.navigationBarTitle("Three")
}
For the future:
I have the feeling you might have troubles with the navigation in general.
Actually it is really simple:
You implement one NavigationView at the "root" / start of your navigation.
From there on you only use NavigationLinks to go further down to child pages. No NavigationView needed anymore.

SwiftUI passing a saved value to parent view

I am fairly new to SwiftUI I am trying to figure out the best way to pass data from a child view to parent?
Thanks for the help I come from a Javascript (React) background so this is a little different for me
The way my child view works is the user clicks on an image to select that image.
I have #State binding that saves the imgUrl which is a String referring to name in Assets.
I am just not sure about the best way to pass that value to the parent component.
Here is the child view (imageSelector)
struct ImageSelector: View {
#State private var windowImgs = ["1", "2", "3","4","5","6","7","8","9","10","11","12","13", "14","15","16","17","18"]
#State private var imgPicked = ""
var body: some View{
ScrollView(Axis.Set.horizontal, showsIndicators: true){
HStack{
ForEach(0..<18){num in
Button(action:{
self.imgPicked = self.windowImgs[num]
print(self.imgPicked)
}){
Image("\(self.windowImgs[num])")
.renderingMode(.original)
.resizable()
.cornerRadius(4)
.frame(width: 100, height: 100)
}
}
}
}
}
}
Here is the parent view (AddCounterForm)
struct AddCounterForm: View {
#Environment(\.presentationMode) var presentationMode
#State private var pickedImg: String = "defaultImg"
#State private var price: String = "0.0"
#State private var qty: String = "0"
var body: some View {
VStack (spacing: 40){
HStack {
Button("Cancel"){
self.presentationMode.wrappedValue.dismiss()
}
.foregroundColor(.red)
Spacer()
Button("Save"){
}
}
HStack {
VStack (spacing: 20){
TextField("Window type", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
TextField("Window location", text: /*#START_MENU_TOKEN#*//*#PLACEHOLDER=Value#*/.constant("")/*#END_MENU_TOKEN#*/)
}
.textFieldStyle(RoundedBorderTextFieldStyle())
Image(pickedImg)
.resizable()
.cornerRadius(4)
.frame(width: 90, height: 90)
.padding(.leading)
}
HStack {
Text("Price")
TextField("", text:$price)
.frame(width: 70)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
Spacer()
Text("Qty")
TextField("", text:$qty)
.frame(width: 70)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
}
VStack {
Text("Select an image")
.foregroundColor(.blue)
ImageSelector()
.padding(.bottom)
Button("Use your own image"){
//method
}
.frame(width: 180, height: 40)
.background(Color.blue)
.clipShape(Capsule())
.foregroundColor(.white)
.padding(.top)
}
}
.padding()
}
}
Solution for preview thanks for the help from #Asperi & #neverwinterMoon
struct ImageSelector_Previews: PreviewProvider {
static var previews: some View {
PreviewWrapper()
}
}
struct PreviewWrapper: View {
#State(initialValue: "") var imgPicked: String
var body: some View {
ImageSelector(imgPicked: $imgPicked)
}
}
In this case Binding is most appropriate
struct ImageSelector: View {
#Binding var imgPicked: String
and use
ImageSelector(imgPicked: $pickedImg)
.padding(.bottom)