I have a HomeView which is basically a ZStack of a MapView and a Circle, and I am following https://www.hackingwithswift.com/books/ios-swiftui/advanced-mkmapview-with-swiftui.
Both the map and circle appear when I am using a ZStack right here in the ContentView. However, I have authentication set up so when I do the following (below), I only see the map and not the circle:
ContentView.swift
import SwiftUI
struct ContentView: View {
#EnvironmentObject var auth: AuthenticationState
var body: some View {
ZStack {
if auth.user != nil {
HomeView()
} else {
GatekeeperView()
}
}
}
}
HomeView.swift
struct HomeView: View {
var body: some View {
ZStack {
Circle()
.fill(Color.blue)
.opacity(1)
.frame(width: 32, height: 32)
MapView().edgesIgnoringSafeArea(.all)
}
}
}
Authenticated users get to see the HomeView. Unfortunately, the circle does not show up.
Try explicitly setting .zIndex() for your views in ZStack:
struct HomeView: View {
var body: some View {
ZStack {
Circle()
.fill(Color.blue)
.opacity(1)
.frame(width: 32, height: 32)
.zIndex(1)
MapView().edgesIgnoringSafeArea(.all)
.zIndex(2)
}
}
}
Alternatively reorder your ZStack:
struct HomeView: View {
var body: some View {
ZStack {
MapView().edgesIgnoringSafeArea(.all)
Circle()
.fill(Color.blue)
.opacity(1)
.frame(width: 32, height: 32)
}
}
}
Related
I'm facing some dilemma. I like to separate my views for readability . so for example i'm having this kind of structure
MainView ->
--List1
----Items1
--List2
----Items2
----DetailView
------CellView
so cellView having same namespace for matchedGeometryEffect as DetailsView. to make the effect of transition to detail view from cell item in list. the problem is that this details view is limited to List2 screen /View.
Here's some code to make it more clear
First I have main View
struct StartFeedView: View {
var body: some View {
ScrollView(.vertical) {
ShortCutView()
PopularMoviesView()
}
}
}
then I have PopularMoviesView()
struct PopularMoviesView: View {
#Namespace var namespace
#ObservedObject var viewModel = ViewModel()
#State var showDetails: Bool = false
#State var selectedMovie: Movie?
var body: some View {
ZStack {
if !showDetails {
VStack {
HStack {
Text("Popular")
.font(Font.itemCaption)
.padding()
Spacer()
Image(systemName: "arrow.forward")
.font(Font.title.weight(.medium))
.padding()
}
ScrollView(.horizontal) {
if let movies = viewModel.popularMovies {
HStack {
ForEach(movies.results, id: \.id) { movie in
MovieCell(movie: movie, namespace: namespace, image: viewModel.imageDictionary["\(movie.id)"]!)
.padding(6)
.frame(width: 200, height: 300)
.onTapGesture {
self.selectedMovie = movie
withAnimation(Animation.interpolatingSpring(stiffness: 270, damping: 15)) {
showDetails.toggle()
}
}
}
}
}
}
.onAppear {
viewModel.getMovies()
}
}
}
if showDetails, let movie = selectedMovie, let details = movie.details {
MovieDetailsView(details: details, namespace: namespace, image: viewModel.imageDictionary["\(movie.id)"]!)
.onTapGesture {
withAnimation(Animation.interpolatingSpring(stiffness: 270, damping: 15)) {
showDetails.toggle()
}
}
}
}
}
}
so whenever I click on MovieCell.. it will expand to the limit of the PopularMoviesView boundaries on the main view.
Is there some kind of way to make it full screen without to have to inject the detail view into the MainView? Cause that would be really dirty
Here is an approach:
Get the size of MainView with GeometryReader and pass it down.
in DetailView use .overlay which can grow bigger than its parent view,
if you specify an explicit .frame
You need another inner GeometryReaderto get the top pos of inner view for offset.
struct ContentView: View {
var body: some View {
// get size of overall view
GeometryReader { geo in
ScrollView(.vertical) {
Text("ShortCutView()")
PopularMoviesView(geo: geo)
}
}
}
}
struct PopularMoviesView: View {
// passed in geometry from parent view
var geo: GeometryProxy
// own view's top position, will be updated by GeometryReader further down
#State var ownTop = CGFloat.zero
#Namespace var namespace
#State var showDetails: Bool = false
#State var selectedMovie: Int?
var body: some View {
if !showDetails {
VStack {
HStack {
Text("Popular")
.font(.caption)
.padding()
Spacer()
Image(systemName: "arrow.forward")
.font(Font.title.weight(.medium))
.padding()
}
ScrollView(.horizontal) {
HStack {
ForEach(0..<10, id: \.self) { movie in
Text("MovieCell \(movie)")
.padding()
.matchedGeometryEffect(id: movie, in: namespace)
.frame(width: 200, height: 300)
.background(.yellow)
.onTapGesture {
self.selectedMovie = movie
withAnimation(Animation.interpolatingSpring(stiffness: 270, damping: 15)) {
showDetails.toggle()
}
}
}
}
}
}
}
if showDetails, let movie = selectedMovie {
// to get own view top pos
GeometryReader { geo in Color.clear.onAppear {
ownTop = geo.frame(in: .global).minY
print(ownTop)
}}
// overlay can become bigger than parent
.overlay (
Text("MovieDetail \(movie)")
.font(.largeTitle)
.matchedGeometryEffect(id: movie, in: namespace)
.frame(width: geo.size.width, height: geo.size.height)
.background(.gray)
.position(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).midY - ownTop)
.onTapGesture {
withAnimation(Animation.interpolatingSpring(stiffness: 270, damping: 15)) {
showDetails.toggle()
}
}
)
}
}
}
I've run in to a strange behavior in SwiftUI that I can't seem to work around.
Given the following simple example app I experience this behavior: The toolbar item renders correctly on the initial run, but navigating away and returning it gets clipped.
Sample code to recreate this:
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: View2()) {
Text("Hello, world!")
.padding()
.navigationTitle("View 1")
.toolbar {
Circle()
.fill(Color.red)
.frame(width: 150, height: 150, alignment: .center)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
View2.swift
import SwiftUI
struct View2: View {
var body: some View {
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
}
}
struct View2_Previews: PreviewProvider {
static var previews: some View {
View2()
}
}
It is clipped by navigation bar as seen on view debug below so it is just rendering issue (should be clipped always):
A possible solution is to use that widget (Circle in the case) above NavigationView and align it with toolbar item.
Here is main part:
.toolbar {
Color.clear
.frame(width: 150)
.overlay(GeometryReader {
Color.clear.preference(key: ViewPointKey.self,
value: [$0.frame(in: .global).center])
})
}
//...
Circle().fill(Color.red)
.frame(width: 150, height: 150)
.position(x: pos.x, y: pos.y) // << here !!
//...
.onPreferenceChange(ViewPointKey.self) {
pos = $0.first ?? .zero
}
Complete findings and code is here
OUTLINE
I have made a custom slimline sidebar that I am now implementing across the whole app. The sidebar consists of a main button that is always showing and when pressed it shows or hides the rest of the sidebar that consists of buttons navigating to other views.
I am currently implementing the sidebar across the app on each view by creating a ZStack like this:
struct MainView: View {
var body: some View {
ZStack(alignment: .topLeading) {
SideBarCustom()
Text("Hello, World!")
}
}
}
PROBLEM
I am planning on adding a GeometryReader so if the side bar is shown the rest of the content moves over. With this in mind, the way I am implementing the sidebar on every view feels clunky and a long winded way to add it. Is there a more simple/better method to add this to each view?
Sidebar Code:
struct SideBarCustom: View {
#State var isToggle = false
var names = ["Home", "Products", "Compare", "AR", "Search"]
var icons = ["house.fill", "printer.fill.and.paper.fill", "list.bullet.rectangle", "arkit", "magnifyingglass"]
var imgSize = 20
var body: some View {
GeometryReader { geo in
VStack {
Button(action: {
self.isToggle.toggle()
}, label: {
Image("hexagons")
.resizable()
.frame(width: 40, height: 40)
.padding(.bottom, 20)
})
if isToggle {
ZStack{
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color.red)
.frame(width: 70, height: geo.size.height)
VStack(alignment: .center, spacing: 60) {
ForEach(Array(zip(names, icons)), id: \.0) { item in
Button(action: {
// NAVIIGATE TO VIEW
}, label: {
VStack {
Image(systemName: item.1)
.resizable()
.frame(width: CGFloat(imgSize), height: CGFloat(imgSize))
Text(item.0)
}
})
}
}
}
}
}
}
}
}
I don't think there's necessarily a reason to use GeometryReader here. The following is an example that has a dynamic width sidebar (although you could set it to a fixed value) that slides in and out. The main content view resizes itself automatically, since it's in an HStack:
struct ContentView : View {
#State private var sidebarShown = false
var body: some View {
HStack {
if sidebarShown {
CustomSidebar(sidebarShown: $sidebarShown)
.frame(maxHeight: .infinity)
.border(Color.red)
.transition(sidebarShown ? .move(edge: .leading) : .move(edge: .trailing) )
}
ZStack(alignment: .topLeading) {
MainContentView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
if !sidebarShown {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
}
}
}
}
}
struct CustomSidebar : View {
#Binding var sidebarShown : Bool
var body: some View {
VStack {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
Spacer()
Text("Hi")
Text("There")
Text("World")
Spacer()
}
}
}
struct MainContentView: View {
var body: some View {
VStack {
Text("Main content")
}
}
}
I'm trying to make this Profile Button to work inside a ScrollView, that when tapped, a modal view should come up. But for some reason, when I add some offset to the Button, to make it inline with the Navigation Title, the Button stops working.
Does anyone know a fix for this?
Here's my code:
import SwiftUI
struct TestView: View {
#State var showMenu = false
var body: some View {
NavigationView {
ScrollView {
VStack {
HStack {
Spacer()
Image(systemName: "person.crop.circle")
.font(.system(size: 36))
.foregroundColor(.blue)
.frame(width: 44, height: 44)
.onTapGesture {
showMenu.toggle()
}
.offset(y: -64)
}
Spacer()
}
.padding()
.sheet(isPresented: $showMenu) {
MenuView()
}
}
.navigationTitle("Browse")
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
Swift 5.x iOS 13/14
Trying to build a custom action sheet and mystified as to why this slides from the left and then up when I am telling it to simply slide from the bottom or at least I thought that was what I was asking.
import SwiftUI
struct ContentView: View {
#State private var showingCustomSheet = false
var body: some View {
ZStack {
Button(action: {
self.showingCustomSheet.toggle()
}) {
Text("Fire")
.font(.largeTitle)
.foregroundColor(Color.red)
}
if showingCustomSheet {
CustomAlertSheet(showingCustomSheet: $showingCustomSheet)
}
}
}
}
struct CustomAlertSheet: View {
#Binding var showingCustomSheet: Bool
#State var showEscape = false
var body: some View {
VStack {
Spacer().onAppear {
withAnimation(.linear(duration: 2)) {
showEscape.toggle()
}
}
if showEscape {
Rectangle()
.fill(Color.red)
.frame(width: 256, height: 128)
.transition(.move(edge: .bottom))
.animation(.default)
}
}
}
}
Here is a solution - removed redundant things and some small fixes... Tested with Xcode 12 / iOS 14.
struct ContentView: View {
#State private var showingCustomSheet = false
var body: some View {
ZStack {
Button(action: {
self.showingCustomSheet.toggle()
}) {
Text("Fire")
.font(.largeTitle)
.foregroundColor(Color.red)
}
CustomAlertSheet(showingCustomSheet: $showingCustomSheet)
}
}
}
struct CustomAlertSheet: View {
#Binding var showingCustomSheet: Bool
var body: some View {
VStack {
Spacer()
if showingCustomSheet {
Rectangle()
.fill(Color.red)
.frame(width: 256, height: 128)
.transition(.move(edge: .bottom))
}
}
.animation(.default, value: showingCustomSheet)
}
}