What's the difference between UIView.animate() and UIButton.animate()? - ios

I want to change the opacity of my button to half when pressed, and animate that change. So I was searching for how to do that and found this piece of code:
#IBAction func keyPressed(_ sender: UIButton) {
UIView.animate(withDuration: 0.3)
{
sender.alpha = 0.5
}
I got curious as to why we called the animate function on a UIView not on a UIButton, as what we want to animate is, specifically, a UIButton. So I tried UIButton.animate() and to my eyes it gives the same result with the animation.
So what's the difference? Is there a reason the person posting this code preferred using UIView.animate() over UIButton.animate()?

The animate function is a class level function of UIView so it is common to use it as UIView.animate....
Since UIButton ultimately extends UIView, using UIButton.animate... also works. You could even use UIScrollView.animate..., for example, in your UIButton code. Obviously that would be confusing but it would work.
In any code within a UIView subclass, using Self.animate... also works.
But the basic answer to your question is that people use UIView.animate... because animate is defined in the UIView class.

Related

Add gesture recognizer to BezierPath created by PaintCode

I have no idea about the following situation:
I had exported a NSObject from PaintCode and made a .swift file (someObject.swift).
public class PlanATrip: NSObject {
class func drawRectangle1(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 200, height:100), resizing: ResizingBehavior = .aspectFit) {
....
}
I also overrode the draw() function in a UIView (someObjectView.swift).
So how can add a gesture recognizer to a bezierPath (for example, a rectangle1 = UIBezierPath(...) ) which is in the someObject.swift ?
I tried to add some functions like:
let tapGestureA:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(touchAction))
However, the scope confused me; like if I put the touchAction function out of the drawRectangle1 function, I will not be able to access rectangle1.
How can I modify to make such a gesture recognizer work?
You are trying to manipulate UIBezierPath objects generated by PaintCode directly, which isn't PaintCode's purpose.
What you are trying to achieve is possible, but not without some hacking.
For example: How to access a layer inside UIView custom class generate with Paintcode?
In my opinion, you are "misusing" PaintCode.
Instead, you would be much better adding the tapping logic inside your custom UIView. The thing you called someObjectView.swift.
For example: https://github.com/backslash-f/paint-code-ui-button
In case you really need to check if gestures occur inside the boundaries of a UIBezierPath then you need to create a public reference to it (e.g.: a var that points to it outside of the drawRectangle1 function) and finally use the code proposed by #Sparky.
The main problem with this approach is that your changes will be overwritten every time you use PaintCode's export feature. I wouldn't recommend it.
If I understand your question correctly, you'd like to add a UITapGestureRecognizer that will recognize a gesture within a portion of your view defined by a UIBezierPath.
In this case, assign the selector to the gesture recognizer as you have in your question, then in the body of touchAction(recognizer:), test whether the gesture lies within the UIBezierPath. The simple case where you only care if the gesture is inside the UIBezierPath might be handled as follows:
func touchAction(recognizer: UITapGestureRecognizer) -> Void {
guard rectangle1.contains(recognizer.location(in: self)) else { return }
// Execute your code for the gesture here
// ...
}

UIView subclass access ViewController methods swift

In a couple of my projects I think I'm not created a great structure in many cases.
It could be a game where I've created a game board (think about chess) with a grid of 8 * 8 cells. Each cell has a gesture recognizer that relies on a subclass (cell.swift), with the game logic in a parent ViewController.
For arguments sake, let us say we want to display to the user which square they have touched.
I've found out how to do this from the subclassed UIView (obvs. create the alert in the subclassed UIView / cell.swift in this example)
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
but it seems to break the structure of the app - but wouldn't it be the same accessing an action in the parent ViewController? What is the best way of approaching this>
Your rootViewController is the VC on the bottom of your stack. It's not a safe way to access the visible VC, and is rarely useful, in general (there are cases, but I doubt your app would find them useful).
What you likely want to use is a delegate pattern. Let's say the parent VC that displays your chess board (let's call this MyBoardViewController), conforms to a protocol like the following. MyView is whatever custom UIView class you're using for the chess squares:
protocol SquareAlertHandler {
func handleSquarePressed(sender : myView)
}
And add the following property to your MyView class:
weak var delegate : SquareAlertHandler?
And replace whatever event handler you're currently using, with the following (I'm assuming you're using a UIButton in IB to handle the press, and have arbitrarily named the outlet 'didPress:'):
#IBAction didPress(sender : UIButton) {
delegate?.handleSquarePressed(self)
}
Now, add the protocol to your MyBoardViewController, and define the method:
class MyBoardViewController : UIViewController, SquareAlertHandler {
... ... ...
func handleSquarePressed(sender : myView) {
// Do something to handle the press, here, like alert the user
}
... ... ...
}
And finally, wherever you create the MyView instances, assign the MyBoardViewController instance as the delegate, and you're good to go.
Depending on your Swift literacy, this may be confusing. Adding code, so that I can at least match up the class names, would help to clarify things.

Setting Slider Value to Set SeekToTime in AVPlayer

I am using Player library, that is using AVPlayer & AVFoundation, which is quiet convenient for my case. I successfully managed to play the video and add a slider. I set the slider's min to 0 and max to duration of the video..
At this point, in order to connect slider to current playtime, I used this answer, on StackOverflow. I setup a protocol and used addPeriodicTimeObserverForInterval, so slider is linked to the currentTime and moving as video moves along successfully.
Now, here is my problem. I want to do the reverse and make - when slider moves, set currentTime of the video (forward,backward).
I made some research and I found out that it's seekToTime that I should link the slider.value (sender.value in my case) but, I couldn't understand how to apply it to Player. How can I make the slider to control the currentTime?
I am not sure if I am right, but as far as I understand, the first step should be:
protocol UserSetsTheTimeDelegate
{
func userSetsTheTime(result: Int)
}
class ViewController: UIViewController, PlayerDelegate {
...
func sliderValueDidChange(sender:UISlider!) {
print("value--\(sender.value)")
self.delegate?.userSetsTheTime(sender.value)
}
}
And add, I think I should add the UserSetsTheTimeDelegate in Player.swift, but I got confused here.
Update:
I realised that I can send the data to the Player.swift by using an instance in ViewController and adding a method in Player class. This is the first step. But now, as currentTime is getting updated (with modifying the slider) so quickly, it doesn't let me manipulate the slider value.
It is preferable from an UX point of view to call the seek() method on your Player after a touchUp on your slider, rather than whenever the value changes.
As you noticed, you had poor experience with the sliderValueDidChange.
Instead, try by adding a target/action on UIControlEvents.TouchUpInside (for instance) on your slider, then only seek. The experience should be better.
var slider : UISlider! {
didSet {
slider.addTarget(self, action: Selector("touchUp:"), forControlEvents: [UIControlEvents.TouchUpInside])
}
}
func touchUp(sender:AnyObject?) {
// player.seek(slider.currentValue)
}
I solved my problem. I created an instance of Player so I was able to send the data from the view controller back to Player class. Then I simply applied seekToTime(). At this point, visually, it was seeming poor but then I noticed that I can use stop(), setCurrentTime() and then play() in order to make it look nicer.

Use buttons inside subview

I'm making a multiple choice quiz game, and my goal right now is to have four buttons that refresh by spinning around with new answer choices. I think that means I need a subview that animates and re-populates with new buttons--if that's incorrect or not best, please stop me here.
At any rate, I created the subview in my storyboard, put the buttons inside it (background is blue just to see it now):
I dragged that over to my ViewController to make an IBOutlet (buttonContainer) and added this code to my ViewDidLoad:
view.addSubview(buttonContainer)
let buttonTap = UITapGestureRecognizer(target:self, action: Selector("checkAnswer"))
buttonTap.numberOfTapsRequired = 1
buttonContainer.addGestureRecognizer(buttonTap)
buttonContainer.userInteractionEnabled = true
However: When I run it in the simulator, the blue background does not appear at all, but the buttons are still disabled.
Before creating the subview, both the buttons and the function (checkAnswer) they called all worked perfectly.
You don't need any of this code if you are creating everything in storyboard. Just create a new class for the containerview and connect the buttons as an outlet collection.
For example, your button container class might look something like this:
class ButtonContainerView: UIView {
#IBOutlet var answerButtons: [UIButton]!
func rotateButtons() {
for button in answerButtons {
var context = UIGraphicsGetCurrentContext()
UIView.beginAnimations(nil, context: &context)
UIView.setAnimationCurve(UIViewAnimationCurve.Linear)
UIView.setAnimationDuration(5.0)
button.transform = CGAffineTransformRotate(button.transform, CGFloat(M_PI))
UIView.commitAnimations()
}
}
}

Swift - Access IBOutlet in other class

I have a UIView with a TableView and a Button (Big Button). The TableView has a custom Cell. In this cell there is an "Add" button. I want to animate the first button when the user makes click on the Add button.
This is my schema:
This is my code:
class ProductsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet var bigButton: UIButton! <- I WANT TO ANIMATE THAT BUTTON
}
ProductCell class
class ProductCell: UITableViewCell {
#IBAction func addProduct(sender: AnyObject) {
//I WANT TO ACCESS THE BIG BUTTON FROM HERE
}
}
Screen example of my app
I've tried to get the parent controller or the superview to get the IBOutlet but the app is crashing allways
Add block properties to your cells which lets them notify your view controller when they have been clicked. In your view controller block code, you can then access the big button.
See my answer to a similar question. Simply replace the switch example with your button. So replace UISwitch with UIButton.
How can I get index path of cell on switch change event in section based table view
So rather than have the cell try and talk to another cell/button, have the cell notify the controller which can then manage the big button changes.
Although I made a comment about using alternate methods you could also employ a strategy below based on updates to a property stored in the current view controller class. You could just as well use property observation on the ProductsViewController but I assume you'd like to keep OOP focused and reduce the size of your controller.
Subclass the ViewController
One could subclass an existing UIViewController and then create a property in the super class that deals with the value that was changed (row tapped). In that subclass you could then do some animation. Because you would be subclassing you continue to obtain all the benefits and methods defined in your existing controller. In your identity inspector point your Class to the new subclass and create any functional updates to your UI using animation.
class ProductsViewController:... {
var inheritedProperty:UIView = targetView {
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
}
}
class AnimatedProductsViewController:ProductsViewController {
override var inheritedProperty:UIView {
//do something interesting if the property of super class changed
willSet {newValue } // is the newValue
didSet {oldValue} //is the old value
//you might want to call this method like so
// didSet { animate(newValue) }
}
func animate (view: UIView){
//do animation routine using UIView animation, UIDynamics, etc.
}
}
Property Observation
Whenever the didSelectCell... method is called just set a value to the inheritedProperty. Then add the property observers (see sample code) and react when the property changes (maybe pass a reference to the view you want to animate).
For example: Within the property observer you can just take that view and pass it to your animator function (whatever is going to do the animation). There are many examples on SO of how to animate a view so just search for (UIView animation, UIDynamics, etc).
The normal benefits of separation are encapsulation of functionality and reuse but Swift also guarantees that each set of property observers will fire independently. You'd have to give some more thought to this as to its applicability in this use case.
Do all this things in your viewController
Add target Method to cell's add button in cellForRowAtIndexPath Method
Like This
cell.add.addTarget(self, action: "addProduct:", forControlEvents: UIControlEvents.TouchUpInside)
Define method
func addProduct(button:UIButton)
{
// do button animation here
}

Resources