The z-index of SwiftUI View and UIKit View - ios

The SwiftUI scroll view is hiding some area of a view presented from UIViewControllerRepresentable viewController.
Part of the SwiftUI code, The GoogleMapsView is the UIViewControllerRepresentable viewController.
struct ContentView: View {
var body: some View {
ZStack(alignment: .top) {
GoogleMapsView()
VStack(alignment: .leading) {
Text("Top Locations near you")
ScrollView(.horizontal, showsIndicators: false) {
HStack() {
ForEach(dataSource.topPlaces) { place in
PlaceCardView(placeImage: place.image, placeName: place.name, placeRating: place.rating, placeRatingTotal: place.ratingTotal, topPlace: place)
}
}
.padding(.horizontal, 10)
}
.background(Color.clear)
.foregroundColor(.black)
.frame(height: 200)
.opacity(self.finishedFetching ? 1 : 0.1)
.animation(.easeInOut(duration: 1.3))
}.edgesIgnoringSafeArea(.all)
}
The view I want to put on the top is from the GoogleMapView() which I put it on the "bottom" of the ZStack because I want that Scroll view to flow on the map. However the view show on the map when marker is tapped is cover up by the SwiftUI ScrollView
I tried to change their zIndex of the scrollView, zPosition of the pop up view or bringSubviewToFront etc. None of them work.

You need to inject local state via binding into `` and change it there on popup shown/hidden.
Here is some pseudo code
struct ContentView: View {
#State private var isPopup = false
var body: some View {
ZStack(alignment: .top) {
GoogleMapsView(isPopup: $isPopup)
VStack(alignment: .leading) {
Text("Top Locations near you")
if !isPopup {
ScrollView(.horizontal, showsIndicators: false) {
// other code
}
// other code
}
}.edgesIgnoringSafeArea(.all)
}
struct GoogleMapsView : UIViewControllerRepresentable {
#Binding isPopup: Bool
// other code
// make self.isPopup = true before popup and false after

Related

A problem with the way to display the NavigationView

Use only one NavigationView I did not repeat it
But it appears to me at first
I have data in the first tab Home
All files I didn't put NavigationView only these
Perhaps the problem is from the Tab View file, is there an error?
struct CustomTabView: View {
#State var selectedTap = "iconeTap2"
var body: some View {
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
TabView(selection: $selectedTap) {
Tap1Home() //Here Frist Tab
.tag("iconeTap2")
Tap2Home()
.tag("iconeTap3")
Tap2Home()
.tag("iconeTap4")
Tap2Home()
.tag("iconeTap5")
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.ignoresSafeArea(.all, edges: .bottom)
HStack(spacing: 0) {
ForEach(tabs, id: \.self) { image in
TabButton(image: image, selectedTab: $selectedTap)
if image != tabs.last {
Spacer(minLength: 0)
}
}
}
.padding(.horizontal,25)
.padding(.vertical,5)
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
here I put NavigationView
struct Tap1Home: View {
var body: some View {
NavigationView {
VStack {
Tap1Section1()
// Section *** 2
Tap1Section2()
Spacer()
}
.navigationTitle("22")
}
}
}
There is a hint on screenshot - some NavigationLink is activated inside view which have "Home" as navigationTitle. You may have used NavigationLink's init with 'isActive' property with its value set to true in Tap1Section1 or Tap1Section2.

Create a common layout for the navigation bar in SwiftUI, so other SwiftUI views should reuse same Nav Bar

In iOS SwiftUI, how can we make a common layout for the navigation bar, so we can use that in all projects without rewriting the same code?
We can use ViewBuilder to create a base view for common code as follows:
struct BaseView<Content: View>: View {
let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
// To-do: The most important part will go here
}
}
How can we add navigation bar code in View Builder or base view?
One way to achieve this is to use a custom view as an overlay.
For example, consider the below code which makes a custom navigation bar using an overlay:
struct Home: View {
var body: some View {
ScrollView {
// Your Content
}
.overlay {
ZStack {
Color.clear
.background(.ultraThinMaterial)
.blur(radius: 10)
Text("Navigation Bar")
.font(.largeTitle.weight(.bold))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 20)
}
.frame(height: 70)
.frame(maxHeight: .infinity, alignment: .top)
}
}
}
The ZStack inside the .overlay will make a view that looks like a navigation bar. You can then move it to its own struct view, add different variables to it and call it from the overlay.
You can create an extension of view like this way. You can check out my blog entry for details.
import SwiftUI
extension View {
/// CommonAppBar
public func appBar(title: String, backButtonAction: #escaping() -> Void) -> some View {
self
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
backButtonAction()
}) {
Image("ic-back") // set backbutton image here
.renderingMode(.template)
.foregroundColor(Colors.AppColors.GrayscaleGray2)
})
}
}
Now you can use this appBar in any place of the view.
struct TransactionsView: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var body: some View {
VStack(spacing: 0) {
}
.appBar(title: "Atiar Talukdar") {
self.mode.wrappedValue.dismiss()
}
}
}
struct TransactionsView_Previews: PreviewProvider {
static var previews: some View {
TransactionsView()
}
}

Custom ScrollView Indicator in SwiftUI

Is it possible to create a custom horizontal indicator that has empty and filled circles to show how many images there are and the current position?
The below attempt uses a lazyHStack and OnAppear but, judging from the console output, it doesn't work properly since scrolling back and forth doesn't recall the onAppear consistently.
import SwiftUI
struct ContentView: View {
let horizontalScrollItems = ["wind", "hare.fill", "tortoise.fill", "rosette" ]
var body: some View {
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(horizontalScrollItems, id: \.self) { symbol in
Image(systemName: symbol)
.font(.system(size: 200))
.frame(width: geometry.size.width)
.onAppear(){print("\(symbol)")}
}
}
}
}
}
}
This is the desired indicator. I'm just not sure how to properly fill and empty each circle as the user scrolls back and forth. Appreciate the help!
You can get the desired result using TabView() and PageTabViewStyle()
Note : This will work from SwiftUI 2.0
Here is the code :
struct ContentView: View {
let horizontalScrollItems = ["wind", "hare.fill", "tortoise.fill", "rosette" ]
var body: some View {
GeometryReader { geometry in
TabView(){
ForEach(horizontalScrollItems, id: \.self) { symbol in
Image(systemName: symbol)
.font(.system(size: 200))
.frame(width: geometry.size.width)
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
}
}
}
Result :

Why SwiftUI context menu show all row view in preview?

I have a complex view in List row:
var body: some View {
VStack {
VStack {
FullWidthImageView(ad)
HStack {
Text("\(self.price) \(self.ad.currency!)")
.font(.headline)
Spacer()
SwiftUI.Image(systemName: "heart")
}
.padding([.top, .leading, .trailing], 10.0)
Where FullWidthImageView is view with defined contexMenu modifier.
But when I long-press on an image I see not the only image in preview, but all row view.
There is no other contextMenu on any element.
How to make a preview in context with image only?
UPD. Here is a simple code illustrating the problem
We don't have any idea why in your case it doesn't work, until we see your FullWidthImageView and how you construct the context menu. Asperi's answer is working example, and it is correctly done! But did it really explain your trouble?
The trouble is that while applying .contextMenu modifier to only some part of your View (as in your example) we have to be careful.
Let see some example.
import SwiftUI
struct FullWidthImageView: View {
#ObservedObject var model = modelStore
var body: some View {
VStack {
Image(systemName: model.toggle ? "pencil.and.outline" : "trash")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 200)
}.contextMenu(ContextMenu {
Button(action: {
self.model.toggle.toggle()
}) {
HStack {
Text("toggle image to?")
Image(systemName: model.toggle ? "trash" : "pencil.and.outline")
}
}
Button("No") {}
})
}
}
class Model:ObservableObject {
#Published var toggle = false
}
let modelStore = Model()
struct ContentView: View {
#ObservedObject var model = modelStore
var body: some View {
VStack {
FullWidthImageView()
Text("Long press the image to change it").bold()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
while running, the "context menu" modified View seems to be "static"!
Yes, on long press, you see the trash image, even though it is updated properly while you dismiss the context view. On every long press you see trash only!
How to make it dynamic? I need that the image will be the same, as on my "main View!
Here we have .id modifier. Let see the difference!
First we have to update our model
class Model:ObservableObject {
#Published var toggle = false
var id: UUID {
UUID()
}
}
and next our View
FullWidthImageView().id(model.id)
Now it works as we expected.
For another example, where "standard" state / binding simply doesn't work check SwiftUI hierarchical Picker with dynamic data crashes
UPDATE
As a temporary workaround you can mimic List by ScrollView
import SwiftUI
struct Row: View {
let i:Int
var body: some View {
VStack {
Image(systemName: "trash")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 200)
.contextMenu(ContextMenu {
Button("A") {}
Button("B") {}
})
Text("I don’t want to show in preview because I don’t have context menu modifire").bold()
}.padding()
}
}
struct ContentView: View {
var body: some View {
VStack {
ScrollView {
ForEach(0 ..< 20) { (i) in
VStack {
Divider()
Row(i: i)
}
}
}
}
}
}
It is not optimal, but in your case it should work
Here is a code (simulated possible your scenario) that works, ie. only image is shown for context menu preview (tested with Xcode 11.3+).
struct FullWidthImageView: View {
var body: some View {
Image("auto")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 200)
.contextMenu(ContextMenu() {
Button("Ok") {}
})
}
}
struct TestContextMenu: View {
var body: some View {
VStack {
VStack {
FullWidthImageView()
HStack {
Text("100 $")
.font(.headline)
Spacer()
Image(systemName: "heart")
}
.padding([.top, .leading, .trailing], 10.0)
}
}
}
}
It's buried in the replies here, but the key discovery is that List is changing the behavior of .contextMenu -- it creates "blocks" that pop up with the menu instead of attaching the menu to the element specified. Switching out List for ScrollView fixes the issue.

SwiftUI View displayed with blue background

I'm trying to reproduce the Apple tutorial(Composing Complex Interfaces) and I have a very weird problem. My CategoryItem view is being displayed as a blue frame.
If I remove the NavigationLink which wraps it, everything works fine but with that one it doesn't.
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
VStack(alignment: .leading) {
Text(self.categoryName)
.font(.headline)
.padding(.leading, 15)
.padding(.top, 5)
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(self.items) { landmark in
NavigationLink(
destination: LandmarkDetail(
landmark: landmark
)
) {
CategoryItem(landmark: landmark)
}
}
}
}.frame(height: 185)
}
}
}
NavigationLink has a blue accent color by default, just call .accentColor(Color.clear) on it
Or you could try this:
NavigationView {
NavigationLink(destination: Text("Detail view here")) {
Image("YourImage")
}
.buttonStyle(PlainButtonStyle())
}
https://www.hackingwithswift.com/quick-start/swiftui/how-to-disable-the-overlay-color-for-images-inside-button-and-navigationlink
renderingMode(.original) is what did it for me; .accentColor(Color.clear) made the image invisible (my best explanation here is because it didn't have a transparency).
NavigationView {
NavigationLink(destination: Text("Detail view here")) {
Image("YourImage")
.renderingMode(.original)
}
}
As the answer above mentioned, How to disable the overlay color for images inside Button and NavigationLink is a good write up as well.

Resources