How to pass UIImage between to 2 Views in SwifUI - swift

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

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

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

onTapGesture fails whole appliaction

I am making a little game with some youtube tutorials, I made the design but whenever I try to add onTapGesture my xCode returns an error Cannot Preview This File, and the simulator launches but it's just all white
struct ContentView: View {
var body: some View {
HStack{
CardView()
CardView()
CardView()
CardView()
}
.foregroundColor(.orange)
.padding(.horizontal)
}
}
struct CardView:View {
#State var isFaceUp:Bool = false
var body: some View {
ZStack{
let shape = RoundedRectangle(cornerRadius: 20)
if isFaceUp {
shape.stroke(lineWidth: 3)
Text("🧞‍♂️").font(.largeTitle)
} else{
shape.fill()
}
}
onTapGesture {
isFaceUp = !isFaceUp
}
}
}
Correct the syntax and it'll fix the crash:
struct CardView:View {
#State var isFaceUp:Bool = false
var body: some View {
ZStack{
let shape = RoundedRectangle(cornerRadius: 20)
if isFaceUp {
shape.stroke(lineWidth: 3)
Text("🧞‍♂️").font(.largeTitle)
} else{
shape.fill()
}
}.onTapGesture {
isFaceUp = !isFaceUp
}
}
}

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

SwiftUI List exposed white background in landscape orientation

I have a viewController that allows the user to select their choice of notification sounds. The list is presented by the SwiftUI with the name of each sound and a preview button. All is fine in portrait mode:
But in landscape the edges of the screen show white:
Here's my SwiftUI code:
import SwiftUI
struct SoundItem: Hashable {
var name: String
var pdSelection: Bool = false
var pESelection: Bool = false
var advanceAlertSelection: Bool = false
}
struct SoundListItem: View {
var item: SoundItem
var delegate: NotificationsSoundControllerViewController?
var body: some View {
HStack {
Image(item.pdSelection ? "post-dose-select" : "post-dose-unselect")
.onTapGesture {
if let d = self.delegate {
d.selectPDSound(name: self.item.name) // defined in the protocol of the preferences view controller for updating the user prefs.
}
}
Spacer()
Text(item.name.localizedCapitalized).font(Font.custom("Exo2-SemiBold", size: 20.0)).foregroundColor(item.advanceAlertSelection ? Color.green : Color.white)
.onTapGesture {
if let d = self.delegate {
d.selectAdvanceSound(name: self.item.name) // defined in the protocol of the preferences view controller for updating the user prefs.
}
}
Spacer()
Image(systemName: "play.fill")
.foregroundColor(Color.white)
.onTapGesture {
if let d = self.delegate {
d.playPreviewSound(name: self.item.name)
}
}.padding(.trailing, 40.0)
Image(item.preExpirySelection ? "pre-expiry-select" : "pre-expiry-unselect")
.onTapGesture {
if let d = self.delegate {
d.selectPESound(name: self.item.name) // defined in the protocol of the preferences view controller for updating the user prefs.
}
}
}
}
}
struct ListHeading: View {
let headingFont: Font = Font.custom("Exo2-SemiBold", size: 12.0)
var body: some View {
HStack{
Text("Post").font(headingFont).foregroundColor(Color.gray)
Spacer()
Text("Click name for advance alert").font(headingFont).foregroundColor(Color.gray)
Spacer()
Spacer()
Text("Pre").font(headingFont).foregroundColor(Color.gray)
}
}
}
struct NotificationSoundsSUIView: View {
#ObservedObject var noteController: NotificationsSoundControllerViewController
init(){
UITableView.appearance().backgroundColor = .black
noteController = NotificationsSoundControllerViewController()
}
var body: some View {
Section(header: ListHeading()) {
List {
ForEach (self.noteController.sounds, id: \.self) { sound in
SoundListItem(item: sound, delegate: self.noteController).listRowBackground(Color.black)
}.background(Color.black)
}.listRowBackground(Color.black)
}
}
}
struct NotificationSoundsSUIView_Previews: PreviewProvider {
static var previews: some View {
NotificationSoundsSUIView()
}
}
And it's presented in the viewController with this:
var listView = NotificationSoundsSUIView() // See above code
listView.noteController = self //pass over data for display by referencing this viewController
let childView = UIHostingController(rootView: listView)
childView.view.backgroundColor = UIColor.black
childView.view.frame = view.frame
view.addSubview(childView.view)
view.sendSubviewToBack(childView.view)
view.backgroundColor = UIColor.black
childView.view.translatesAutoresizingMaskIntoConstraints = false
How can I force the entire background of the screen to remain black?
Assuming your constraints in UIViewController set correctly here is to be done in SwiftUI part
Section(header: ListHeading()) {
List {
ForEach (self.noteController.sounds, id: \.self) { sound in
SoundListItem(item: sound, delegate: self.noteController).listRowBackground(Color.black)
}.background(Color.black)
}.listRowBackground(Color.black)
}.edgesIgnoringSafeArea([.leading, .trailing]) // << here !!
I stumbled on an answer that seems to solve the problem and also maintain the safe area clearence of the list. I changed the SwiftUI view's body to:
var body: some View {
HStack {
Spacer() // < Inserted a spacer
VStack { // < Using a HStack causes the list headings to appear to the left of the list itself
Section(header: ListHeading()) {
List {
ForEach (self.noteController.sounds, id: \.self) { sound in
SoundListItem(item: sound, delegate: self.noteController).listRowBackground(Color.black)
}.background(Color.black)
}.listRowBackground(Color.black)
}
}
Spacer() // < Inserted a spacer
}
}
}