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())
}
Related
I am attempting my first steps using matchedgeometry and have followed some tutorials to get a simple view to expand when selected.I have two files, a card view and a content view. I would like to place all of my card views in a horizontal scrollview.
import SwiftUI
struct smallCard: View {
#State private var flag: Bool = true
#Namespace var nspace
var body: some View {
if flag {
VStack{
Image("chemex.jpg")
.resizable()
.matchedGeometryEffect(id: "image", in: nspace)
.scaledToFill()
Spacer()
Button("CHEMEX") { withAnimation(.default) { flag.toggle() } }
.matchedGeometryEffect(id: "text", in: nspace)
Spacer()
.frame(height:8)
}
.matchedGeometryEffect(id: "geoeffect1", in: nspace)
.frame(width: 100, height: 100, alignment: .center)
.background(Color.white)
.cornerRadius(20)
.shadow(color: .gray, radius: 9, x: 0, y: 9)
}
if !flag {
VStack{
Image("chemex.jpg")
.resizable()
.matchedGeometryEffect(id: "image", in: nspace)
.scaledToFit()
Spacer()
Button("CHEMEX") { withAnimation(.default) { flag.toggle() } }
.matchedGeometryEffect(id: "text", in: nspace)
Spacer()
.frame(height:20)
}
.matchedGeometryEffect(id: "geoeffect1", in: nspace)
.layoutPriority(1)
.frame(width: 300, height: 600, alignment: .center)
.background(Color.white)
.cornerRadius(20)
.shadow(color: .gray, radius: 9, x: 0, y: 9)
}
}
I the have my main content view:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack{
HStack{
Text("My App")
.font(.title)
.padding(.leading, 25)
Spacer()
}
ScrollView(.horizontal){
HStack{
smallCard()
.padding(.horizontal)
smallCard()
.padding(.horizontal)
smallCard()
.padding(.horizontal)
}
}
}
.frame(alignment: .center)
}
This currently works for clicking on the small card and the matched geometry animates this to the larger card. The issue I have is that this changes the height of my scrollview and as such this moves everything else such as my title up and out of view.
Effectively what I would like to achieve is like a popover view, so that this detail view is on top of all of the elements in my content view, but whilst using matchedgeometry (or an alternative solution) to animate nicely between the views.
Is this possible? I am at the point where I do not know if there is a solution to this.
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)
}
My app has a SwiftUI View that contains both a TextEditor to collect a log entry and a List containing past entries.
When the text editor has a non-empty string, a button is presented to process the entry, add it to the data populating the list, and reset the TextEditor. Presently the button appears suddenly whenever text is typed, and I would love to animate that more smoothly, perhaps by fading in the opacity or scaling up the button. However, I can't figure out where to enter that animation.
#State var newEntryString = ""
var body: some View {
NavigationView{
VStack{
Text("Create a new log entry.")
.padding()
TextEditor(text: $newEntryString)
.cornerRadius(3.0)
.padding()
.border(Color.gray, width: 1)
.frame(minWidth: 0, idealWidth: 100, maxWidth: .infinity, minHeight: 0, idealHeight: 100, maxHeight: 150, alignment: .center)
HStack {
Spacer()
if !newEntryString.isEmpty{
Button(action: {
addEntry()
}) {
Text("Add Entry")
}.buttonStyle(JournalButtonStyle())
.animation(Animation.default.speed(1))
}
}
Divider()
List {
ForEach(entrys) { item in
Text(item.timestamp!, formatter: entryFormatter).font(.footnote)
.foregroundColor(.gray)
.padding(.bottom, -10)
Text((item.entryString!))
}
.onDelete(perform: deleteEntrys)
}.onTapGesture {
UIApplication.shared.endEditing()
}
Use animation on container, like
HStack {
Spacer()
if !newEntryString.isEmpty{
Button(action: {
addEntry()
}) {
Text("Add Entry")
}.buttonStyle(JournalButtonStyle())
}
}.animation(Animation.default.speed(1)) // << here !!
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 !!
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