SwiftUI image with borders overlaying button - swiftui

Thanks to a previous question I asked, I am using Color.black.overlay and .clipped() to show an image with letterbox borders above and below it.
But when I attempt to put a button on the top border, it can't be tapped on (I assume because the image (unclipped) is in that space, and is intercepting the tap gesture).
Here is what the layout looks like:
Here is the code:
var body: some View {
ZStack {
Color.black
VStack {
topBorder
imageMiddle
bottomBorder
}
}
.ignoresSafeArea()
}
var topBorder: some View {
return Group {
ZStack {
Rectangle()
.fill(.green)
.frame(minHeight: borderHeight, maxHeight: borderHeight)
Button {
print("tap")
} label: {
Image(systemName: "hand.tap.fill")
.foregroundColor(.black)
}
}
}
}
var bottomBorder: some View {
return Group {
Rectangle()
.fill(.green)
.frame(minHeight: borderHeight, maxHeight: borderHeight)
}
}
var imageMiddle: some View {
return Group {
Color.black.overlay(
Image("cat")
.scaledToFill()
)
.clipped()
}
}
How can I expose that button to a user's tap?

Adding .allowsHitTesting(false) to your image view will fix it. However, it seems like the wrong approach.
VStack {
topBorder
imageMiddle
.allowsHitTesting(false) // <- This will fix your problem.
bottomBorder
}
I would recommend using another approach to add your borders on top of the image instead. Something like this:
ZStack {
imageMiddle
VStack {
topBorder
.overlay(alignment: .bottom) {
Rectangle().frame(minHeight: 0, maxHeight: 10)
}
Spacer()
bottomBorder
.overlay(alignment: .top) {
Rectangle().frame(minHeight: 0, maxHeight: 10)
}
}
}
.ignoresSafeArea()

Related

tab bar customisation swift ui

i am new in swiftUI and i want to make custom tab bar.
Let's ignore effects and icons.
I create new struct struct BottomPoligon: Shape for draw this Shape and after this i create new view TabBarView:
struct TabBarView: View {
var body: some View {
Spacer()
Button {
print("tap")
} label: {
ZStack {
Circle()
.fill(.clear)
.border(.red, width: 2)
Image(systemName: "barcode.viewfinder")
}
}
.frame(width: 50, height: 50)
HStack(alignment: VerticalAlignment.center) {
Button {
print("tap")
} label: {
Image(systemName: "person")
}
.frame(maxWidth: .infinity)
Button {
print("tap")
} label: {
Image(systemName: "list.bullet.circle.fill")
}
.frame(maxWidth: .infinity)
}
.frame(height: 50)
.background(
BottomPoligon(height: 50, cornerRadius: 15)
// .fill(.gray)
.stroke(Color.gray)
)
}
}
But I don't know how to position the button in the middle. Do you have any idea how I can make this or maybe another approach to make this? Is my overall idea good or bad?

Display View on top off bottom sheet

I'm trying to implement a view that displays error message for my whole app.
I want this view to always be above every other view, but I also use sheets in my app and in that case the error message is hidden behind the sheet, since the sheet is displayed above every other view.
Here is a View to reproduce my situation:
struct AppView: View {
#State var isPresentingSheet = false
var body: some View {
ZStack {
VStack {
Button("Toggle sheet") {
isPresentingSheet.toggle()
}
}
.sheet(isPresented: $isPresentingSheet) {
Text("Im above everything else")
}
VStack {
HStack {
Image(systemName: "xclose")
Text("I want to be even above the sheet")
}
.foregroundColor(Color.red)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(Padding.l)
.background(Color.red.opacity(0.2))
.overlay(
Rectangle()
.frame(height: 1)
.foregroundColor(Color.red),
alignment: .bottom
)
Spacer()
}
}
}
}
I want to know if it's possible to display a view above a sheet, but to me it looks like the sheet is in a completely different window?
But maybe it's possible to create a custom sheet that moves in from the top and is displayed above other native sheets?
If anybody is interested, I have created a custom bottom sheet with simple controls and snap functionality.
The bottom sheet has a zIndex of 1 so you can easily place views above it with a greater zIndex.
You can find it here: AlternativeSheet.
Here is the code to achieve the view of the image above.
import AlternativeSheet
...
ZStack {
VStack {
Button("Toggle sheet") {
isPresentingSheet.toggle()
}
}
.alternativeSheet(isPresented: $isPresentingSheet, snaps: [0.95]) {
Text("Im above everything else")
}
.isDraggable()
.dampenDrag()
VStack {
HStack {
Image(systemName: "xclose")
Text("I want to be even above the sheet")
}
.foregroundColor(Color.red)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(Padding.l)
.background(Color.red.opacity(0.3))
.overlay(
Rectangle()
.frame(height: 1)
.foregroundColor(Color.red),
alignment: .bottom
)
Spacer()
}
}

swiftui list with custom image header

I want to set a image header for list in swiftui. The effect I want is shown in the figure below:
My code is as bellow:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
Section() {
VStack() {
Image("HeadImage")
.resizable()
.frame(width: 50, height: 50)
Text("Test word")
.foregroundColor(Color.red)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 150)
}
ForEach((0..<4), id: \.self) { index in
Section {
NavigationLink(destination: Text("aaa")) {
Label("Buttons", systemImage: "capsule")
}
NavigationLink(destination: Text("aaa")) {
Label("Colors", systemImage: "paintpalette")
}
NavigationLink(destination: Text("aaa")) {
Label("Controls", systemImage: "slider.horizontal.3")
}
}
}
}
.navigationBarTitle("SwiftUI")
.navigationBarTitleDisplayMode(.inline)
}
.accentColor(.accentColor)
}
}
The result is as bellow, the header view has a white background:
I find a method to set the image row backgroud color as bellow:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
Section() {
VStack() {
Image("HeadImage")
.resizable()
.frame(width: 50, height: 50)
Text("Test word")
.foregroundColor(Color.red)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 150)
.listRowInsets(EdgeInsets())
// rgb get from screenshot of List in Sketch
.background(Color(red: 242/255, green: 242/255, blue: 247/255))
}
ForEach((0..<4), id: \.self) { index in
Section {
NavigationLink(destination: Text("aaa")) {
Label("Buttons", systemImage: "capsule")
}
NavigationLink(destination: Text("aaa")) {
Label("Colors", systemImage: "paintpalette")
}
NavigationLink(destination: Text("aaa")) {
Label("Controls", systemImage: "slider.horizontal.3")
}
}
}
}
.navigationBarTitle("SwiftUI")
.navigationBarTitleDisplayMode(.inline)
}
.accentColor(.accentColor)
}
}
This can make the image row to same background color with the List.
But this way is to set a fixed color, which is the RGB value of the background color of the List obtained by using the color finder in Sketch. It is not a very beautiful method. Is there a more elegant way to set the background color of the image row to be the same as the background color of the List?
It can be done making list row background transparent, like
Section() {
VStack() {
Image("HeadImage")
.resizable()
.frame(width: 50, height: 50)
Text("Test word")
.foregroundColor(Color.red)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 150)
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear) // << here !!
Tested with Xcode 13.4 / iOS 15.5

SwiftUI - How to add a pageTabView inside a scrollview with PinnedViews

I had one problem while using SwiftUI. I have implemented a sectionHeader using PinnedView which is currently scrollable all up and down, has a header area, and is LazyVStack. Below is the implementation to show the corresponding content.
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack(spacing: 0, pinnedViews: .sectionHeaders) {
Section {
Text("Header")
.frame(maxWidth: .infinity, minHeight: 300)
.background(Color.red)
}
Section {
LazyVStack {
ForEach(0...100, id: \.hashValue) { num in
Text("\(num)")
}
}
} header: {
ZStack {
Color.black.ignoresSafeArea()
Text("Section Header")
.frame(maxWidth: .infinity, minHeight: 50)
.background(Color.black)
}
}
}
}
}
}
https://imgur.com/a/xYIFzgy
However, I did not stop here and declared a TabView to enable horizontal paging scrolling in the corresponding content area as shown below, but nothing was displayed in the Contents area.
struct ContentView: View {
#State private var currentIndex = 0
var body: some View {
ScrollView {
LazyVStack(spacing: 0, pinnedViews: .sectionHeaders) {
Section {
Text("Header")
.frame(maxWidth: .infinity, minHeight: 300)
.background(Color.red)
}
Section {
TabView(selection: $currentIndex) {
LazyVStack {
ForEach(0...100, id: \.hashValue) { num in
Text("A: \(num)")
}
}
.tag(0)
LazyVStack {
ForEach(0...100, id: \.hashValue) { num in
Text("B: \(num)")
}
}
.tag(1)
}
} header: {
ZStack {
Color.black.ignoresSafeArea()
Text("Section Header")
.frame(maxWidth: .infinity, minHeight: 50)
.background(Color.black)
}
}
}
}
}
}
https://imgur.com/a/TvKVoIR
I have found through debugging that if I declare a TabView inside a ScrollView, the Contents area doesn't show anything. Can you give me an idea on how to use the stickyHeader as in the above example to make it horizontally paging?
Thanks
add These Modifiers to the TabView:
TabView {
...
}
.frame(height: UIScreen.main.bounds.height)
.tabViewStyle(.page(indexDisplayMode: .never))

SwifUI - How to remove list row background color for card style

I made a card style List row which work fine. The issue is that the list row background color remains. I can make it disappear by setting it to systemGray6 but it's not very adaptive for dark mode and there is very likely a better way of doing this.
Card View:
var body: some View {
ZStack {
Rectangle().fill(Color.white)
.cornerRadius(10).shadow(color: .gray, radius: 4)
.frame(width: UIScreen.main.bounds.width - 40)
HStack {
Image(uiImage: (UIImage(data: myVideo.thumbnailImage!) ?? UIImage(systemName: "photo"))!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100)
VStack {
Text(myVideo.title!)
.multilineTextAlignment(.center)
.font(.body)
.padding(.bottom, 10)
.frame(maxWidth: UIScreen.main.bounds.width - 40, maxHeight: .infinity, alignment: .center)
Text(myVideo.youtuber!)
.font(.subheadline)
}
}
.padding(5)
}
}
List View:
var body: some View {
NavigationView {
if myVideos.isEmpty {
Text("You have not added videos yet!")
font(.subheadline)
} else {
List() {
Section(header: Text("Not Watched")) {
ForEach(myVideos) { video in
if !video.watched {
NavigationLink(destination: MyVideoView(myVideo: video)) {
MyListRowView(myVideo: video)
}
}
}
.onDelete(perform: removeItems)
}
Section(header: Text("Watched")) {
ForEach(myVideos) { video in
if video.watched {
NavigationLink(destination: MyVideoView(myVideo: video)) {
MyListRowView(myVideo: video)
}
}
}
.onDelete(perform: removeItems)
}
.listRowBackground(Color(UIColor.systemGray6))
}
.navigationBarTitle("Videos")
.listStyle(GroupedListStyle())
}
}
}
Image: (Intended behaviour in "WATCHED" and Incorrect behaviour in "NOT WATCHED"