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

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.

Related

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

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

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!

Loading Process- Different UILabels

In my app I have a UIButton and when you tap it I have a UIActivityIndicatorView and I want to add diffrent UI Labels as it loads. Its actually not a real loading process its just for looks. How would I go about doing this?
Step 1:
Create a custom UIView (u can xibs if you want). That view should have second UIView as a container in it. When you init the view set it to be the same size as the viewController you are putting it in. Its background color is transparent (or black with a lower alpha if you want to have the greyed out effect). Set the container to be the actual size you want it to appear.
Step 2:
Put a UIActivityIndicatorView and a UILabel in the container. Lay it out how you like.
Step 3:
Write a label updating method that uses a timer. Either performSelector:afterDelay: or dispatch_after. Have this method set the label text, wait a period of time, change then text, wait a period of time, change it again and so on.
Step 4:
Write showIndicator() and hideIndicator() methods. The view and the container's hidden property should be set to true. When showIndicator() is called set the hidden to false and call the label updating method from step 3. When you've reached the end of all your label changes then set everything back to hidden = true
It should work like this. The view controller loads up and adds this view. The view takes up the entire screen but you cant see anything because it is hidden by default. The user hits the button and showIndicator() method is called. The view become visible. Although it takes up the entire screen it appears to be a small overlay because the background is transparent (or back with an alpha for the greyed out look) and the container has a smaller frame and a solid color. The delayed methods are also called now and it changes the label, waits, changes it and so on. When you reach the end of the sequence hideIndicator() is called and all views are set back to hidden = true. Good Luck!!!

iOS View Controller is only accepting touches in a certain portion of the window

I have a relatively simple dialog/popup style UIViewController that isn't behaving correctly. I have many others just like it (though each with a unique button layout) that work just fine. For some reason that I cannot figure out, this controller is only accepting touches within (approximately) the green shaded area.
Note that none of these colors are the actual colors, just placeholders. Same goes for the text.
Tapping in the "Search..." text field does nothing. Tapping on the "Cancel" button does nothing. No UI reaction whatsoever.
The tableview will scroll just fine (there are over 100 rows) if it's touched inside (approximately) the green region. Touching the tableview below the green results in no response. Same thing for the "Cats" button. It reacts when touched in approximately the top half, but nothing in the bottom half.
I've banged my head against the wall for the requisite "several hours" and am getting nowhere.
It seems that part of your view controller's view is overlapped with some "harmful" view/views. Maybe this view is created programmatically and is not visible in IB. You should start with checking your views hierarchy. Put breakpoint in your view controller's code. Somewhere, where you are sure all views (normal and harmful) are already created and type in debug area
po [self.view recursiveDescription]
you should receive something like
Now try to find harmful views. Temporarily delete some "normal" views could be a good idea.
I found my solution. I have a method that launches a dialog controller given the dialog to launch and the parent (up until this point, always a full-screen view controller). The parent gets darkened and the dialog gets launched on the center of the screen.
This one wasn't working because I was launching it from an existing dialog; I was telling the dialog launcher that the new dialog's parent was the current dialog, not the whole screen. This means that the new dialog (which is bigger than the first one) launches at the correct size, but only accepts input inside the area of the "parent" dialog, which is the smaller one underneath it.
Fix: Launch the new dialog using the current dialog's parent (which is full screen) and it works fine. The green zone on my image is the approximate size of the dialog that was being used as the parent, which is why input was only being accepted in that area.

For iOS how do I create a draggable button within a button?

In xCode I'm making an iPad app, and have created a series of counters in UIimageViews that drag around the screen when you touch them. All good so far.
But now I want to have another smaller hidden button just above each counter that is disabled, so that when you tap on the counter the smaller button can appear and can perform an action (such as 'return to starting position' or 'hide'.
I'm just not sure where to go with this one now. Any ideas or hints would be much appreciated!
So you create the buttons and set their frames and add them as sub views to your view. From that point on you can hide them by making their alpha = 0.0, have them appear disabled by setting alpha to say 0.5 (set enabled to NO), and make them live with alpha=1.0. You can animate frame and alpha changes. For convenience you can set their tag properties to a value related to the counter so you can easily get them using viewWithTag:.

Resources