SwiftUI custom tab bar: how to switch views when user drags finger across tab bar buttons? - ios

I have developed a custom tab bar (ready to paste playground source), but now, in addition to user tapping the buttons to switch views, I would like to be able to swipe finger over the tab bar, and have views activated as I'm dragging over the tab bar buttons.
I tried listening on DragGesture(minimumDistance: 0) on individual buttons.
This helps in activating the view on touch down instead of touch up (which is how default Button works), but will only activate the button where user started dragging.
I assume I would somehow need to add .simultaneousGesture(DragGesture(minimumDistance: 0) to the whole tab bar, and I could then probably interpret the touch coordinates against individual button hit tests.
However, this doesn't feel like the SwiftUI way - is there an easier way to let the SwiftUI do the heavy lifting?
(Please note - in playground, I now get AttributeGraph: cycle detected through attribute 2584 notices, and the actual selection lags one tap behind the last tapped button for some reason, but it works okay in Xcode project.)

I don't know if it's a best solution, but I:
Saved a frame in global coordinates in each button
Used DragGesture(minimumDistance: 0, coordinateSpace: .global) on a tab bar, to manually hit test each button
And disabled hit testing on individual buttons with .allowsHitTesting(false), otherwise they would 'steal' the gesture.
Here's a diff of changes

Related

Button images which are part of a Container View appear before that view is supposed to animate in

I've implemented a custom dialog box in a game (showing options when the game is paused) by using a Container View in the main game's ViewController.
That Container View has a constraint to be centered vertically and I'm using that constraint to animate this custom dialog box.
The dialog box itself is an image of a wooden board on a pole with 4 buttons, each being an image I prepared. These buttons are arranged in a vertical Stack View which contains 2 horizontal Stack Views, each with 2 buttons, so they will be laid out nicely symmetrical.
All of the above is done in Interface Builder. So a segue was automatically added from the game's main ViewController to the new Pause Dialog View Controller.
In my game's main ViewController I move the Container View out of view by adding the following to my viewDidLoad():
dialogBoxYConstraint.constant -= (view.bounds.height)
Then when the user clicks on a PAUSE button which should show this dialog, the following code is running:
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut) {
self.dialogBoxYConstraint.constant += (self.view.bounds.height)
self.view.layoutIfNeeded()
}
}
So this code will bring the constraint's constant back to its original location and it will show the dialog box that I put inside the Container View.
When the user clicks on the PAUSE button all of this indeed happens and there's a nice animation with the wooden board and all 4 buttons fall into place and all buttons are clickable. Here's an image (disregard the small white buttons, these are temporary work-in-progress):
But, before that, before I click on the PAUSE button, I always see part of the buttons all the way on top. I see the lower 2 buttons completely and a bit of those above as in the following image:
As you can see, the wooden board isn't here, only the buttons, and when I do press the PAUSE button everything together correctly animates to the right place as in the 1st image.
(It's an AR app so you basically see my walls in the background, but that's irrelevant for this question).
Moreover, when the buttons are on top, they are not clickable.
Also, it doesn't matter if I change the constraint's constant to be even higher, say I do this:
dialogBoxYConstraint.constant -= (view.bounds.height + 500)
the buttons will always show at the same place.
And if I try to put this line in viewDidAppear then I can see the whole board with the buttons as in the first image for a second on a black background and then I get what you see in the 2nd image, which makes sense.
The above happens whether or not I've implemented the prepare(for segue: )
The segue itself is actually happening immediately as the main view is loaded, which is why I had to initially move it out of view.
As a test, I tried to set one button's isHidden to true in the Pause Dialog View Controller and then set it to false in the prepare(for segue: ), thinking that maybe that would do something, but the button remained hidden all the time.
(Side question: how should I perform such changes in this child View Controller only after the user presses the PAUSE button? Since the segue happens already from the start, I don't understand how to control such changes only later on by user action?)
I'm not sure what I'm doing wrong. Looks like a glitch, but, as always, I guess it's something I did.
I had assumed that moving the view's constraint should always move all of its contents together.
Anyone has any idea why the buttons are always there at the top?
I saw that there is a present(_:animated:completion:) method to present VC's. Should I be looking into this instead of animating the constraint as I did???
Posting this as an answer, as per the OP's comments...
It can be very difficult to debug layouts when UI element are "clear." Giving them background colors allows you to easily see the framing at run-time.
Also, Debug View Hierarchy can be very helpful, because you can inspect constraints (and even see hidden or out-of-bounds elements). Note that you may need to temporarily disable certain features - such as playing video or an active AR scene.

React Native press and hold, drag finger to another touchable and capture touch by that view

In my React Native app I'm trying to have a button that the user can long press, and without lifting their finger, able to interact with another view. Here is roughly what I want:
Think of it like how 3D touch/long press worked prior to iOS 13/14 (depending on place in system and device): user either 3D touched or long pressed a button, for example an app icon, and a contextual menu popped up. Then, users could, without lifting the finger, hover onto one of the buttons and release their finger, triggering the button tap.
I have complete control over my buttons, touchables, and views (even the tab bar is custom, as opposed to the illustrations I made above).
How can I achieve this? (I'm on React Native 0.63)
There may be a better solution to this but off the top of my head I would use the Gesture Responder System
https://reactnative.dev/docs/gesture-responder-system
You can have a one container view that wraps tab bar and buttons. Then listen to the onResponderMove event to decide when these buttons should appear. This may happen for example when the locationY exceeds some value.
You can also use the onResponderRelease event (again with the help of locationX and locationY parameters) to determine if the finger was released above the button.

UIView: Inheriting Touch

Example 1:
When invoking 3D Touch on app icon, you are able to make selections without lifting the finger up.
Example 2
Long pressing on a keyboard key allowing you to drag in to different selections without lifting finger up.
If the app icon is the first view and the pop up is the second view, how can I transfer touch down from first to second view?
Normally, a view loses control of the touches when the fingers leave its area. But if you set isMultipleTouchEnabled to true, it will keep control over touches if the finger leave its area. If you use a button or another UIControl you can assign actions to touchDragExit, touchUpOutside or touchDragOutside etc. to handle events outside of the control.

How do I make the elements in my UIView responsive?

My goal is to create an alert that has three text fields, one taller than the others, and an image that, when tapped, allows the user to choose a picture to replace a set default one.
After unsuccessfully searching for a library for this, I decided to create my own alert by placing a UIView off the screen and, when prompted by a button, would zoom onto the screen; it consists of all the elements I require.
When I run the application, the view pops up correctly, but none of the elements on the view are responding to touch. I've checked that isUserInteractionEnabled for everything is turned on.
What's also odd is that when I keep the view on the screen (instead of placing it some distance away on Storyboard), all the elements work fine.
I'm assuming it had something to do with the animation. I tested it with a fade in instead of a displacement, and the result was the same - the elements were unresponsive.
In order for your elements to be responsive you have to link the action of you clicking them to your view's code. You can do this in a non-programmatic manner by ctrl-clicking your element on story-views and then dragging to the view controller. Then choose action instead of outlet, and choose when the action you want will be triggered (bottom part). Then insert your code in the viewController.
So I figured it out. I used the debug view hierarchy and saw that the alert was behind the elements behind it, even though it was still being shown (for some reason). I changed the zIndex of the UIView and it worked!

custom button requirements for the ipad - what is the best approach?

I'm trying to build a set of buttons that behave slightly different than regular buttons. The requirements are:
When a user's finger slides over a button, it should highlight (a custom image changes).
When a user's finger slides off the button, it reverts the highlight.
When a user's finger slides off the button and onto a new button (without lifting the finger), a new button highlights and the old one reverts.
If a user's finger is released while on top of the button, the button triggers and the highlight stays.
I think I can implement 1, 2 and 4 using existing the existing button framework.
However, 3 is not possible. As the system continues to register touches when I drag off the button and does not register touches on the new button unless I release. Any ideas?

Resources