I've read a lot here about navigation in SwiftUI and tried a couple of things, but nothing is working as desired.
I am developing a watch app in which I have to use two TextField. I only want to move on next screen when both field are not empty. Here is my code.
#State var movetoNextScreen: Int? = nil
NavigationLink(destination: WaterTemprature(), tag: 1,
selection: $movetoNextScreen) {
ZStack {
Text("Next")
.font(.system(size: 12, weight: .semibold))
.frame(alignment: .center)
Button(action: nextBtnPressed) {
if !check_minute_and_seconds_are_empty() {
// move to next screen.
movetoNextScreen = 1
}
}.opacity(0)
}
}
But when I pressed the navigation link, Button action did not call and control move to next screen. So I want to find any method that can first check the textfields and then move to next view.
Try in opposite direction - put button in front (with needed redesign) and place link in background with programmatic activation, like
Button(action: {
if !check_minute_and_seconds_are_empty() {
movetoNextScreen = 1 // << activate link
}
}) {
Text("Next")
.font(.system(size: 12, weight: .semibold))
.frame(alignment: .center)
}
.background(
NavigationLink(destination: WaterTemprature(), tag: 1,
selection: $movetoNextScreen) { EmptyView() }
)
Related
I have a Button with an overlay, but it is not clickable. If I take the overlay off, it becomes clickable. How can I make it work with the overlay?
Button {
UIImpactFeedbackGenerator(style: .light).impactOccurred()
UIPasteboard.general.string = viewModel.promoCode?.code ?? ""
} label: {
HStack {
Text(viewModel.promoCode?.code ?? "")
.foregroundColor(Color(UIColor.uStadium.primary))
.font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
.padding(.leading, 20)
Spacer()
Image("copyIcon")
.foregroundColor(Color(UIColor.uStadium.primary))
.padding(.trailing, 20)
}
}
.overlay (
RoundedRectangle(cornerRadius: 8)
.stroke(Color(UIColor.uStadium.primary), style: StrokeStyle(lineWidth: 2, dash: [10]))
.frame(height: 42)
.background(Color(UIColor.uStadium.primary.withAlphaComponent(0.2)))
)
.frame(height: 42)
.cornerRadius(8)
Another solution is adding .allowsHitTesting(false) modifier at the view inside of the overlay.
The reason why touch is not working is overlay modifier layers a secondary view in front of current view. So when user tap the button they were actually tapping the overlayed view.
The allowsHitTesting(false) stop a view from receiving any kind of taps, and any taps automatically continue through the view on to whatever is behind it.
Simple example:
struct ContentView: View {
var body: some View {
Button {
print("dd")
} label: {
Image(systemName: "house").resizable().frame(width: 50, height: 50)
}
.overlay (
RoundedRectangle(cornerRadius: 8).allowsHitTesting(false)
)
}
}
and touch works!
I have a for each list that displays items that I call snippets ("Kinda like a tweet"). Within these snippets is a button that allows the user to open up the comments section via a full-screen sheet. The problem is that when I scroll on the comment sheet, it automatically closes. I think the reason is due to my pagination functionality.
Basically I have a updateData() function that will be called to fetch the next batch of firestore documents. It is activated when the last item on the list is reached. It appears that when I scroll on the comment section sheet, it also activates the updateData() function in the view behind it. This resets everything and closes the comment section sheet.
This only happens for the last item in the list and not for any item before it.
Here is the main view
ForEach(self.data){i in
ZStack{
if i.name == ""{
ProgressView()
}
else {
//Track end of list. This is where I get the last item in the list and update data if scrolling continues.
if self.data.last?.id == i.id{
VStack{
GeometryReader{g in
VStack{
ZStack{
FeedSnippetsRow(refSnippet: i, refreshData: self.refresh)
.environmentObject(viewedModel)
}
.padding(EdgeInsets.init(top: 0, leading: 15, bottom: 0, trailing: 15))
Divider().foregroundColor(doppleGray)
}
.onAppear {
self.time = Timer.publish(every: 0.1, on: .main, in: .tracking).autoconnect()
}
.onReceive(self.time) { (_) in
if (g.frame(in: .global).minY) < UIScreen.main.bounds.height - 80 {
self.updateData()
print("updated")
self.time.upstream.connect().cancel()
}
}
}
}
.frame(height: 1000)
} else {
//Original data
VStack{
ZStack{
FeedSnippetsRow(refSnippet: i, refreshData: self.refresh)
.environmentObject(viewedModel)
}
.padding(EdgeInsets.init(top: 0, leading: 15, bottom: 0, trailing: 15))
Divider().foregroundColor(doppleGray)
}
}
}
}
}
This is the FeedSnippetsRow:
Here let's focus on the comment button that opens the comments sheet via a full-screen sheet.
HStack{
Button(action:{
self.showCommentsView.toggle()
}){
Image(systemName: "message")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 18, height: 18)
.foregroundColor(.primary)
}
.fullScreenCover(isPresented: $showCommentsView){
Comments(snippetId: refSnippet.snippetId, didComment: $didComment, commentCount: $commentCount)
}
What should I do so that the last item in the list, does not cause the comment sheet to close when scrolling in the comment section?
I'm working on my first SwiftUI app, and in it would like to display a List of categories, with a badge indicating the number of items in that category. The title of the category would be on the left, and the badge would be right-aligned on the row. The list would consist of NavigationLinks so that tapping on one would drill further down into the view hierarchy. The code I've written to render the NavigationLinks looks like this:
List {
ForEach(myItems.indices) { categoryIndex in
let category = categories[categoryIndex]
let title = category.title
let fetchReq = FetchRequest<MyEntity>(entity: MyEntity(),
animation: .default)
NavigationLink(destination: MyItemView()) {
HStack(alignment: .center) {
Text(title)
Spacer()
ZStack {
Circle()
.foregroundColor(.gray)
Text("\(myItemsDict[category]?.count ?? 0)")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
}
}
}
}
While it does render a functional NavigationLink, the badge is not displayed right-aligned, as I had hoped. Instead, it looks like this:
I know I'm getting hung up on something in my HStack, but am not sure what. How do I get it so that the category title Text takes up the majority of the row, with the badge right-aligned in the row?
SwiftUI doesn't know how big your Circle should be, so the Spacer doesn't do anything. You should set a fixed frame for it.
struct ContentView: View {
var body: some View {
List {
ForEach(0..<2) { categoryIndex in
let title = "Logins"
NavigationLink(destination: Text("Hi")) {
HStack(alignment: .center) {
Text(title)
Spacer()
ZStack {
Circle()
.foregroundColor(.gray)
.frame(width: 25, height: 25) // here!
Text("5")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
}
}
}
}
}
}
Result:
Thanks in advance for any advice you can give. I have a custom dropdown menu that I have built in SwiftUI:
struct PhoneTypeDropdown: View {
let phoneTypes:[PhoneType] = [.Cell, .Work, .Landline]
#State var isExpanded: Bool
var body : some View {
VStack (spacing: 0) {
HStack {
Text("select type").font(.footnote)
Spacer()
Image(systemName: Constants.Design.Image.IconString.ChevronDown)
.resizable()
.frame(width: Constants.Design.Measurement.DropDownIconWidth,
height: Constants.Design.Measurement.DropDownIconHeight)
}
.padding(Constants.Design.Measurement.PadMin)
.background(Constants.Design.Colors.LightGrey)
.cornerRadius(Constants.Design.Measurement.CornerRadius)
.onTapGesture {
self.isExpanded.toggle()
}
if isExpanded {
VStack (spacing: 0){
ForEach(0 ..< phoneTypes.count) { i in
Button(self.phoneTypes[i].description, action: {
print("button tapped")
self.isExpanded.toggle()
})
.buttonStyle(DropDownButtonStyle())
if i != self.phoneTypes.count - 1 {
Divider()
.padding(.vertical, 0)
.padding(.horizontal, Constants.Design.Measurement.Pad)
.foregroundColor(Constants.Design.Colors.DarkGrey)
}
}
}.background(Constants.Design.Colors.LightGrey)
.cornerRadius(Constants.Design.Measurement.CornerRadiusMin)
.padding(.top, Constants.Design.Measurement.PadMin)
}
}.frame(width: Constants.Design.Measurement.PhoneTypeFieldWidth)
.cornerRadius(Constants.Design.Measurement.CornerRadius)
}
}
When I went to utilize this in my view, I had planned on using it as an overlay like this:
VStack(spacing: 0) {
ProfileField(text: phone1,
label: Constants.Content.PrimaryPhone,
position: .Justified)
.overlay(
PhoneTypeDropdown(isExpanded: expandDropdown1)
.padding(.top, 32) //FIXME: Fix this hard-coded value.
.padding(.trailing, Constants.Design.Measurement.PadMax),
alignment: .topTrailing
)
}
However, although the above code will trigger on clicking and expand the dropdown box, tapping any of the Button objects inside the dropdown box does nothing. I then tried to implement the dropdown box using a ZStack like this:
ZStack(alignment: .topTrailing) {
ProfileField(text: phone1,
label: Constants.Content.PrimaryPhone,
position: .Justified)
PhoneTypeDropdown(isExpanded: expandDropdown1)
.padding(.top, 32) //FIXME: Fix this hard-coded value.
.padding(.trailing, Constants.Design.Measurement.PadMax)
}
The dropdown box worked beautifully, expanding and collapsing as expected. However, now, when it expands, it pushes down the rest of the form instead of laying on top of the form as desired.
My question then is this: what would cause my button action to fire correctly when using the dropdown object in a ZStack as opposed to incorporating it in an overlay on an view in a VStack?
I have 2 buttons in an H/VStack. Both of them contain some text, in my example "Play" and "Pause". I would like to have that both buttons have the same width (and height) determined by the largest button. I have found some answers right here at SO but I can't get this code working unfortunately.
The following code illustrates the question:
import SwiftUI
struct ButtonsView: View {
var body: some View {
VStack {
Button(action: { print("PLAY tapped") }){
Text("Play")
}
Button(action: { print("PAUSE tapped") }) {
Text("Pause")
}
}
}
}
struct ButtonsView_Previews: PreviewProvider {
static var previews: some View {
ButtonsView()
}
}
The tvOS preview from Xcode shows the problem:
I would be thankful for an explanation for newbies 🙂
Here is run-time based approach without hard-coding. The idea is to detect max width of available buttons during drawing and apply it to other buttons on next update cycle (anyway it appears fluently and invisible for user).
Tested with Xcode 11.4 / tvOS 13.4
Required: Simulator or Device for testing, due to used run-time dispatched update
struct ButtonsView: View {
#State private var maxWidth: CGFloat = .zero
var body: some View {
VStack {
Button(action: { print("PLAY tapped") }){
Text("Play")
.background(rectReader($maxWidth))
.frame(minWidth: maxWidth)
}.id(maxWidth) // !! to rebuild button (tvOS specific)
Button(action: { print("PAUSE tapped") }) {
Text("Pause Long Demo")
.background(rectReader($maxWidth))
.frame(minWidth: maxWidth)
}.id(maxWidth) // !! to rebuild button (tvOS specific)
}
}
// helper reader of view intrinsic width
private func rectReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { gp -> Color in
DispatchQueue.main.async {
binding.wrappedValue = max(binding.wrappedValue, gp.frame(in: .local).width)
}
return Color.clear
}
}
}
You can implement the second custom layout example in the WWDC 2022 talk https://developer.apple.com/videos/play/wwdc2022/10056/ titled "Compose custom layouts with SwiftUI" which, if I understand the question, specifically solves it, for an arbitrary number of buttons/subviews. The example starts at the 7:50 mark.
after reading hit and trial implementing SO solns etc finally resolved this issue posting so that newbies as well as intermediate can benefit
paste it and obtain equal size(square) views
VStack(alignment: .center){
HStack(alignment:.center,spacing:0)
{
Button(action: {}, label: {
Text("Button one")
.padding(35)
.foregroundColor(.white)
.font(.system(size: 12))
.background(Color.green)
.frame(maxWidth:.infinity,maxHeight: .infinity)
.multilineTextAlignment(.center)
.cornerRadius(6)
}).background(Color.green)
.cornerRadius(6)
.padding()
Button(action: {}, label: {
Text("Button two")
.padding(35)
.foregroundColor(.white)
.font(.system(size: 12))
.frame(maxWidth:.infinity,maxHeight: .infinity)
.background(Color.green)
.multilineTextAlignment(.center)
}) .background(Color.green)
.buttonBorderShape(.roundedRectangle(radius: 8))
.cornerRadius(6)
.padding()
}.fixedSize(horizontal: false, vertical: true)
Add as many as buttons inside it. You can adjust it for VStack by adding only one button in hstack and add another button in another Hstack. I gave a general soln for both VStack and Hstack. You can also adjust padding of button as .padding(.leading,5) .padding(.top,5) .padding(.bottom,5) .padding(.trailing,5) to adjust the gaps between buttons
I think the best solution is to use GeometryReader, which resizes the width of the content of the Button. However, you need to check that you set a width of the Wrapper around the GeometryReader, because otherwise it would try to use the full screen width. (depends where you use that view, or if it is your primary view)
VStack
{
GeometryReader {Â geo in
VStack
{
Button(action: { print("PLAY tapped") }){
Text("Play")
.frame(width: geo.size.width)
}
.border(Color.blue)
Button(action: { print("Pause tapped") }){
Text("PAUSE")
.frame(width: geo.size.width)
}
.border(Color.blue)
}
}
}
.frame(width: 100)
.border(Color.yellow)
... which will look like that.
What happens if you put a Spacer() right after the Text("Play")? I think that might stretch out the 'Play' button.
Or maybe before and after Text("Play").