Clip clickable parts of image as NavigationLink (SwiftUI) - xcode

When implementing an image in a NavigationLink closure and clipping that image, the unclipped image is clickable. Because of the clipping, the images are overlapping each other (see attached screenshots).
The first screenshot shows the original size. When clipped (second screenshot) clicking on the red hatched area (shown in the first screenshot), the second NavigationLink is triggered, not the first one.
The following code produces the problem:
var body: some View {
NavigationView{
ScrollView{
VStack (spacing: 20) {
NavigationLink(destination: ImageGalleryView1()) {
Image(uiImage: downsample(imageAt: URL(string: "imageURL")!, to: CGSize(width: 500, height: 500), scale: 1))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipped()
}
NavigationLink(destination: ImageGalleryView2()) {
Image(uiImage: downsample(imageAt: URL(string: "imageURL")!, to: CGSize(width: 500, height: 500), scale: 1))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipped()
}
NavigationLink(destination: ImageGalleryView3()) {
Image(uiImage: downsample(imageAt: URL(string: "imageURL")!, to: CGSize(width: 500, height: 500), scale: 1))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipped()
}
}
}
}
I tried to clip the Image, tried to clip the NavigationLink, played around with the .frame()-properties. But with no success.
My aim is to create a VStack with three images where each of them is a NavigationLink. The clipped parts shouldn't be clickable. I want to avoid buttons or shapes in this case, if possible.

Just add .contentShape(Rectangle()) directly before .clipped(). That solved this issue for me.

I don't know your exact implemetation of downsample(imageAt:), but the implementation below gets rid of the overlapping of images:
NavigationLink(destination: ImageGalleryView1()) {
Image("sample-image")
.resizable(resizingMode: .tile)
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: 200,
alignment: .center)
}

Related

How to align bottom edge to bottom trailing of above view SwiftUI

I'm currently trying to align the person image bottom trailing to the bottom edge of the image in a SwiftUI View.
Code:-
struct AddSecondImageOnSameImage: View {
#State var image = Image(systemName: "person.circle.fill")
var body: some View {
VStack{
self.image
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
.padding(1)
.overlay(Circle()
.frame(width: 20, height:20)
.foregroundColor(Color.init(UIColor.init(displayP3Red: 255/255, green: 92/255, blue: 210/255, alpha: 1.0))),alignment: .bottomTrailing)
}
}
}
Output:-
]
Want to achieve:-
Can someone please explain to me how to align the person image bottom trailing to the bottom edge of the image in a SwiftUI View. I've tried to implement by above but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
Use ZStack instead of .overlay()
The ZStack assigns each successive child view a higher z-axis value than the one before it, meaning later children appear “on top” of earlier ones.
https://developer.apple.com/documentation/swiftui/zstack
struct AddSecondImageOnSameImage: View {
#State var image = Image(systemName: "person.circle.fill")
var body: some View {
ZStack(alignment: .bottomTrailing) {
self.image
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.clipShape(Circle())
.padding(1)
Circle()
.frame(width: 20, height:20)
.foregroundColor(Color.init(UIColor.init(displayP3Red: 255/255, green: 92/255, blue: 210/255, alpha: 1.0)))
.offset(x: -5, y: -5)
}
}
}
You may need to adjust the offset.
By using a Z-Stack and defining the frame, you can align the views inside by manipulating their frame using .infinity and setting the alignment to the desired outcome. For example in your scenario:
import SwiftUI
struct SampleView: View {
#State var image = Image(systemName: "person.circle.fill")
var body: some View {
ZStack {
self.image
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100, alignment: .center)
.clipShape(Circle())
.padding(1)
Circle()
.frame(width: 20, height:20)
.foregroundColor(.green)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
}
.frame(width: 100, height: 100)
}
}
There are several ways to implement it but setting the frame and the alignment is required. Avoid using offset constants since this would not work the same everywhere, for example, in responsive resizing.
SampleView

NavigationLink inside LazyVGrid is not displayed correctly in swiftUI macOS

I am developing a cross platform app in SwifUI. On iPhone / iPad this code works really well on MacOS instead when I insert a NavigationLink the ForecastCardView is totally cut off. When I remove the NavigationLink everything is rendered correctly.
With NavigationLink
var FullSection: some View {
LazyVGrid(columns: homeConfigurationUI.columns, alignment: .center, spacing: 20) {
NavigationLink(destination: Text("test")) {
ForecastCardView(viewModel: ForecastCardViewModel.initForTest())
}.frame(width: 300, height: 300, alignment: .center)
}
.border(Color.yellow)
.frame(minWidth: 300, idealWidth: 400, maxWidth: 600, minHeight: 0, idealHeight: 500, maxHeight: .infinity, alignment: .center)
}
Image With NavigationLink
Without NavigationLink
var FullSection: some View {
LazyVGrid(columns: homeConfigurationUI.columns, alignment: .center, spacing: 20) {
ForecastCardView(viewModel: ForecastCardViewModel.initForTest())
}
.border(Color.yellow)
.frame(minWidth: 300, idealWidth: 400, maxWidth: 600, minHeight: 0, idealHeight: 500, maxHeight: .infinity, alignment: .center)
}
Image Without NavigationLink
Everything is inside a ScrollView, I tried to insert a List a VStack, but no results. I tried to put a static frame on every single component, but nothing to do.
You have to set buttonStyle to PlainButtonStyle():
var FullSection: some View {
LazyVGrid(columns: homeConfigurationUI.columns, alignment: .center, spacing: 20) {
NavigationLink(destination: Text("test")) {
ForecastCardView(viewModel: ForecastCardViewModel.initForTest())
}
.buttonStyle(PlainButtonStyle()) // <— HERE
.frame(width: 300, height: 300, alignment: .center)
}
.border(Color.yellow)
.frame(minWidth: 300, idealWidth: 400, maxWidth: 600, minHeight: 0, idealHeight: 500, maxHeight: .infinity, alignment: .center)
}

macOS SwiftUI Button "Active Area" Width of Parent View

For a macOS app, I like to extend a button's width across it's parent view using SwiftUI. I tried this:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button(action: {print("TEST")}) {
Image(systemName: "clock")
.frame(width: 25, height: 25, alignment: .center)
Text("This is a button")
.frame(minWidth: 200, idealWidth: 200, maxWidth: .infinity, minHeight: 25, idealHeight: 25, maxHeight: 25, alignment: .leading)
Image(systemName: "chevron.left")
.frame(width: 25, height: 25, alignment: .center)
}
.buttonStyle(PlainButtonStyle())
}
}
}
and it will yield a result that looks quite what I am searching, but if you click in the area marked in red in the following screenshot of the app
the buttons action does not fire. Also the area between the first image and the text does not fire the action. I also tried to implement a custom ButtonStyle but with no luck. How can I achieve a button that extends over the full width of the parent view?
You can try using .contentShape(Rectangle()):
Button(action: { print("TEST") }) {
HStack {
// ...
}
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}

SwiftUI Scrollview contents are outside scrollable area

I'm having trouble keeping the contents of a ScrollView contained within the scrollview:
Initially, I want to display letters A and B in the ScrollView and have the user scroll to see additional letters. However, even though I've constrained the parent VStack to a frame with height of 120, you can also see the letter C which is outside of the ScrollView (as indicated by the blue background). Here's the code:
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .center , spacing: 5) {
Text("Letter").font(.tableHeader).frame(width: 75, height: 30, alignment: .center)
} // HStack
.frame(width: 195, height: 50, alignment: .center)
.background(Color.green)
VStack(alignment: .leading, spacing: 0) {
GeometryReader { outsideProxy in
ScrollView (.vertical, showsIndicators: false) {
ZStack(alignment: .top) {
GeometryReader { insideProxy in
Color.clear
// get offset
} // GeometryReader inside
VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .center, spacing: 5){
Text("A").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("B").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("C").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("D").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
} // VStack
} // ZStack
} // Scrollview
} // GeometryReader outside
.background(Color.blue)
} // VStack
.frame(width: 195, height: 120, alignment: .leading)
} // VStack
}
The full code requires that use of GeometryReader (and consequently, the ZStack) which is why I've left those items in the sample above.
What is the best way to solve this issue? Open to any improvements for coding the above layout. Keep in mind that ultimately, I want to the user to be able to click on A, B, C, or D to be taken to the next view in the navigation stack.
Make it clipped
ScrollView (.vertical, showsIndicators: false) {
ZStack(alignment: .top) {
GeometryReader { insideProxy in
Color.clear
// get offset
} // GeometryReader inside
VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .center, spacing: 5){
Text("A").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("B").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("C").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("D").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
} // VStack
} // ZStack
} // Scrollview
.clipped() // << here !!

How to fix gray bar under List in NavigationView in TabView?

So I am having a problem where just below my list I am having a gray bar that appears and when I click on a cell to go to the other view there is an even bigger gray bar. Here is the code for the List View:
VStack{
NavigationView{
VStack{
List{
ForEach(answersArray.indices, id: \.self) { day in
NavigationLink(destination: DetailView(questions: self.answersArray[day].questions, answers: self.answersArray[day].answers, date: self.answersArray[day].dayDone.toString(dateFormat: "MM/dd/yy"))) {
HStack{
VStack{
ForEach(self.answersArray[day].questions.indices) { question in
//Text(question)
Text(self.answersArray[day].questions[question])
.lineLimit(1)
.frame(width: 250, height: 30, alignment: .leading)
// .padding(.vertical, 5)
//.padding(.bottom)
}
}
// .truncationMode(.tail)
//.frame(minWidth: 0, idealWidth: 200, maxWidth: .infinity, minHeight: 0, idealHeight: 50, maxHeight: 75, alignment: .leading)
Text(self.answersArray[day].dayDone.toString(dateFormat: "MM/dd/yy"))
.frame(minWidth: 0, idealWidth: 50, maxWidth: .infinity, minHeight: 0, idealHeight: 20, maxHeight: 20, alignment: .trailing)
}
.padding(.horizontal, 5)
}
//.frame(minWidth: 0, idealWidth: 250, maxWidth: .infinity, minHeight: 0, idealHeight: 50, maxHeight: 100, alignment: .center)
}
//.colorMultiply(Color("Background Green"))
// .listRowBackground(Color("Background Green"))
//.listRowBackground(Color("Background Green"))
Button(action: {
self.answersArray.append(DailyAnswer(questions: ["Question 1", "Question 2"], answers: ["I am happy", "I am sad"], dayDone: Date().addingTimeInterval(100000), lastWeek: false))
self.answersArray.append(DailyAnswer(questions: ["Question 1", "Question 2"], answers: ["I am happy", "I am sad"], dayDone: Date(), lastWeek: false))
self.answersArray.append(DailyAnswer(questions: ["Question 1", "Question 2"], answers: ["I am happy", "I am sad"], dayDone: Date(), lastWeek: false))
}) {
Text("Create cells")
}
}
.navigationBarTitle("Title")
//.colorMultiply(Color("Background Green"))
}
}
.accentColor(Color("My Gray"))
}
And here is the code for the separate view:
import SwiftUI
struct DetailView: View {
var questions : [String];
var answers : [String];
var date : String;
var body: some View {
//Color("Background Green")
//.edgesIgnoringSafeArea(.all)
NavigationView{
ZStack{
Color("Background Green")
.edgesIgnoringSafeArea(.all)
ScrollView{
VStack{
ForEach(questions.indices) { pair in
Text(self.questions[pair])
.font(.title)
.padding()
Text(self.answers[pair])
.padding()
.font(.body)
.frame(minWidth: 0, idealWidth: 250, maxWidth: 350, minHeight: 150, idealHeight: 200, maxHeight: .infinity, alignment: .topLeading)
.background(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 1)
)
}
}
.padding(.top)
.navigationBarTitle("\(date)", displayMode: .inline)
}
}
}
}
}
Also I know this appears very similar to This Question, but when I implemented the solution on that page it would just change the color of the top Navigation Bar Title and not the gray on the bottom.
Also, this is where I am styling both the Tab Bar and the Navigation Bar
init() {
UINavigationBar.appearance().backgroundColor = UIColor(named: "Background Green")
UITabBar.appearance().isTranslucent = false
UITabBar.appearance().barTintColor = UIColor.black
}
I see there are many NavigationView in the stack:
VStack{
NavigationView{ // << here in first snapshot
VStack{
and
NavigationView{ // << here in second snapshot
ZStack{
and as there no complete code provided there are possible others as well...
Here is a thumb-rule: there must be only one root NavigationView in one view hierarchy chain. So make sure you place one NavigationView as tab-item root view of Past Journals tab (again, assumption based only on provided code).
Setting UITabBar.appearance().isTranslucent to false will break constraints that NavigationView is relying on.
To fix this, replace these lines of code:
UITabBar.appearance().isTranslucent = false
UITabBar.appearance().barTintColor = UIColor.black
With these:
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithOpaqueBackground()
tabBarAppearance.backgroundColor = UIColor.black
UITabBar.appearance().standardAppearance = tabBarAppearance

Resources