#State variable in onTapGesture not getting updated - swift

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)
}
}
}

Related

How to pass UIImage between to 2 Views in SwifUI

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

SwiftUI - Display selected value with Single Selected

I want when I finish selecting the language and click the Save button it will return the ContentView page and display the language I have selected. And when I click again, it has to checkmark the language I selected before.
I have successfully displayed the data, but I don't know how to save it when I click the Save button
Here is all my code currently
ContentView
struct ContentView: View {
var body: some View {
NavigationView {
HStack {
NavigationLink(destination:LanguageView() ) {
Text("Language")
Spacer()
Text("I want to show the language here ")
}
}
}
}
}
LanguageView
struct LanguageView: View {
var body: some View {
VStack {
CustomLanguageView()
Button(action: {
})
{
Text("Save")
.foregroundColor(.black)
}
.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#State var selectedLanguage: String? = nil
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage)
.padding(.trailing,40)
Rectangle().fill(Color.gray)
.frame( height: 1,alignment: .bottom)
}
.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark")
.resizable()
.frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
There are multiple ways to "Save" something but if you are just trying to get it back to the other view you could do something like this that I quickly setup.
struct ContentView: View {
#State var language: String? = ""
var body: some View {
NavigationView {
HStack {
NavigationLink(destination:LanguageView(language: $language)) {
Text("Language")
.padding()
Spacer()
Text(language!)
.padding()
}
}
}
}
}
struct LanguageView: View {
#Binding var language: String?
#State var selectedLanguage: String? = ""
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $selectedLanguage)
Button(action: {
language = selectedLanguage
})
{
Text("Save")
.foregroundColor(.black)
}
.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#Binding var selectedLanguage: String?
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage)
.padding(.trailing,40)
Rectangle().fill(Color.gray)
.frame( height: 1,alignment: .bottom)
}
.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark")
.resizable()
.frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
Or if you are actually trying to save it to the device for later use you could use
UserDefaults.standard.setValue(selectedLanguage, forKey: "language")
Then to Retrieve it later do
UserDefaults.standard.value(forKey: "language") as! String

How to setup NavigationLink in SwiftUI sheet to redirect to new view

I am attempting to build a multifaceted openweathermap app. My app is designed to prompt the user to input a city name on a WelcomeView, in order to get weather data for that city. After clicking search, the user is redirected to a sheet with destination: DetailView, which displays weather details about that requested city. My goal is to disable dismissal of the sheet in WelcomeView and instead add a navigationlink to the sheet that redirects to the ContentView. The ContentView in turn is set up to display a list of the user's recent searches (also in the form of navigation links).
My issues are the following:
The navigationLink in the WelcomeView sheet does not work. It appears to be disabled. How can I configure the navigationLink to segue to destination: ContentView() ?
After clicking the navigationLink and redirecting to ContentView, I want to ensure that the city name entered in the WelcomeView textfield is rendered as a list item in the ContentView. For that to work, would it be necessary to set up an action in NavigationLink to call viewModel.fetchWeather(for: cityName)?
Here is my code:
WelcomeView
struct WelcomeView: View {
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = ""
#State private var showingDetail: Bool = false
#State private var linkActive: Bool = true
#State private var acceptedTerms = false
var body: some View {
Section {
HStack {
TextField("Search Weather by City", text: $cityName)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10.0).strokeBorder(Color.gray, style: StrokeStyle(lineWidth: 1.0)))
.padding()
Spacer()
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}
.sheet(isPresented: $showingDetail) {
VStack {
NavigationLink(destination: ContentView()){
Text("Return to Search")
}
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}.interactiveDismissDisabled(!acceptedTerms)
}
}
}.padding()
}
}
}
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
WelcomeView()
}
}
ContentView
let coloredToolbarAppearance = UIToolbarAppearance()
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = ""
#State var showingDetail = false
init() {
// toolbar attributes
coloredToolbarAppearance.configureWithOpaqueBackground()
coloredToolbarAppearance.backgroundColor = .systemGray5
UIToolbar.appearance().standardAppearance = coloredToolbarAppearance
UIToolbar.appearance().scrollEdgeAppearance = coloredToolbarAppearance
}
var body: some View {
NavigationView {
VStack() {
List () {
ForEach(viewModel.cityNameList) { city in
NavigationLink(destination: DetailView(detail: city)) {
HStack {
Text(city.name).font(.system(size: 32))
Spacer()
Text("\(city.main.temp, specifier: "%.0f")°").font(.system(size: 32))
}
}
}.onDelete { index in
self.viewModel.cityNameList.remove(atOffsets: index)
}
}.onAppear() {
viewModel.fetchWeather(for: cityName)
}
}.navigationTitle("Weather")
.toolbar {
ToolbarItem(placement: .bottomBar) {
HStack {
TextField("Enter City Name", text: $cityName)
.frame(minWidth: 100, idealWidth: 150, maxWidth: 240, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
Spacer()
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}.sheet(isPresented: $showingDetail) {
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DetailView
struct DetailView: View {
var detail: WeatherModel
var body: some View {
VStack(spacing: 20) {
Text(detail.name)
.font(.system(size: 32))
Text("\(detail.main.temp, specifier: "%.0f")°")
.font(.system(size: 44))
Text(detail.firstWeatherInfo())
.font(.system(size: 24))
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(detail: WeatherModel.init())
}
}
ViewModel
class WeatherViewModel: ObservableObject {
#Published var cityNameList = [WeatherModel]()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=<MyAPIKey>") else { return }
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.cityNameList.append(model)
}
}
catch {
print(error) // <-- you HAVE TO deal with errors here
}
}
task.resume()
}
}
Model
struct WeatherModel: Identifiable, Codable {
let id = UUID()
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Double = 0.0
}
struct WeatherInfo: Codable {
var description: String = ""
}
DemoApp
#main
struct SwftUIMVVMWeatherDemoApp: App {
var body: some Scene {
WindowGroup {
// ContentView()
WelcomeView()
}
}
}

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 + " 🥐🥐🥐🥐")
}
}

How to set controllers in view in SwiftUI

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))
...
}