SwiftUI .sheet issues - swift

I have been working on an app recently, and in the App, I use an ImagePicker in order to post images. I have my TabBarView below, and I have a button that should present a sheet with the ImagePicker, but the sheet is not being presented. Here, I just replaced the image picker with some text in order to ensure the issue isn't with the ImagePicker. I know the problem is probably trivial, but I can't seem to figure it out. Any help would be appreciated!
I took out a bunch of the TabBarView out here, so if you need more of the code, just let me know.
struct TabBarView: View {
#State var pickAnImage: Bool = false
#State var showImagePicker: Bool = false
#State var showCameraPicker: Bool = false
#State var image: UIImage?
#ObservedObject var viewRouter = ViewRouter()
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0){
Spacer()
if self.viewRouter.currentView == "home" {
HomeView2(dataHandler: dataHandler)
} else if self.viewRouter.currentView == "profile" {
ProfileView()
}
else if self.viewRouter.currentView == "new-entry" {
NewEntryView(image: self.image)
}else if self.viewRouter.currentView == "explore" {
ExploreView()
}else if self.viewRouter.currentView == "settings" {
SettingsView()
}
Spacer()
ZStack {
Circle()
.foregroundColor(yellow)
.frame(width: 70, height: 70)
Image(systemName: "photo")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(20)
.frame(width: 70, height: 70)
.foregroundColor(.white)
}.onTapGesture {
self.viewRouter.currentView = "home"
self.showImagePicker.toggle()
self.pickAnImage.toggle()
self.showPopUp = false
}
}
}
}
.sheet(isPresented: self.$pickAnImage, content: {
VStack{
Text("Hello")
Text("World!!")
}
})
}
}
}

Related

How to transition to a new view in swiftui

My goal is to have the user click a button that then gives them a choice of yes or cancel, this works fine. Is yes is selected it should move to the Camera View. I am getting the error: Result of 'NavigationLink<Label, Destination>' initializer is unused.
struct ContentView: View {
#State private var showAlert = false
var body: some View {
NavigationStack
{
VStack {
Button{
showAlert = true
} label: {
Text("+")
}
.frame(width: 40, height: 40)
.symbolVariant(.fill)
.background(.red)
.cornerRadius(15)
.foregroundColor(.white)
.padding(.trailing,300)
Spacer()
}
.alert("Create Event Here?", isPresented: $showAlert) {
Button("Yes"){NavigationLink("addCameraView", destination: CameraView())//*****gets the error
}
Button("Cancel", role: .cancel) { }
}
}
struct CameraView: View{
#State private var sourceType: UIImagePickerController.SourceType = .photoLibrary
#State private var selectedImage: UIImage?
#State private var imagePickerDisplay = false
var body: some View {
NavigationView {
VStack {
if selectedImage != nil {
Image(uiImage: selectedImage!)
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
.frame(width: 300, height: 300)
} else {
Image(systemName: "snow")
.resizable()
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
.frame(width: 300, height: 300)
}
Button("Camera") {
self.sourceType = .camera
self.imagePickerDisplay.toggle()
}.padding()
}
.navigationBarTitle("Take a Photo of the Event")
.sheet(isPresented: self.$imagePickerDisplay) {
ImagePickerView(selectedImage: self.$selectedImage, sourceType: self.sourceType)
}
}
}
}
}
To navigate with a button, we need to utilize a variable as our trigger. Wrapping the button in a NavigationLink and updating the associated variable to the appropriate value will trigger the navigation.
Below you will find the updated ContentView. CameraView remains unchanged.
import SwiftUI
struct ContentView: View {
#State private var showAlert = false
#State var selection: Int? = nil
var body: some View {
NavigationStack
{
VStack {
Button{
showAlert = true
} label: {
Text("+")
}
.frame(width: 40, height: 40)
.symbolVariant(.fill)
.background(.red)
.cornerRadius(15)
.foregroundColor(.white)
.padding(.trailing,300)
Spacer()
}
.alert("Create Event Here?", isPresented: $showAlert) {
NavigationLink(destination: CameraView(), tag: 1, selection: $selection) {
Button("Yes"){
selection = 1
}
}
Button("Cancel", role: .cancel) {
selection = nil
}
}
}
}
}

ScrollView stops components from expanding

I would like to have my cards expandable and fill the while area of the screen while they are doing the change form height 50 to the whole screen (and don't display the other components)
Here is my code:
import SwiftUI
struct DisciplineView: View {
var body: some View {
ScrollView(showsIndicators: false) {
LazyVStack {
Card(cardTitle: "Notes")
Card(cardTitle: "Planner")
Card(cardTitle: "Homeworks / Exams")
}
.ignoresSafeArea()
}
}
}
struct DisciplineV_Previews: PreviewProvider {
static var previews: some View {
DisciplineView()
}
}
import SwiftUI
struct Card: View {
#State var cardTitle = ""
#State private var isTapped = false
var body: some View {
RoundedRectangle(cornerRadius: 30, style: .continuous)
.stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
.foregroundColor(.gray.opacity(0.2))
.frame(width: .infinity, height: isTapped ? .infinity : 50)
.background(
VStack {
cardInfo
if(isTapped) { Spacer() }
}
.padding(isTapped ? 10 : 0)
)
}
var cardInfo: some View {
HStack {
Text(cardTitle)
.font(.title).bold()
.foregroundColor(isTapped ? .white : .black)
.padding(.leading, 10)
Spacer()
Image(systemName: isTapped ? "arrowtriangle.up.square.fill" : "arrowtriangle.down.square.fill")
.padding(.trailing, 10)
.onTapGesture {
withAnimation {
isTapped.toggle()
}
}
}
}
}
struct Card_Previews: PreviewProvider {
static var previews: some View {
Card()
}
}
here is almost the same as I would like to have, but I would like the first one to be on the whole screen and stop the ScrollView while appearing.
Thank you!
Described above:
I would like to have my cards expandable and fill the while area of the screen while they are doing the change form height 50 to the whole screen (and don't display the other components)
I think this is pretty much what you are trying to achieve.
Basically, you have to scroll to the position of the recently presented view and disable the scroll. The scroll have to be disabled enough time to avoid continuing to the next item but at the same time, it have to be enabled soon enough to give the user the feeling that it is scrolling one item at once.
struct ContentView: View {
#State private var canScroll = true
#State private var itemInScreen = -1
var body: some View {
GeometryReader { geo in
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(0...10, id: \.self) { item in
Text("\(item)")
.onAppear {
withAnimation {
proxy.scrollTo(item)
canScroll = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
canScroll = true
}
}
}
}
.frame(width: geo.size.width, height: geo.size.height)
.background(Color.blue)
}
}
}
.disabled(!canScroll)
}
.ignoresSafeArea()
}
}

SwiftUI For Each Button in list - not working... sometimes

I have made a custom selection button view in SwiftUI for an app that is being developed. I cant for the life of me work out why sometimes the buttons don't do anything - It is always the last x number of buttons that don't work (which made me think it was related to the 10 view limitation of swift ui however, I've been told this isn't an issue when using a for each loop).
Sometimes it works as expected and others it cuts off the last x number of buttons. Although when it is cutting off buttons it is consistent between different simulators and physical devices. Can anybody see anything wrong here?
I am new to SwiftUI and so could be something simple...
#EnvironmentObject var QuestionManager: questionManager
var listItems: [String]
#State var selectedItem: String = ""
var body: some View {
GeometryReader {geom in
ScrollView{
VStack{
ForEach(Array(listItems.enumerated()), id: \.offset){ item in
Button(action: {
if (selectedItem != item.element) {
selectedItem = item.element
} else {
selectedItem = ""
QuestionManager.tmpAnswer = ""
}
}, label: {
GeometryReader { g in
Text("\(item.element)")
.font(.system(size: g.size.width/22))
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.black)
.lineLimit(2)
.frame(width: g.size.width, height: g.size.height)
.minimumScaleFactor(0.5)
.background(
Rectangle()
.fill((item.element == selectedItem) ? Color(.green) : .white)
.frame(width: g.size.width, height: g.size.height)
.border(Color.gray)
).scaledToFit()
}
.frame(width: geom.size.width*0.92, height: 45)
}).disabled((Int(QuestionManager.answers.year) == Calendar.current.component(.year, from: Date())) ? validateMonth(month: item.offset) : false)
}
}
.frame(width: geom.size.width)
}
}
}
} ```
As #Yrb mentioned, using enumerated() is not a great option in a ForEach loop.
Your issue could be compounded by listItems having duplicate elements.
You may want to restructure your code, something like this approach using a dedicated
item structure, works very well in my tests:
struct MyItem: Identifiable, Equatable {
let id = UUID()
var name = ""
init(_ str: String) {
self.name = str
}
static func == (lhs: MyItem, rhs: MyItem) -> Bool {
lhs.id == rhs.id
}
}
struct ContentView: View {
#EnvironmentObject var QuestionManager: questionManager
// for testing
var listItems: [MyItem] = [MyItem("1"),MyItem("2"),MyItem("3"),MyItem("4"),MyItem("6"),MyItem("7"),MyItem("8"),MyItem("9")]
#State var selectedItem: MyItem? = nil
var body: some View {
GeometryReader {geom in
ScrollView{
VStack{
ForEach(listItems){ item in
Button(action: {
if (selectedItem != item) {
selectedItem = item
} else {
selectedItem = nil
QuestionManager.tmpAnswer = ""
}
}, label: {
GeometryReader { g in
Text(item.name)
.font(.system(size: g.size.width/22))
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.black)
.lineLimit(2)
.frame(width: g.size.width, height: g.size.height)
.minimumScaleFactor(0.5)
.background(
Rectangle()
.fill((item == selectedItem) ? Color(.green) : .white)
.frame(width: g.size.width, height: g.size.height)
.border(Color.gray)
).scaledToFit()
}
.frame(width: geom.size.width*0.92, height: 45)
})
.disabled((Int(QuestionManager.answers.year) == Calendar.current.component(.year, from: Date())) ? validateMonth(item: item) : false)
}
}
.frame(width: geom.size.width)
}
}
}
func validateMonth(item: MyItem) -> Bool {
if let itemOffset = listItems.firstIndex(where: {$0.id == item.id}) {
// ... do your validation
return true
}
return false
}
}

SwiftUI - How to close a fake Modal View - from the second view inside it, with a close button?

I have a tricky design. I'm trying to have a close button that from whatever views, inside a "fake" Modal View (fake cause it's full screen, thanks to a code that i found online), it's gonna close the Modal View.
Right now the close button is working in the first view that open in the modal view, but I need it to work also in the next views of the modal view, cause I have a flow inside this modal view to create an item.
This is the View from which I start ColorNewItemView, my fake Modal View.
struct RecapItemToAdd: View {
#State var isPresented: Bool = false
var body: some View {
NavigationView {
VStack {
Button(action: { withAnimation { self.isPresented.toggle()}}) {
Image(systemName: "pencil")
.resizable()
.renderingMode(.original)
.frame(width: 13, height: 17)
.foregroundColor(UIManager.hLightGrey)
}
}
ZStack {
VStack(alignment: .leading) {
ColorNewItemView(isPresenteded: self.$isPresented)
Spacer()
}
}
.background(Color.white)
.edgesIgnoringSafeArea(.all)
.offset(x: 0, y: self.isPresented ? 0 : UIApplication.shared.keyWindow?.frame.height ?? 0)
}
}
}
Note: I know that "keyWindow" is deprecated but I don't know how to change it.
With ColorNewItemView starts my full screen Modal View. In this view the close button works.
struct ColorNewItemView: View {
#State var selection: Int? = nil
#Binding var isPresenteded: Bool
var body: some View {
NavigationStackView {
VStack(alignment: .center) {
Button(action: {
self.isPresenteded = false
}) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(UIManager.hBlueLight)
}
Text("First View")
.font(UIManager.einaTitle)
.foregroundColor(UIManager.hDarkBlue)
Image("black-hoodie")
.resizable()
.renderingMode(.original)
.frame(width: 245, height: 300)
PushView(destination: Color2NewItemView(isPresenteded: self.$isPresenteded), tag: 1, selection: $selection) {
Button(action: {self.selection = 1}) {
Text("Avanti")
.font(UIManager.einaButton)
.foregroundColor(.white)
.frame(width: 291, height: 43)
.background(UIManager.buttonGradient)
.cornerRadius(6)
.shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
}
}
}
}
}
}
Now I have the next view inside the Modal view, where the close button starts to stop working.
struct Color2NewItemView: View {
#Binding var isPresenteded: Bool
#State var selection: Int? = nil
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.isPresenteded = false
}) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(UIManager.hBlueLight)
}
Text("Second View")
.font(UIManager.einaTitle)
.foregroundColor(UIManager.hDarkBlue)
Image("black-hoodie")
.resizable()
.renderingMode(.original)
.frame(width: 245, height: 300)
PushView(destination: FabricNewItemView(isPresenteded: $isPresenteded), tag: 1, selection: $selection) {
Button(action: {self.selection = 1}) {
Text("Tessuto")
.font(UIManager.einaButton)
.foregroundColor(.white)
.frame(width: 291, height: 43)
.background(UIManager.buttonGradient)
.cornerRadius(6)
.shadow(color: UIManager.hBlueShadow, radius: 7, x: 0.0, y: 6.0)
}
}
Spacer()
.frame(height: 18)
PopView{
Text("Back")
.font(UIManager.einaBodySemibold)
.foregroundColor(UIManager.hGrey)
}
}
}
}
Ps.
I had also to use a library called NavigationStack, since I have a custom back button on the bottom of the page, and the Navigation View doesn't let me pop back without using the back in the navigation bar.
Binding can be lost on deep view hierarchy, so it is more appropriate to operate with it on the level it was received.
Here is possible approach using EnvironmentKey (by same idea as presentationMode works)
Introduce helper environment key which holds some closure
struct DismissModalKey: EnvironmentKey {
typealias Value = () -> ()
static let defaultValue = { }
}
extension EnvironmentValues {
var dismissModal: DismissModalKey.Value {
get {
return self[DismissModalKey.self]
}
set {
self[DismissModalKey.self] = newValue
}
}
}
so in your top modal view you can inject into hierarchy callback to dismiss
struct ColorNewItemView: View {
#State var selection: Int? = nil
#Binding var isPresented: Bool
var body: some View {
NavigationStackView {
// ... other code
}
.environment(\.dismissModal, { self.isPresented = false} ) // << here !!
}
}
thus this environment value now is available for all subviews, and you can use it as
struct Color2NewItemView: View {
#Environment(\.dismissModal) var dismissModal
#State var selection: Int? = nil
var body: some View {
VStack(alignment: .center) {
Button(action: {
self.dismissModal() // << here !!
}) {
// ... other code
}
}

Why is my view not transitioning when first appeared on screen in swiftUI

I want a simple view to scale from 0 to 1 when the app first loads. How ever its not happening.Look at my code here:
struct ContentView: View {
#State private var isLoadng = false
var body: some View {
ZStack {
if isLoadng {
Color.red
.cornerRadius(20)
.frame(width: 150, height: 200)
.transition(.scale)
}
}
.onAppear {
withAnimation(.easeInOut(duration: 2)) {
self.isLoadng = true
}
}
}
}
The view just popping up without any transition
Here is modified part. Tested with Xcode 12
var body: some View {
ZStack {
if isLoadng {
Color.red
.cornerRadius(20)
.frame(width: 150, height: 200)
.transition(.scale)
}
}
.animation(.easeInOut(duration: 2))
.onAppear {
self.isLoadng = true
}
}