So, I have a segmented control to switch between to view controllers.
However, often, as soon as I switch, I get this message:
Unbalanced calls to begin/end appearance transitions
Plus, after I get this, sometimes it happens that every object on the view disappear.
Here's the code:
func segmentValueChanged(sender: AnyObject) {
if sender.selectedIndex == 0 {
let newController = controllers.newController1
let oldController = childViewControllers.last as UIViewController!
self.container.frame.size.height = newController.view.frame.height
oldController.willMoveToParentViewController(nil)
addChildViewController(newController)
transitionFromViewController(oldController, toViewController: newController, duration: 0.9, options: .CurveLinear, animations:{ () -> Void in
// nothing needed here
}, completion: { (finished) -> Void in
oldController.removeFromParentViewController()
newController.didMoveToParentViewController(self)
})
} else if sender.selectedIndex == 1 {
let newController = controllers.newController2
let oldController = childViewControllers.last as UIViewController!
oldController.willMoveToParentViewController(nil)
addChildViewController(newController)
transitionFromViewController(oldController, toViewController: newController, duration: 0.9, options: .CurveLinear, animations:{ () -> Void in
// nothing needed here
}, completion: { (finished) -> Void in
oldController.removeFromParentViewController()
newController.didMoveToParentViewController(self)
})
}
}
How can I solve this?
This line is backwards:
self.container.frame.size.height = newController.view.frame.height
It is newController's view's frame that you need to set!
Moreover, you are failing to put newController.view into the interface. That is why you end up without an interface.
Related
I have a view that im moving using this
func r() {
movel.constant = self.view.bounds.width - self.move.bounds.width
move.setNeedsLayout()
UIView.animate(withDuration: time, delay: 0, options:[], animations: {
self.move.superview?.layoutIfNeeded()
}, completion: {(finished: Bool) -> Void in
if (finished == true){
self.timerand()
self.l()
}else{
self.view.layoutIfNeeded()
}
})
}
And I'm using these functions to pause and resume it
func movestop() {
pausedmovetime = self.move.layer.convertTime(CACurrentMediaTime(), from: nil)
self.move.layer.speed = 0.0
self.move.layer.timeOffset = pausedmovetime
}
func movestart() {
self.move.layer.speed = 1.0
self.move.layer.timeOffset = 0.0
self.move.layer.beginTime = 0.0
let timeSincePause = self.move.layer.convertTime(CACurrentMediaTime(), from: nil) - pausedmovetime
self.move.layer.beginTime = timeSincePause
}
The app works fine but when I go out of the app I call the movestop() but when I come back it is at its final position and the completion block doesnt get called
I'm attempting to write a protocol and a custom UIStoryboardSegue class that will allow me to easily implement custom transitions in my UIViewControllers:
public protocol TransitionController
{
var transitionDurationIn: CFTimeInterval { get }
var transitionDurationOut: CFTimeInterval { get }
func prepareTransitionIn()
func prepareTransitionOut()
func performTransitionIn(finished: #escaping () -> Void)
func performTransitionOut(finished: #escaping () -> Void)
}
class JFTransitionControllerSegue: UIStoryboardSegue {
override func perform() {
let defaultTransitionDuration : CFTimeInterval = 1.5
if let dvc = self.destination as? TransitionController {
dvc.prepareTransitionIn()
}
else {
// Default transition
self.destination.view.alpha = 0
}
if let svc = self.source as? TransitionController {
svc.prepareTransitionOut()
svc.performTransitionOut(){ () in
if let dvc = self.destination as? TransitionController {
dvc.performTransitionIn(){ () in
self.source.present(self.destination, animated: false, completion: nil)
}
}
else {
// Default transition for the destination controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.destination.view.alpha = 1
}) { (Finished) in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
}
else
{
// Default transition for the source controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.source.view.alpha = 0
}) { (Finished) in
if let dvc = self.destination as? TransitionController {
dvc.performTransitionIn(){ () in
self.source.present(self.destination, animated: false, completion: nil)
}
}
else {
// Default transition for the destination controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.destination.view.alpha = 1
}) { (Finished) in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
}
}
}
class TestController: UIViewController, TransitionController {
#IBOutlet weak var form_username: UITextField!
// MARK: - TransitionController Protocol
var transitionDurationIn : CFTimeInterval {return 1.0}
var transitionDurationOut : CFTimeInterval {return 1.0}
func prepareTransitionIn()
{
//self.view.alpha = 0 // no fade in if you uncomment
form_username.alpha = 0 // nil
}
func prepareTransitionOut()
{
self.view.alpha = 1 // works
}
func performTransitionIn(finished: #escaping () -> Void)
{
UIView.animate(withDuration: self.transitionDurationIn, animations: {
//self.view.alpha = 1 // no fade in if you uncomment
self.form_username.alpha = 1 // nil, crashes
}) { (Finished) in
finished()
}
}
func performTransitionOut(finished: #escaping () -> Void)
{
UIView.animate(withDuration: self.transitionDurationOut, animations: {
self.view.alpha = 0 // fades out correctly
}) { (Finished) in
finished()
}
}
}
Basically, you just implement the protocol in any UIViewController you want, then make a segue of class JFTransitionControllerSegue. In the performTransitionIn function, you can just do something like UIView.animate and change the alpha or whatever you like. The problem I'm having is that the destination segue simply pops up instead of transitioning in properly. From what I can tell while debugging it isn't fully initialized - IBOutlet variables are nil, but the controller itself isn't. Is this a bad design pattern, or am I just missing something simple?
A view controller being initialised is one event. It's view being loaded is another.
The view property of a view controller is loaded lazily and the outlets are built and connected at that point. That's why viewDidLoad() is a thing.
If you want the view to be ready for you, you can call loadViewIfNeeded() on the view controller first.
Misdiagnosed the problem...the destination controller was loaded, but I had forgotten to add the destination controller's view to the window in the Segue class:
class JFTransitionControllerSegue: UIStoryboardSegue {
override func perform() {
let defaultTransitionDuration : CFTimeInterval = 1.5
if let dvc = self.destination as? TransitionController {
dvc.prepareTransitionIn()
}
else {
// Default transition
self.destination.view.alpha = 0
}
if let svc = self.source as? TransitionController {
svc.prepareTransitionOut()
svc.performTransitionOut(){ () in
UIApplication.shared.keyWindow?.insertSubview(self.destination.view, aboveSubview: self.source.view)
if let dvc = self.destination as? TransitionController {
dvc.performTransitionIn(){ () in
self.source.present(self.destination, animated: false, completion: nil)
}
}
else {
// Default transition for the destination controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.destination.view.alpha = 1
}) { (Finished) in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
}
else
{
// Default transition for the source controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.source.view.alpha = 0
}) { (Finished) in
UIApplication.shared.keyWindow?.insertSubview(self.destination.view, aboveSubview: self.source.view)
if let dvc = self.destination as? TransitionController {
dvc.performTransitionIn(){ () in
self.source.present(self.destination, animated: false, completion: nil)
}
}
else {
// Default transition for the destination controller
UIView.animate(withDuration: defaultTransitionDuration, animations: {
self.destination.view.alpha = 1
}) { (Finished) in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
}
}
}
I want to set speed to my UISlider, to make it move more smoothly.
Here is how I am trying to animate it :
UIView.animateWithDuration(6, delay: 0.0, options: UIViewAnimationOptions.TransitionCurlUp, animations: { () -> Void in
self.videoSlider.setValue(time, animated: true)
if self.videoSlider.value == self.videoSlider.maximumValue {
self.playerLayer.stop()
self.playBigButton.hidden = false
self.pauseButton.hidden = true
self.playButton.hidden = false
}
}, completion: { (finished: Bool) -> Void in
})
But it moves with a different speed on the beginning, middle and end.
Try giving your animation the option of .CurveLinear, like this:
UIView.animateWithDuration(6,
delay: 0.0,
options: [UIViewAnimationOptions.TransitionCurlUp, .CurveLinear],
animations: { () -> Void in
self.videoSlider.setValue(time, animated: true)
if self.videoSlider.value == self.videoSlider.maximumValue {
self.playerLayer.stop()
self.playBigButton.hidden = false
self.pauseButton.hidden = true
self.playButton.hidden = false
}
}, completion: { (finished: Bool) -> Void in
})
How to refactore code to be more compact by sending the function the view , number of animated times , applied action function and success funtion
If functional programming solution can be proposed it will be helpful
typealias AnimateAction = UIView -> Void
typealias AnimateSuccess = Bool -> Void
func animateThreetimes(animatedView:UIView,animateAction:AnimateAction,animateSuccess:AnimateSuccess)
{
UIView.animateWithDuration(0.5, animations: { () -> Void in
animateAction(animatedView)
}) { (success) -> Void in
UIView.animateWithDuration(1, animations: { () -> Void in
animateAction(animatedView)
}, completion: { (success) -> Void in
UIView.animateWithDuration(0.5, animations: { () -> Void in
animateAction(animatedView)
}, completion: { (success) -> Void in
animateSuccess(success)
})
})
}
}
Recursion would do quite nicely:
func animate(count: Int) {
if count > 0 {
UIView.animateWithDuration(0.2, animations: { () -> Void in
// do the animations
}, completion: { (_) -> Void in
self.animate(count-1);
})
}
}
You could also do something like this to have a bit more control of the variables you are passing in and how you want to handle the completion. Furthermore this answers your request for a functional solution to the problem.
typealias AnimationAction = UIView -> Void
typealias AnimationSuccess = Bool -> Void
func animateView(view: UIView, animationAction: AnimationAction) -> (duration: NSTimeInterval, completionHandler: AnimationSuccess?) -> Void {
// Return a function that takes a duration and a maybe completion handler
return { duration, completionHandler in
return UIView.animateWithDuration(duration, animations: {
animationAction(view)
}, completion: { finished in
completionHandler?(finished) // Optional function only called if exists
})
}
}
// Just showing the mechanism
let durations = [0.5, 1, 0.5]
for index in 0..<durations.count {
let view = UIView(frame: CGRectZero)
let animationAction: AnimationAction = { view in
print("View: \(view)")
}
let completionHandler: AnimationSuccess? = {
if durations[index] == durations.last {
return { finished in
print(finished)
}
} else {
return nil
}
}()
animateView(view, animationAction: animationAction)(duration: durations[index], completionHandler: completionHandler)
}
Assuming that you want a general approach that could be used for an arbitrary number of animations ...
You could include a counter variable in the function call, decrement it when you have animated, and while it is non-zero, call the animate function again
I'm using transitionFromViewController to switch between two view controllers using a segmented control.
Here's my code:
#IBAction func valueChanged(sender: AnyObject) {
var newController = storyboard?.instantiateViewControllerWithIdentifier(viewControllerIdentifiers[sender.selectedSegmentIndex]) as! UIViewController
let oldController = childViewControllers.last as! UIViewController
oldController.willMoveToParentViewController(nil)
addChildViewController(newController)
newController.view.frame = oldController.view.frame
if viewControllerIdentifiers[sender.selectedSegmentIndex] == "first" {
let vc = newController as! userProfileViewController
vc.userToShow = self.userToShow
}
transitionFromViewController(oldController, toViewController: newController, duration: 0.25, options: .TransitionCrossDissolve, animations:{ () -> Void in
// nothing needed here
}, completion: { (finished) -> Void in
oldController.removeFromParentViewController()
newController.didMoveToParentViewController(self)
})
}
is it possible to pass data to the childViewControllers, from the Parent?
Yes
transitionFromViewController(oldController, toViewController: newController, duration: 0.25, options: .TransitionCrossDissolve, animations:{ () -> Void in
newController.property = currentProperty
//basically here is where you can pass the data. I used a property as an example. You know what you need to send :)
}, completion: { (finished) -> Void in
oldController.removeFromParentViewController()
newController.didMoveToParentViewController(self)
})