Use stride to rotate object - swift

I want to use the stride syntax to rotate a object in stride. The box should go through 3 iterations and rotate 270 degrees. I don’t believe everything some of my code below matches the Syntax by word. Just a FYI.
Var box = UIView()
Override func viewdidload(){
Super.viewdidload()
For rotate in Stride(from : 0, to: 3, by +1 ) {
box.transform = box.transform.rotatedby( .pi/2)
}}
Swift, Stirde, loop, viewdidload

you cannot use a loop to do that, because the UI will only be updated after the loop finish
you can use UIView.animate to do animation and do recursive to achieve n-times animation
func rotateBox(count: Int) {
if count > 0 {
UIView.animate(withDuration: 1, animations: {
self.box.transform = self.box.transform.rotated(by: .pi/2)
}, completion: { _ in
self.rotateBox(count: count - 1)
})
}
}

Related

How to define functionality to be executed after a ui transformation

I have an element I need to animate and I followed this question to transform it as follows.
I defined a button that when I click it, the view would move horizontally. However, I need to define certain functions after the transformation (For example, I would need the print statement to be working after the transformation is completed in 5 seconds). But currently, it is printed simultaneously.
How can I define functionality to be done after the transformation.
#IBAction func mybutton(_ sender: Any) {
UIView.animate(withDuration: 5.0) {
self.hhview.transform = CGAffineTransform(translationX: -160, y: 0)
print("Voila!")
}
}
You can use a completion handler to execute code after the transformation:
UIView.animate(withDuration: 5.0) {
self.hhview.transform = CGAffineTransform(translationX: -160, y: 0)
} completion: { _ in
print("Completed")
}

animation: stop reset position of an element (swift)

I'm trying animations and I'm facing a big problem: when my animation finished and I do something ( touch the screen, etc), the elements reset their positions to their first position.
I found this: [blog]: Animation Blocks resets to original position after updating text
they say it's because elements have constraints or auto layout so turn it off to fix it but I don't want to turn it off.
Can we update constraints programmatically?
is there an other solution?
here's my animation:
#IBOutlet var tfUser: UITextfiled!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
tfUser.center.x += view.bounds.width
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(2, delay: 1.8, options: [.CurveEaseInOut], animations: {
self.tfUser.center.x += self.view.bounds.width
//self.tfUser.frame = CGRectMake(0, 233, 375 , 89)
// i tried CGRectMake but it delete the animation
}, completion: nil)
}
In comment of my question , #Hardik Shekhat told to use :
self.tfUser.translatesAutoresizingMaskIntoConstraints = true
it works for me !

Showing and hiding button with custom animation

Now I'm having such animation:
UIView.animateWithDuration(0.5) { () -> Void in
self.scrollToTopBtn.alpha = self.isVisible(self.searchBar.searchBar) ? 0 : 0.6
}
Button is located in bottom-right corner of screen. I want her to appear moving from right to left and dissapear moving from left to right.
How can I do this?
One easy way to achieve this is:
1.- Lets say that let buttonWidth is the width of your scrollToTopBtn UIButton.
2.- Put your scrollToTopBtn in the bottom-right corner of screen, but all the way right out of the screen bounds. This will be your initial state for the button.
3.- When you want it to appear, just call following function:
Swift 2
func showButton() {
UIView.animateWithDuration(0.35, animations: { () -> Void in
self.scrollToTopBtn.transform = CGAffineTransformTranslate(self.scrollToTopBtn.transform, -buttonWidth, 0)
}, nil)
}
Swift 3, 4, 5
func showButton() {
UIView.animate(withDuration: 0.35, animations: { () -> Void in
self.scrollToTopBtn.transform = self.scrollToTopBtn.transform.translatedBy(x: -buttonWidth, y: 0)
}, completion: nil)
}
4.- If you want it to disappear:
Swift 2
func hideButton() {
UIView.animateWithDuration(0.35, animations: { () -> Void in
self.scrollToTopBtn.transform = CGAffineTransformTranslate(self.scrollToTopBtn.transform, buttonWidth, 0)
}, nil)
}
Swift 3, 4, 5
func hideButton() {
UIView.animate(withDuration: 0.35, animations: { () -> Void in
self.scrollToTopBtn.transform = self.scrollToTopBtn.transform.translatedBy(x: buttonWidth, y: 0)
}, completion: nil)
}
Notice that a good practice may be to create the button in runtime, adding it to the hierarchy when needed and then removing it when done using it.
There are a bunch of ways to do it. The easiest would be setting frame of the button. Something like this:
UIView.animateWithDuration(0.5) { () -> Void in
self.scrollToTopBtn.alpha = self.isVisible(self.searchBar.searchBar) ? 0 : 0.6
let superViewWidth = self.scrollToTopBtn.superview!.bounds.size.width
let buttonWidth = self.scrollToTopBtn.frame.size.width
if(self.isVisible(self.searchBar.searchBar)) { // move it off of the view (towards right)
self.scrollToTopBtn.frame.origin.x = superViewWidth + buttonWidth
}
else { // bring it back to it's position
self.scrollToTopBtn.frame.origin.x = 10 // or whatever the normal x value for the button is
}
}
The better way to do it
The better way would be to animate the constraint constant. Here's how you could do it:
First create an outlet for the constraint in your view controller:
#IBOutlet weak var buttonLeadingConstraint: NSLayoutConstraint!
Go to Interface builder and connect the button's leading constraint to buttonLeadingConstraint outlet.
Now you can animate it like this:
if(self.isVisible(self.searchBar.searchBar)) { // move it off the screen
self.buttonLeadingConstraint.constant = self.view.bounds.width + 10
}
else {
self.buttonLeadingConstraint.constant = 10 // or whatever the normal value is
}
// Now animate the change
UIView.animateWithDuration(0.5) { () -> Void in
self.view.layoutIfNeeded()
}
Note that you set the constant on constraint before you call animateWithDuration, and inside the animations block, you only need to call `layoutIfNeeded' on the parent view.

UITesting Xcode 7: How to tell if XCUIElement is visible?

I am automating an app using UI Testing in Xcode 7. I have a scrollview with XCUIElements (including buttons, etc) all the way down it. Sometimes the XCUIElements are visible, sometimes they hidden too far up or down the scrollview (depending on where I am on the scrollview).
Is there a way to scroll items into view or maybe tell if they are visible or not?
Thanks
Unfortunately Apple hasn't provided any scrollTo method or a .visible parameter on XCUIElement. That said, you can add a couple helper methods to achieve some of this functionality. Here is how I've done it in Swift.
First for checking if an element is visible:
func elementIsWithinWindow(element: XCUIElement) -> Bool {
guard element.exists && !CGRectIsEmpty(element.frame) && element.hittable else { return false }
return CGRectContainsRect(XCUIApplication().windows.elementBoundByIndex(0).frame, element.frame)
}
Unfortunately .exists returns true if an element has been loaded but is not on screen. Additionally we have to check that the target element has a frame larger than 0 by 0 (also sometimes true) - then we can check if this frame is within the main window.
Then we need a method for scrolling a controllable amount up or down:
func scrollDown(times: Int = 1) {
let topScreenPoint = app.mainWindow().coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.05))
let bottomScreenPoint = app.mainWindow().coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.90))
for _ in 0..<times {
bottomScreenPoint.pressForDuration(0, thenDragToCoordinate: topScreenPoint)
}
}
func scrollUp(times: Int = 1) {
let topScreenPoint = app.mainWindow().coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.05))
let bottomScreenPoint = app.mainWindow().coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.90))
for _ in 0..<times {
topScreenPoint.pressForDuration(0, thenDragToCoordinate: bottomScreenPoint)
}
}
Changing the CGVector values for topScreenPoint and bottomScreenPoint will change the scale of the scroll action - be aware if you get too close to the edges of the screen you will pull out one of the OS menus.
With these two methods in place you can write a loop that scrolls to a given threshold one way until an element becomes visible, then if it doesn't find its target it scrolls the other way:
func scrollUntilElementAppears(element: XCUIElement, threshold: Int = 10) {
var iteration = 0
while !elementIsWithinWindow(element) {
guard iteration < threshold else { break }
scrollDown()
iteration++
}
if !elementIsWithinWindow(element) { scrollDown(threshold) }
while !elementIsWithinWindow(element) {
guard iteration > 0 else { break }
scrollUp()
iteration--
}
}
This last method isn't super efficient, but it should at least enable you to find elements off screen. Of course if you know your target element is always going to be above or below your starting point in a given test you could just write a scrollDownUntil or a scrollUpUntill method without the threshold logic here.
Hope this helps!
Swift 5 Update
func elementIsWithinWindow(element: XCUIElement) -> Bool {
guard element.exists && !element.frame.isEmpty && element.isHittable else { return false }
return XCUIApplication().windows.element(boundBy: 0).frame.contains(element.frame)
}
func scrollDown(times: Int = 1) {
let mainWindow = app.windows.firstMatch
let topScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.05))
let bottomScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.90))
for _ in 0..<times {
bottomScreenPoint.press(forDuration: 0, thenDragTo: topScreenPoint)
}
}
func scrollUp(times: Int = 1) {
let mainWindow = app.windows.firstMatch
let topScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.05))
let bottomScreenPoint = mainWindow.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.90))
for _ in 0..<times {
topScreenPoint.press(forDuration: 0, thenDragTo: bottomScreenPoint)
}
}
func scrollUntilElementAppears(element: XCUIElement, threshold: Int = 10) {
var iteration = 0
while !elementIsWithinWindow(element: element) {
guard iteration < threshold else { break }
scrollDown()
iteration += 1
}
if !elementIsWithinWindow(element: element) {
scrollDown(times: threshold)
}
while !elementIsWithinWindow(element: element) {
guard iteration > 0 else { break }
scrollUp()
iteration -= 1
}
}
What i had to do to address this problem is to actually swipe up or down in my UI testing code. Have you tried this?
XCUIApplication().swipeUp()
Or you can also do WhateverUIElement.swipUp() and it will scroll up/down with respect to that element.
Hopefully they will fix the auto scroll or auto find feature so we don't have to do this manually.
You should check isHittable property.
If view is not hidden, the corresponding XCUIElement is hittable. But there is a caveat. "View 1" can be overlapped by "View 2", but the element corresponding to "View 1" can be hittable.
Since you have some XCUIElements in the bottom of the tableview (table footer view), the way of scrolling the tableview all the way to the bottom in the UI test, supposing your tableview has a lot data, is by tap().
.swipeUp() also does the job but the problem is when your test data is huge, it takes forever to swipe, as oppose to .tap() which directly jumps to the bottom of the tableView.
More specially:
XCUIElementsInTheBottomOrTableFooterView.tap()
XCTAssert(XCUIElementsInTheBottomOrTableFooterView.isHittable, "message")
Looks like this is a known bug :-(
https://forums.developer.apple.com/thread/9934

Odd error when capturing function parameter in Swift call to animateWithDuration:Animations:

I'm writing some simple animation code to make a button get taller and then shorter using UIView animations. The code is a little long, but fairly simple:
func animateButton(aButton: UIButton, step: Int)
{
let localStep = step - 1
let localButton = aButton
let halfHeight = aButton.bounds.height / 2
var transform: CGAffineTransform
switch step
{
case 2:
//Make the center of the grow animation be the bottom center of the button
transform = CGAffineTransformMakeTranslation(0, -halfHeight)
//Animate the button to 120% of it's normal height.
transform = CGAffineTransformScale( transform, 1.0, 1.2)
transform = CGAffineTransformTranslate( transform, 0, halfHeight)
UIView.animateWithDuration(0.5, animations:
{
aButton.transform = transform
},
completion:
{
(finshed) in
//------------------------------------
//--- This line throws the error ---
animateButton(aButton, step: 1)
//------------------------------------
})
case 1:
//In the second step, shrink the height down to .25 of normal
transform = CGAffineTransformMakeTranslation(0, -halfHeight)
//Animate the button to 120% of it's normal height.
transform = CGAffineTransformScale( transform, 1.0, 0.25)
transform = CGAffineTransformTranslate( transform, 0, halfHeight)
UIView.animateWithDuration(0.5, animations:
{
aButton.transform = transform
},
completion:
{
(finshed) in
animateButton(aButton, step: 0)
})
case 0:
//in the final step, animate the button back to full height.
UIView.animateWithDuration(0.5)
{
aButton.transform = CGAffineTransformIdentity
}
default:
break
}
}
The completion blocks for the animation methods are closures. I'm getting an error "Call to method animateButton in closure requires explicit self. to make capture semantics explicit.
The thing is, the parameter aButton is a parameter to the enclosing function. There is no reference to an instance variable.
It looks to me like this is compiler bug. Am I missing something here?
Calling methods in the same class are called with an implied self. In this case because of the closure you have to make it explicit:
self.animateButton(aButton, step: 1)