I've a set of buttons which I animate in expand-collapse fashion, more like a drawer open and close action. I simply vary the X positions of these buttons to achieve this and it works fine.
While designing this layout in interface builder, I've set the constraints for all these buttons in their 'drawer-closed' state.
Problem arises when the drawer is left open and some other view on the screen gets updated.
I understand that viewDidLayoutSubviews() gets called at this instance.
Because of this, the drawer menu which was left open closes abruptly messing up with the 'drawer close' animation and the buttons don't get the expected view as in their 'drawer close' state.
I suppose the constraints of the buttons would be getting re-initialized when the viewDidLayoutSubviews() got called.
Now coming to my question - how do we set/manage constraints for views
which are going to move runtime because of animations?
I'm a beginner in iOS app development and I did go through questions asked on managing constraints programmatically or viewDidLayoutSubviews etc. but I couldn't find a standard way to follow in the situation I've encountered.
Instead of changing the frame, you could animate the change of constraints.
Short preview of how to do that:
yourConstraint.constant = newValue
UIView.animate(withDuration: animationDuration) {
yourView.layoutIfNeeded()
}
More information about this can be found here.
Related
Sample Project
This is a sample project that showing the issue. It's storyboard based, but method of building interface doesn't matter. It's UIViewController with UIScrollView for entire screen and 128 pts height view that is on top of this UIScrollView.
Inside scroll view there is an UIView that has 2000 pts height and UIButton in the center.
Initial State
After light scroll
At the bottom of UIScrollView
Link here: https://github.com/JakubMazur/UITestsDemo
Problem
I'm trying to tap this green button with XCUITest using app.buttons["Tap Me!"].tap()
XCUITest get identifiers from elements on screen for entire scroll view that works fine.
According to this reply on a thread on Apple Developer Forum written by Apple Framework Engineer I shouldn't scroll manually to get to the button and yes, this is partially true.
What is happening when code from (1) is executed is that button is scrolled just enough to be visible on screen but it's still not hittable, because other (purple view) is on top of UIScrollView
What is working
If I run a test written like this:
func testThatDoWorkButItsSlow() {
app.scrollViews.firstMatch.swipeUp()
app.buttons[buttonLabel].tap()
}
that is scrolling up and then looks for a button this will work, but it's slow and so inaccurate that is hardly usable.
What I cannot do
Disabling userInteractions on purple view. In real example I still need touches for this (purple) view.
Questions
Is there a way to use precise scrolling in XCTest for this case?
Or is there a way to set contentOffset scrollview to other value that will make this button more centered on a screen compared to action of tap()?
Or there is a way to fast scroll to the bottom (without animations) and maybe moving only up for each element?
My recommendation here would be to use the XCUICoordinate.press(forDuration:thenDragTo:) method to scroll.
https://developer.apple.com/documentation/xctest/xcuicoordinate/1615003-press
You can create a XCUICoordinate for the yellow view, then drag it slightly upwards to expose the button and make it hittable.
In most cases, the automatic scroll should work, but it seems like in this case a manual scroll/drag is necessary.
The UI Testing should replicate human interactions. You cannot expect from a human being to scroll "153px", you can just expect to "scroll until".
You can try something like :
while (!app.buttons["Tap Me!"].isHittable) {
app.swipeUp()
}
NB: You may also want to add a condition to leave the while loop if you can't find the button after a reasonable amount of attempts
I make a Today Notification Widget and I use AutoLayout.
When I press a number button, whole view moves to left. I cannot figure out why.
Why view moves left when I tap a button? Is it from AutoLayout or is a general problem?
Before touch
After touch
Image1
Image2
Image3
This appears to be a problem with your implementation. There's no reason for this to happen unless you are changing some positions or autolayout constraints when tapping a button. also, you may want to take a look at widgetMarginInsetsForProposedMarginInsets function and that you are already inheriting from NCWidgetProviding:
class MainViewController: UIViewController, NCWidgetProviding {
func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
return UIEdgeInsetsZero
}
}
...anyway, your question is really broad the way it's stated here, maybe you will want to add some source for further insight.
Hope it helps!
UPDATE:
The problem is that when you input some text, the margins of that view get's bigger, and that's doing something weird because there is some constraint that you are missing and that is related to the view (your UITextField/UILabel) that is giving value to your right side leading margin constraint.
If you isolate completely, right part from Left, maybe putting both of them in separated views. You should be able to resolve this issue as any changes inside of the left view, remains in the left view.
Other way is to remove all constrains and start over, putting your constrains manually, do not let xcode do that job for you
I have a container view showing a sidebar which is pinned to the main views leading edge. The sidebar is initial visible which is fine for iPads however I would like it to be hide initially for smaller devices. To do that I need to set the side bars trailing edge constraint to be (0 - its own width)
As far as I can see this is not possible in the interface builder. I have tried to do it in the viewDidLoad, checking if the device is an iPhone before doing self.sidebarX.constant = -self.sidebar.frame.width. This fails because viewDidLoad has not set up the views yet so the width is wrong. I also tried to do it in viewDidLayoutSubviews however the user sees the sidebar disappearing which isn't nice. I am sure there must be a common way of dealing with this?
I finally worked it out. viewDidLayoutSubviews was the correct place to be doing this. At first when I tried it, it was showing the sidebar slide away as the view controller loaded. It turns out this is because I was calling my closeSidebar method which animates the side bar moving off screen. Changing this so it just sets the view off screen and adding a check to ensure this only done once on first load (as viewDidLayoutSubviews is called multiple times) does the job of hiding the the sidebar for certain devices without anyone seeing it happen.
You can set this using xcode adaptative layout:
You can set the different position for all different screen types here, changing the constraints, positions, sizes to each different type you need.
You can install the layout of one object in different screen types using the dialog below:
Have a look in this 2 parts tutorial from raywenderlich part 1 part 2
I am using ios7 and I am enabled iAds with "self.canDisplayBannerAds"
The iAd basically reshapes the view when it shifts in or when it shifts out. However, When the banner shifts, my view Moves off screen!
In my story board, I put my view outside of the screen. And when the user presses a button, I set its frame property so that it exists inside of the screen. But when the iAd shifts in or out, the View travels back to its original place in the story board.
How can I prevent my view from sliding off the screen?
Update:
I tried simply adding a view programatically. and placed it in the same place as the one I made in Interface Builder. Initially they both move offscreen and on screen when commanded. however, once the iAd shifts in and out, the view I made in interface builder will slide out. The view I made programatically will stay put.
My best guess at this point was Auto layout or something to do with constraints. The IB view was sliding out rather than simply disappearing and It definitely was not using UIView's animation methods. Thus the only other thing that I could think of that could cause animations to happen are constrains. (eg rotating a view with view's that have constriaints will automatically resize in a smooth fashion.
so I took a look at the constraints of the view by calling the constraints method in an NSLog. It printed out a huge list of constraints. I did not add these explicitly, which means they were added automatically.
There may be a connection between these constraints. The question now is, why is the iAD banner triggering the view to basically spring back to its former place?
When the banner shifts in and out, it will layout the subviews. Views without any constraints are given automatic constraints. These constraints place it in the same location as it's place in the xib.
You can try changing the constraints.
I want to create a footer fixed to the bottom of the viewport in an iOS app I'm developing (it's my first one, I come from a web dev background). However I'm having difficulty finding information on how to go about this.
What I have in mind is essentially just 3 rectangular buttons next to each other that are just constantly fixed to the bottom of the viewport. I tried dragging a toolbar into my scene as a starting point, but in the interface builder it's placing itself under my table cell, rather than fixing to the bottom. I feel like this is probably easier to do programmatically, but I just don't know where to start.
If someone could at the very least guide me in the right direction I'd really appreciate it. Thank you.
Okay... create/add custom UIView on your main view (self.view)
and set this custom view's frame = (0, 0, 320 , self.view.frame.size.height - 50) /// change value 50 to as per your requirement.
and add all of then UIControls such like textField, tableVie, button ...etc.. on this custom view.
Don't use a UITableViewController. Use a UIViewController, then add a table view and a toolbar. You'll need to implement the UITableViewDelegate protocol in code and then connect the delegate in interface builder.