Move From SpriteKit scene to SwiftUI View - swift

I'm trying to figure out the proper way to move back to a SwiftUI view from a SpriteKit Scene. I currently have a Swift UI "main menu" which looks like this.
struct MainMenu: View {
var body: some View {
NavigationView {
VStack {
Text("Replicator")
.font(.largeTitle)
.fontWeight(.bold)
.padding()
NavigationLink(destination: ContentView().navigationBarBackButtonHidden(true)) {
HStack {
Image(systemName: "play.circle")
.font(.title3)
Text("Start")
.font(.title)
}
.frame(width: 250, height: 50)
.background(.cyan)
.cornerRadius(25)
.foregroundColor(.white)
}
}
}
}
}
The ContentView() is what contains the SpriteKit game and that looks like the following.
struct ContentView: View {
var scene: SKScene {
let scene = Level_1()
scene.size = CGSize(width: 750, height: 1334)
scene.scaleMode = .aspectFit
return scene
}
var body: some View {
VStack {
SpriteView(scene: scene)
.ignoresSafeArea()
}
}
}
My question is... once I'm in ContentView how do I return to "Main Menu"?
Thanks for any help you can provide.

You can use
#Environment(.presentationMode) var presentationMode
So you can create a button and call
presentationMode.wrappedValue.dismiss()
Or you can pass a binding var to Content View and set to false like so:
In MainMenu
struct MainMenu: View {
#State var isPresented = false
var body: some View {
NavigationView {
VStack {
Text("Replicator")
.font(.largeTitle)
.fontWeight(.bold)
.padding()
NavigationLink(destination: ContentView(isPresented: $isPresented).navigationBarBackButtonHidden(true), isActive: $isPresented) {
HStack {
Image(systemName: "play.circle")
.font(.title3)
Text("Start")
.font(.title)
}
.frame(width: 250, height: 50)
.background(.cyan)
.cornerRadius(25)
.foregroundColor(.white)
}
}
}
}
}
In ContentView:
struct ContentView: View {
#Binding var isPresented: Bool
var scene: SKScene {
let scene = Level_1()
scene.size = CGSize(width: 750, height: 1334)
scene.scaleMode = .aspectFit
return scene
}
var body: some View {
ZStack {
SpriteView(scene: scene)
.ignoresSafeArea()
Button(action: { //You can put the button wherever you want as long as you pass in the isPresented Binding
isPresented.toggle()
}) {
Text("Back to MainMenu")
}
}
}
}

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

SwiftUI - Sheet Dismmis button not working

I want to add a button on the top of my view that will be the "X" to close the sheet. I am unable to use presentationmode option. For some reason it isn't working. Can someone please let me know how I can add a button that shows an "X" on the top that if I click on will close this view. Your help is very much appreciated.
import SwiftUI
struct TopicsExperienceCards: View {
// #Environment(\.presentationMode) var presentationMode
let etype: EItype
var body: some View {
NavigationView{
/* Alernatively Page Layout View */
ScrollView (.vertical, showsIndicators: false) {
Rectangle()
.fill(Color(etype.accentcolor))
.frame(width: 300, height: 5)
.padding()
GroupBox {
TabView {
ForEach(etype.content1,id: \.self) {item in
VStack (alignment:.center, spacing:0){
Text(item)
.padding()
.frame(width:300, height:300, alignment:.center)
Divider()
Spacer()
Text("Room for an image")
Spacer()
Spacer()
}
} //foreach
} //: TABVIEW
.tabViewStyle(PageTabViewStyle())
.onAppear {
setupAppearance() }
} //end of GroupBox
// .padding()
.frame(width:350, height:650)
.clipShape(RoundedRectangle(cornerRadius: 25.0, style: .circular))
.shadow(radius: 5)
} //end of ScrollView
.edgesIgnoringSafeArea(.all)
} //end of Navigation view
}
}
/* Function for the black dots in pagination */
func setupAppearance() {
UIPageControl.appearance().currentPageIndicatorTintColor = .black
UIPageControl.appearance().pageIndicatorTintColor = UIColor.black.withAlphaComponent(0.2)
}
struct TopicsExperienceCards_Previews: PreviewProvider {
static let etypes: [EItype] = Bundle.main.decode("eibasestructure.json")
static var previews: some View {
TopicsExperienceCards(etype:etypes[1])
}
}
You can just pass in the Binding that you use to present the sheet to TopicsExperienceCards as well.
struct ContentView: View {
#State var isPresented = false
var body: some View {
Button("Present") {
isPresented = true /// set Binding to true to present
}
.sheet(isPresented: $isPresented) {
TopicsExperienceCards(isPresented: $isPresented) /// pass Binding here
}
}
}
struct TopicsExperienceCards: View {
#Binding var isPresented: Bool
var body: some View {
VStack {
HStack {
Spacer()
Button(action: {
isPresented = false /// set Binding back to false
}) {
Image(systemName: "xmark")
.padding()
}
}
Spacer()
}
}
}
Result:

View don't disappear with the way it should

I am trying to make the yellow views disappear from bottom and top with a nice and smooth animation. (slide/move to top/bottom + fadding and keep the middle view taking the whole space)
This is my current state, and it is everything but smooth and nice haha. but it works.
import SwiftUI
struct ContentView: View {
#State var isInterfaceHidden: Bool = false
var body: some View {
VStack(spacing: 0, content: {
if !isInterfaceHidden {
Rectangle()
.id("animation")
.foregroundColor(Color.yellow)
.frame(height: 40)
.transition(AnyTransition.move(edge: .top).combined(with: .opacity).animation(.linear))
}
Rectangle()
.id("animation2")
.foregroundColor(Color.red)
.transition(AnyTransition.opacity.animation(Animation.linear))
/// We make sure it won't cover the top and bottom view.
.zIndex(-1)
.background(Color.red)
.onTapGesture(perform: {
DispatchQueue.main.async {
self.isInterfaceHidden.toggle()
}
})
if !isInterfaceHidden {
Rectangle()
.id("animation3")
.foregroundColor(Color.yellow)
.frame(height: 80)
.transition(AnyTransition.move(edge: .bottom).combined(with: .opacity).animation(.linear))
}
})
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
Current animation:
Edit3: Thanks to #Andrew I was able to progress in a better state.
But now I have a sort of jerky animation.
Any thoughts?
I may have found a solution for you:
import SwiftUI
struct ContentView: View {
#State var isInterfaceHidden: Bool = false
var body: some View {
VStack(spacing: 0, content: {
if !isInterfaceHidden {
Rectangle()
.id("animation")
.foregroundColor(Color.yellow)
.frame(height: 40)
.transition(.topViewTransition)
}
Rectangle()
.id("animation2")
.foregroundColor(Color.red)
/// We make sure it won't cover the top and bottom view.
.zIndex(-1)
.background(Color.red)
.onTapGesture(perform: {
DispatchQueue.main.async {
self.isInterfaceHidden.toggle()
}
})
if !isInterfaceHidden {
Rectangle()
.id("animation3")
.foregroundColor(Color.yellow)
.frame(height: 80)
.transition(.bottomViewTransition)
}
})
.navigationBarTitle("")
.navigationBarHidden(true)
.animation(.easeInOut)
}
}
extension AnyTransition {
static var topViewTransition: AnyTransition {
let transition = AnyTransition.move(edge: .top)
.combined(with: .opacity)
return transition
}
static var bottomViewTransition: AnyTransition {
let transition = AnyTransition.move(edge: .bottom)
.combined(with: .opacity)
return transition
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Simply set Z index for the both of your yellow views anything higher than the default value of 1.0. This way SwiftUI will make sure they won't be covered by the red view.
The modifier to do that is .zIndex()
A simpler solution
struct Test: View {
#State private var isHiding = false
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.yellow)
.frame(width: 200, height: 100)
Rectangle()
.foregroundColor(.red)
.frame(width: 200, height: isHiding ? 100 : 80)
.onTapGesture {
withAnimation {
self.isHiding.toggle()
}
}
}
}
}

SwiftUI: Can't get the transition of a DetailView to a ZStack in the MainView to work

I can't find the answer to this anywhere, hopefully one of you can help me out.
I have a MainView with some content. And with the press of a button I want to open a DetailView. I am using a ZStack to layer the DetailView on the top, filling the screen.
But with the following code I can't get it to work. The DetailView does not have a transition when it inserts and it stops at removal. I have tried with and without setting the zIndex manually, and a custom assymetricalTransition. Couldn't get that to work. Any solutions?
//MainView
#State var showDetail: Bool = false
var body: some View {
ZStack {
VStack {
Text("Hello MainWorld")
Button(action: {
withAnimation(.spring()) {
self.showDetail.toggle()
}
}) {
Text("Show detail")
}
}
if showDetail {
ContentDetail(showDetail: $showDetail)
.transition(.move(edge: .bottom))
}
}
.edgesIgnoringSafeArea(.all)
}
And here is the DetailView:
//DetailView
#Binding var showDetail: Bool
var body: some View {
VStack (spacing: 25) {
Text("Hello, DetailWorld!")
Button(action: { withAnimation(.spring()) {
self.showDetail.toggle()
}
}) {
Text("Close")
}
.padding(.bottom, 50)
}
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
}
The result of this code is this:
I'm running Xcode 11.4.1 so implicit animations doesn't seem to work either. Really stuck here, hope one of you can help me out! Thanks :)
Here is a solution. Tested with Xcode 11.4 / iOS 13.4.
struct MainView: View {
#State var showDetail: Bool = false
var body: some View {
ZStack {
Color.clear // extend ZStack to all area
VStack {
Text("Hello MainWorld")
Button(action: {
self.showDetail.toggle()
}) {
Text("Show detail")
}
}
if showDetail {
DetailView(showDetail: $showDetail)
.transition(AnyTransition.move(edge: .bottom))
}
}
.animation(Animation.spring()) // one animation to transitions
.edgesIgnoringSafeArea(.all)
}
}
struct DetailView: View {
#Binding var showDetail: Bool
var body: some View {
VStack (spacing: 25) {
Text("Hello, DetailWorld!")
Button(action: {
self.showDetail.toggle()
}) {
Text("Close")
}
.padding(.bottom, 50)
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // fill in container
.background(Color.yellow)
}
}

How do I implement #EnvironmentObject for this custom full-screen modal setup?

My goal is to have custom modals present over an root view that is essentially a tabbed view. So, I wrapped the TabView in a ZStack and am using an ObservableOBject. But I don't feel I'm doing it the right way.
In my other file, I have the Custom modal "subviews" which has an enum, too, which I think is the right approach to take. But I cannot figure out how to dismiss a modal after it is visible.
It must be #EnvironmentObject, but I don't know what if anything to put in the scene delegate, etc. ("Hacking with Swift" is failing me here, although it's a great resource.)
My idea is that views from the tabbed view will have various buttons which present different modal views, populated later with data specific to say a user and set of fields for data entry.
Right now, I just want to understand how to present and dismiss them.
Here is my root view
import SwiftUI
struct ContentView: View {
#ObservedObject var modal = CustomModal()
var body: some View {
ZStack {
TabView {
ZStack {
Color.pink.opacity(0.2)
Button(action: {
withAnimation{
self.modal.visibleModal = VisibleModal.circle
}
}) {
Text("Circle").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.pink.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "1.square.fill")
Text("One")
}
}.tag(1)
ZStack {
Color.blue.opacity(0.2)
Button(action: {
self.modal.visibleModal = VisibleModal.squircle
}) {
Text("Square").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.blue.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "2.square.fill")
Text("Two")
}
}.tag(2)
}.accentColor(.purple)
VStack {
containedView()
}
}
}
func containedView() -> AnyView {
switch modal.visibleModal {
case .circle: return AnyView(CircleView())
case .squircle: return AnyView(SquircleView())
case .none: return AnyView(Text(""))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And here is my second file with the enum and "subview" modals
import SwiftUI
class CustomModal: ObservableObject {
#Published var visibleModal: VisibleModal = VisibleModal.none
}
enum VisibleModal {
case circle, squircle, none
}
struct CircleView: View {
var body: some View {
ZStack {
Color.pink.blur(radius: 0.4)
Circle().fill()
.frame(width: 300)
.foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct SquircleView: View {
var body: some View {
ZStack{
Color.green.blur(radius: 0.4)
RoundedRectangle(cornerRadius: 48, style: .continuous)
.frame(width: 300, height: 300).foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct dismissButton: View {
#ObservedObject var modal = CustomModal()
var body: some View {
VStack{
Spacer()
Button(action: {
self.modal.visibleModal = VisibleModal.none
}) {
Text("Dismiss").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.white.opacity(0.35)).foregroundColor(.white)
.cornerRadius(12)
.padding(.bottom, 44)
}
}
}
Are you just trying to pass your observable object to the new view?
func containedView() -> some View {
switch modal.visibleModal {
case .circle: return CircleView()
.environmentObject(self.modal)
case .squircle: return SquircleView()
.environmentObject(self.modal)
case .none: return Text("")
}
}
Unless I am misunderstanding the question.
Okay, after a lot of fiddling, it works.
Now my code is as follows.
Root view
struct ContentView: View {
#EnvironmentObject var isModalVisible: CustomModal
#ObservedObject var modal = CustomModal()
var body: some View {
ZStack {
TabView {
ZStack {
Color.pink.opacity(0.2)
Button(action: {
withAnimation{
self.isModalVisible.isModalVisible.toggle()
self.modal.currentModal = VisibleModal.circle
}
}) {
Text("Circle").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.pink.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "1.square.fill")
Text("One")
}
}.tag(1)
ZStack {
Color.blue.opacity(0.2)
Button(action: {
self.isModalVisible.isModalVisible.toggle()
self.modal.currentModal = VisibleModal.squircle
}) {
Text("Square").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.blue.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "2.square.fill")
Text("Two")
}
}.tag(2)
}.accentColor(.purple)
if self.isModalVisible.isModalVisible {
VStack {
containedView()
}
}
}
}
func containedView() -> AnyView {
switch modal.currentModal {
case .circle: return AnyView(CircleView())
case .squircle: return AnyView(SquircleView())
case .none: return AnyView(Text(""))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(CustomModal())
}
}
and the second file with the supporting views and classes and enums:
import SwiftUI
class CustomModal: ObservableObject {
#Published var isModalVisible = false
#Published var currentModal: VisibleModal = .none
}
enum VisibleModal {
case circle, squircle, none
}
struct CircleView: View {
#EnvironmentObject var env: CustomModal
var body: some View {
ZStack {
Color.pink.blur(radius: 0.4)
Circle().fill()
.frame(width: 300)
.foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct SquircleView: View {
var body: some View {
ZStack{
Color.green.blur(radius: 0.4)
RoundedRectangle(cornerRadius: 48, style: .continuous)
.frame(width: 300, height: 300).foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct dismissButton: View {
#EnvironmentObject var env: CustomModal
var body: some View {
VStack{
Spacer()
Button(action: {
self.env.isModalVisible.toggle()
print("TAPPED")
}) {
Text("Dismiss").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.white.opacity(0.35)).foregroundColor(.white)
.cornerRadius(12)
.padding(.bottom, 44)
}
}
}
It still can be refactored. I'm sure. I'd also be happy to hear any comments on how to improve it. But it seems to work.
NOTE: This code ContentView().environmentObject(CustomModal()) is put in the previewP{rovider code and in SceneDelegate.