make Button action cover it's superview swiftUI - ios

i need to make a button that fills it's container view and make all the view clickable, but i failed, it only make button content size clickable (which is the label size)
Button(action: {
print("item Clicked")
}) {
Text("whyyyy")
}.frame(maxWidth: .infinity, maxHeight: .infinity).buttonStyle(PlainButtonStyle())
i know i can reverse it by putting my container view within the button but i just don't like this style i need the button to be within the view
and i know i can make a tapGesture but i want to feel free like in UIKit

You can use clear color content to make whole area clickable.
struct ContentViewNewTest: View {
var body: some View {
ZStack {
Color.red
Button(action: {
print("item Clicked")
}) {
Color.clear //<--- Here
}
}
}
}

Just move .frame(maxWidth: .infinity, maxHeight: .infinity) under Text("whyyyy") before before closing the bracket
Edit:
Also add the modifier .background(Color.clear)
Final Result:
Button(action: {
print("item Clicked")
}) {
Text("whyyyy")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.clear)
}

Related

How to put tabs inside a scrollview in SwiftUI?

So basically I am very new to SwiftUI(started a few days ago) and am trying to put tabs within a ScrollView. The end result I am trying to achieve is that of Instagram's profile view.
I'd imagine the view's implementation would be something like this:
ScrollView {
VStack {
HStack {
// Profile Pic
// Stats
}
// Bio
// Button(s)
LazyVStack(pinnedViews: .sectionHeaders) {
Section {
TabView {
// Tab 1
// Tab 2
// Tab 3
}
} headers: {
// Tab icons
}
}
}
}
The problem is, the TabView never appears. Also I am unsure whether this is the best setup especially the LazyVStack as I am only using to for the fact that it pins the headers. As I said previously, I'm super new to SwiftUI so there are definetly some views that I have no idea exist some of which might be useful in what I am trying to achieve.
Nonetheless, how can I achieve the layout I am going for?
Thank you!
Side Question: With the way that I have the view setup, the scrollbar for the view is for the entire view however in apps like Instagram, the scrollbars are only within the tabs themselves. How could I also incorporate that aspect into the solution? Thanks again!
You are on the right track. TabView when not used as the root loses the ability to resize it self properly.
So that's why we need to specfiy its minHeight. You can use GeometryReader for that.
Then just give TabView a selection in order for the Tab-Buttons to work and most likely you want to apply .tabViewStyle(.page(indexDisplayMode: .never)) in order to be able to swipe between them.
Working Demo:
GeometryReader { proxy in
ScrollView {
VStack {
HStack {
Image(systemName: "person.circle")
Text("Some Text")
}
}
LazyVStack(spacing: 0, pinnedViews: .sectionHeaders) {
Section {
TabView(selection: $tabIndex) {
VStack {
Spacer()
Text("1")
Spacer()
}
.frame(maxWidth: .infinity)
.background(.red.opacity(0.5))
.tag(0)
VStack {
Spacer()
Text("2")
Spacer()
}
.frame(maxWidth: .infinity)
.background(.green.opacity(0.5))
.tag(1)
VStack {
Spacer()
Text("3")
Spacer()
}
.frame(maxWidth: .infinity)
.background(.blue.opacity(0.5))
.tag(2)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.frame(maxWidth: .infinity, minHeight: proxy.size.height)
} header: {
HStack {
Button {
withAnimation {
tabIndex = 0
}
} label: {
Text("Tab 1")
}
.buttonStyle(.bordered)
Button {
withAnimation {
tabIndex = 1
}
} label: {
Text("Tab 2")
}
.buttonStyle(.bordered)
Button {
withAnimation {
tabIndex = 2
}
} label: {
Text("Tab 3")
}
.buttonStyle(.bordered)
}
.padding()
.frame(maxWidth: .infinity)
.background(.regularMaterial)
}
}
}
}
As to your side question: They most likely still use a single scrollview but then move the inset of the scroll indicators. You cannot do that with SwiftUI only but you would need to introspect the underlying UIScrollView for that. SwiftUI-Introspect is good for that.
And then adjust the verticalScrollIndicatorInsets to your needs

SwiftUI onTapGuesture not working using offset inside ZStack?

I have a Mapbox map view and a search bar and activate button (HStack) inside a ZStack and using an offset modifier to position them at the top of the screen.
For some reason the offset is preventing the onTapGesture from working for the activate button, if I comment out the offset it will work but will be placed at the bottom of the screen.
I tried adding the offset to each element individually inside the HStack but that did not work...
How can I make the onTapGesture functionality work with offset?
Thank you
struct MapScreen: View {
var body: some View {
NavigationView {
ZStack(alignment: .bottom) {
HStack(spacing: 10) {
SearchBar()
.padding(.leading, 5)
.onTapGesture {
print("search pressed")
}
ActivateButton()
.onTapGesture {
print("activate pressed")
}
}.zIndex(2)
.offset(y: -770)
MapView(locations: $locations)
.edgesIgnoringSafeArea([.top, .bottom])
BottomNavBar().zIndex(1)
.edgesIgnoringSafeArea(.all).offset(y: 35)
}
.navigationViewStyle(StackNavigationViewStyle())
.fullScreenCover(isPresented: $presentSearchView, content: {
SearchView()
})
}
}
}
There's a couple problems here:
.offset(y: -770)
If you're trying to use an offset so large, you shouldn't be using offset at all. offset is usually for fine-tune adjustments and doesn't work great with big values. And also, 770 is hardcoded. What happens when you use another device with a different screen size? Don't hardcode or do calculations yourself — SwiftUI can do it for you!
Instead, use a VStack + Spacer() to push the search bar up.
ZStack(alignment: .bottom) {
VStack { /// here!
HStack(spacing: 10) {
SearchBar()
.padding(.leading, 5)
.onTapGesture {
print("search pressed")
}
ActivateButton()
.onTapGesture {
print("activate pressed")
}
}
Spacer() /// push the `HStack` to the top of the screen
}
.zIndex(2)
MapView(locations: $locations)
.edgesIgnoringSafeArea([.top, .bottom])
BottomNavBar().zIndex(1)
.edgesIgnoringSafeArea(.all).offset(y: 35)
}

Possible to allowsHitTesting on part of view?

I have a button behind a ScrollView, but cannot tap it since it's under it. I have a Spacer at the top of the scroll view that shows the button. I tried putting allowsHitTesting(false) on the Spacer, but this is still not letting it pass underneath the ScrollView.
This is what the `ScrollView looks like, I cannot tap the "Press" button:
This is the code, notice the button is in the ZStack, and the Spacer in the ScrollView has the allowsHitTesting(false):
struct ContentView: View {
#State private var isPresented = false
var body: some View {
ZStack(alignment: .top) {
Button("Press") {
isPresented = true
}
.frame(maxWidth: .infinity, maxHeight: 200)
.background(Color(.label).ignoresSafeArea())
ScrollView {
Spacer()
.frame(height: 200)
.allowsHitTesting(false) // <---- Will not tap thru ScrollView!!
VStack {
Button("Another Press") {
isPresented = true
}
.padding(50)
ForEach((0...50), id: \.self) {
Text("Some text \($0)")
}
}
.frame(maxWidth: .infinity)
.background(Color(.white))
}
}
.navigationBarHidden(true)
.alert(isPresented: $isPresented) {
Alert(title: Text("Button tapped"))
}
}
}
Tapping the button in the background under the Spacer doesn't work. Is there a way to apply the allowsHitTesting(false) to part of the ScrollView that the Spacer occupies? I obviously don't want to apply allowsHitTesting(false) on the entire ScrollView because this is a simplistic example but a real app would have tons of interaction views within the ScrollView. Thanks for any help or insight!

Present Modal fullscreem SwiftUI

how can i present a modal that will take up the fullscreen and can't be dismissed by swiping it down? Currently I am using .sheet on a view to present a modal that is dismissible.
I haven't noticed any beta changes in Xcode that changes this behavior.
Any help would be appreciated :)
SwiftUI 1.0
I'm not sure if this what you'd want to go with but it's possible to create your own modal screen by using the ZStack and a state variable to control the hiding/showing of it.
Code
struct CustomModalPopups: View {
#State private var showingModal = false
var body: some View {
ZStack {
VStack(spacing: 20) {
Text("Custom Popup").font(.largeTitle)
Text("Introduction").font(.title).foregroundColor(.gray)
Text("You can create your own modal popup with the use of a ZStack and a State variable.")
.frame(maxWidth: .infinity)
.padding().font(.title).layoutPriority(1)
.background(Color.orange).foregroundColor(Color.white)
Button(action: {
self.showingModal = true
}) {
Text("Show popup")
}
Spacer()
}
// The Custom Popup is on top of the screen
if $showingModal.wrappedValue {
// But it will not show unless this variable is true
ZStack {
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.vertical)
// This VStack is the popup
VStack(spacing: 20) {
Text("Popup")
.bold().padding()
.frame(maxWidth: .infinity)
.background(Color.orange)
.foregroundColor(Color.white)
Spacer()
Button(action: {
self.showingModal = false
}) {
Text("Close")
}.padding()
}
.frame(width: 300, height: 200)
.background(Color.white)
.cornerRadius(20).shadow(radius: 20)
}
}
}
}
}
Example
(Excerpt from "SwiftUI Views" book)
So here, your popup is small, but you can adjust the dimensions to make it fullscreen with the frame modifier that is on that VStack.

How to put a logo in NavigationView in SwiftUI?

I am trying to use a logo image instead of a NavigationView title at the top section of the app. Couldn't find any documentation of using images inside a NavigationView.
iOS 14+
Starting from iOS 14 you can create a ToolbarItem with the principal placement:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Test")
.toolbar {
ToolbarItem(placement: .principal) {
Image(systemName: "ellipsis.circle")
}
}
}
}
}
See the ToolbarItemPlacement documentation for more placements.
NavigationView.navigationBarTitle() can only take a Text() argument right now. You could instead use .navigationBarItems() to set an Image as either the trailing or leading argument, but this is the SwiftUI equivalent of UINavigationItem.leftBarButtonItem[s] and UINavigationItem.rightBarButtonItem[s], which means that you're restricted to navigation bar button dimensions. But if you're ok with that, you may want to set a blank title so that you can specify a standard-height navigation bar.
Hard-Coded Positioning
If you can stand to live with yourself, you can fake a centered nav bar item by hard-coding padding around the image, like
.padding(.trailing, 125),
(Note that I deliberately positioned it off-center so that you can see that it's hard-coded.)
Slightly Less Hard-Coded Positioning
Even better would be to wrap the whole thing in a GeometryReader { geometry in ... } block to use the screen dimensions to calculate precise positioning, if you know the exact width of the image you're using:
GeometryReader { geometry in
NavigationView {
...
}
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(trailing:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.padding(.trailing, (geometry.size.width / 2.0) + -30), // image width = 60
destination: ProfileHost()
)
)
If you don't want to hack it, here's what you can do:
Standard nav bar height, left button item
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(leading:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.padding(),
destination: ProfileHost()
)
)
Standard nav bar height, right button item
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(trailing:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.padding(),
destination: ProfileHost()
)
)
Expanded nav bar height, no title, left button item
.navigationBarItems(leading:
PresentationButton(
Image(systemName: "person.crop.circle")
.imageScale(.large)
.padding(),
destination: ProfileHost()
)
)
Use this:
NavigationView {
Text("Hello, SwiftUI!")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
Image(systemName: "sun.min.fill")
Text("Title").font(.headline)
}
}
}
}
Credit: https://sarunw.com/posts/custom-navigation-bar-title-view-in-swiftui/
With SwiftUIX, you can use navigationBarTitleView(View):
NavigationView() {
NavigationLink(destination:YourView().navigationBarTitleView(Image(systemName: "message.fill")))
}
I don't want to claim 100% accuracy whether title image positioned at center but visually it looks center to me. Do your judgment and adjust padding :)
Here is code:
.navigationBarTitle(
Text("")
, displayMode: .inline)
.navigationBarItems(leading:
HStack {
Button(action: {
}) {
Image(systemName: "arrow.left")
}.foregroundColor(Color.oceanWhite)
Image("oceanview-logo")
.resizable()
.foregroundColor(.white)
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 40, alignment: .center)
.padding(UIScreen.main.bounds.size.width/4+30)
}
,trailing:
HStack {
Button(action: {
}) {
Image(systemName: "magnifyingglass")
}.foregroundColor(Color.oceanWhite)
}
)
To extend on NRitH's answer, putting your logo in a different component (to borrow a React way of putting it) may help anyone looking to understand the concepts.
The actual Image can be wrapped in any container view such as a VStack, etc. An example of setting up a struct as a component to be used in our navigation items could be something like the following:
struct NavLogo: View {
var body: some View {
VStack {
Image("app-logo")
.resizable()
.aspectRatio(2, contentMode: .fit)
.imageScale(.large)
}
.frame(width: 200)
.background(Color.clear)
}
}
When the aspect ratio is set, only the width needs to be set on the frame on the container view. We could also set a property in the NavLogo to set width and/or height from property dependency injection. Regardless, our navigationBarItems becomes very straight forward and more readable 🙂
NavigationView {
Text("Home View")
.navigationBarItems(
leading: NavLogo()
trailing: ProfileButton()
)
}
On iOS 13, a little hacky way to achieve this:
private var logo: some View {
Image("logo-image")
}
var body: some View {
GeometryReader { g in
content()
.navigationBarTitle("")
.navigationBarItems(leading:
ZStack(alignment: .leading) {
logo.frame(width: g.size.width).padding(.trailing, 8)
HStack {
leadingItems().padding(.leading, 10)
Spacer()
trailingItems().padding(.trailing, 10)
}
.frame(width: g.size.width)
}
)
}
}
Try the following.
struct ContainerView: View {
var body: some View {
VStack {
Image(systemName: "person.crop.square")
ContentView()
}
}
}
It worked for me.
Make sure you change ContentView to ContainerView inside SceneDelegate.swift before running on simulator or device.

Resources