UIView animation with sub-subview - ios

So I want to animate a subview of a container view in my main view (essentially a sub subview), should I create another view controller for the subview to control the sub subview or create an outlet in the main view controller for the sub subview? The problem is that I am triggering the animation with a UIButton, which also does some other things, so if I create another view controller, this UIButton would have two IBActions. Anyways, this is the code I have for the animation in the main viewController:
#IBAction func hourSelectorTap(gesture: UITapGestureRecognizer){
if (isHourSelected){
let path = UIBezierPath()
path.moveToPoint(CGPoint(x:100, y:8))
let anim = CAKeyframeAnimation(keyPath: "MoveZeBox")
anim.path = path.CGPath
self.MinHrView.layer.addAnimation(anim, forKey: "Move")
isHourSelected = !isHourSelected
} else{
}
}

My first question to this would be, why have a container view if you're not placing some subclass of UIViewController in it? The main usage of embedding a view controller is so you can switch out which subclassed UIViewController is currently displaying. If you simply wanted nested views, I would suggest nesting UIViews and going from there.
Essentially the question is, you have some view controller that contains some subview, and you want to animate the subview, right? I would create an IBOutlet, say myView, for the view that I want to animate, and then in my function for the button press, do something like this:
#IBAction func hourSelectorTap(gesture: UITapGestureRecognizer){
if (isHourSelected){
UIView.animateWithDuration(0.2, animations: {
//your animation code here
self.myView.frame = CGRectMake(50,100,300,300)
}, completion: {
//anything you want to do when the animation is complete
})
} else {
}
}
Otherwise, yes, the correct method for handling a view controller that is being embedded in your view is to subclass that view controller and place the code in there.

Related

Why aren't the main views aligned for the view controllers in a custom cross-dissolve segue?

I've written a custom UIStoryboardSegue subclass to create a fade-out / fade-in, cross-dissolve transition between two UIViewControllers.
The view controller's main views have buttons that are centered within each of them (labeled "View Controller 1" and "View Controller 2", so I assume that when I cross-dissolve one to the other -- setting them both as children of the same superviews and making their frames the same -- the buttons will be aligned too.
class SwapSegue: UIStoryboardSegue {
override func perform() {
fade()
}
func fade() {
destination.view.alpha = 0
source.view.superview?.addSubview(destination.view)
destination.view.frame = source.view.frame
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseInOut, animations: {
self.destination.view.alpha = 1
}, completion: { success in
if var controllerStack = self.source.navigationController?.viewControllers {
controllerStack[controllerStack.firstIndex(of: self.source)!] = self.destination
self.source.navigationController?.setViewControllers(controllerStack, animated: false)
} else {
self.source.present(self.destination, animated: false, completion: nil)
}
})
}
}
This almost works, but it doesn't position the destination view properly during the transition, so there's a "pop" at the end when the segue's animation completes. The alignment seems to be off by roughly the height of the navigation bar, so I suspect that's a clue.
(Note: In this looping GIF you can see the button press on "View Controller 1", the animated cross-fade with the buttons misaligned, and then the "pop" at the end when the animation completes and the destination view controller is finally placed properly.)
How do I properly position it so it'll be aligned as it should be and the animation will be smooth?
I found a solution. I'm not 100% sure why this works, so if you can explain it, please do so in the comments below.
Originally I had the two buttons centered in the view controllers' main views via auto layout.
By adding a subview (let's call it the "Content View")to each view controller's main view, pinned to its edges, and centering each button in that view, the transition now works as expected.

How to present an UIViewController like an actionSheet?

I would like to present a view that only occupies the bottom half of the screen, with the background view remaining visible with no possible interaction.
Just like an UIAlertContoller with the actionSheet style.
How to do that in Swift ?
Create that view as a normal UIViewController, assign an identifier to it and then instantiate it like this: (keep it as a member variable though, you will need it)
var controller = self.storyboard!.instantiateViewControllerWithIdentifier("yourIdentifier") as! UIViewController
then you can add it to your parent view like
self.addSubview(controller)
Be careful about positioning it though, you can animate it neatly to the middle of the screen like
UIView.animateWithDuration(0.5, animations: {
controller.frame.origin.y = UIScreen.mainScreen().bounds.height/2
})
in
But the initial positioning should be made the holding view's viewDidLayoutSubviews method, like
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
controller.frame.origin.y = UIScreen.mainScreen().bounds.height
}
because in viewDidLoad the views don't really have correct frames yet and it would end up in wrong position

How to approach to this design - iOS

My App is Navigation based Application for iPad.
First Screen is Home Screen. On Clicking button on Home Screen, it pushes to Map Screen. On the Map Screen, i have left Panel (view controller) over the map view controller which occupies 1/4 of the screen.
Left Panel is a View Controller which has Table View. On clicking cell, it should push new viewcontroller to left panel leaving the map view controller behind.
Push
Home Screen -------- Map Screen
|(Added over map screen) Push
|----- Left Panel (Table View) -------- Detail View
I can't use Split View Controller because there is a navigation in left panel as well as in Home Screen. Some times i need to animate/hide left panel. I can customise left panel.
How to implement this structure. Is it good to use Nested Navigation Controller or is there any library available. My App supports both Portrait and Landscape. I am using Swift.
I'm sorry, I don't know Swift as well. However, I think you have to declare a base layout:
you will have a MainViewController that will include a LeftPanelViewController and a FrontViewController. In the MainViewController nib, you will create the main layout using AutoLayout: add a UIView at the left of the screen and another UIView for the frontpage.
Then, link outlets and you will have the layout done! Then you have only to add/remove subviews to leftPanelView and to FrontView.
Now, I think that the right logic is that MainViewController is the NavigationController, so you have to implement the protocol of LeftPanelViewController and FrontViewController, so Main will know how and when add/remove subviews.
The important things is that no one object have to know the existence of MainViewController to preserve the logic. So you have to notify MainViewController for something, to use delegation pattern or something else as NSNotification (be aware, it could be much weight...)
I hope it will bel helpful. Bye
I would solve this by adding a ContainerView as your Left Panel. As you want to do pushes here, you can make the container view controller a navigation controller with the table view as its root view controller.
You'll still be able to animate/hide the container view just like any other view.
This extension for the UIViewController creates the side panel to the left.
private var temp_view:UIView?
private var temp_width:CGFloat?
private weak var temp_sidebar:UIViewController?
extension UIViewController {
func configureSideBar(StoryBoard:String,SideBarIdentifier:String,View:UIView){
var storyboard = UIStoryboard(name: StoryBoard, bundle: nil)
weak var sidebar = storyboard.instantiateViewControllerWithIdentifier(SideBarIdentifier) as? UIViewController
let width:CGFloat = 250//sidebar!.view.frame.size.width
let height = UIScreen.mainScreen().bounds.height
let frame = CGRectMake(0, 0, width, height)
temp_view = View
temp_width = width
temp_sidebar = sidebar
sidebar!.view.frame = frame
addChildViewController(sidebar!)
view.addSubview(sidebar!.view)
view.sendSubviewToBack(sidebar!.view)
toogleSideBarWithAnimation(0.2,Open:false)
}
func getSidebar() -> UIViewController?{
if let sdbar = temp_sidebar {
return sdbar
}
else {
println("Warning:You have tou configure sidebar first")
return nil
}
}
func toogleSideBarWithAnimation(Duration:NSTimeInterval,Open:Bool) {
if let view = temp_view {
UIView.animateWithDuration(Duration, delay: 0, options: UIViewAnimationOptions.TransitionFlipFromLeft, animations: {
var Frame = view.frame
if !Open{
Frame.origin.x = 0
}
else {
Frame.origin.x = temp_width!
}
view.frame = Frame
}, completion: { finished in
})
}
else {
println("Warning:You have tou configure sidebar first")
}
}
}
Let me explain how to use it.
First create a view controller scene and set the storyboard id. In this examples i used "sidebar"
Then add this instruction to the map view controller to the viewDidLoad like this
override func viewDidLoad() {
super.viewDidLoad()
self.configureSideBar("Main", SideBarIdentifier: "sidebar", View: Subview)
}
I suggest adding a UIView with all your view in it in the map view controller
To open and close it use this instruction
self.toogleSideBarWithAnimation(0.2,Open:OpenSidebar)

Custom segue, scaling out the source view controller

Attempting to write a custom segue where the source view is scaled out, while changing the alpha of the destination to fade the destination in. The destination is a MKMapView, so I want it updating as the fade occurs.
With what I've tried I end up with the source and designation scaling out simultaneously, and I can't get just the source view to scale out.
class Map_Segue: UIStoryboardSegue {
override func perform()
{
var sourceViewController : UIViewController = self.sourceViewController as UIViewController
var destinationViewController : UIViewController = self.destinationViewController as UIViewController
destinationViewController.view.alpha = 0 sourceViewController.addChildViewController(destinationViewController) sourceViewController.view.addSubview(destinationViewController.view)
UIView.animateWithDuration(2.0,delay:1.0,options: UIViewAnimationOptions.CurveEaseInOut, // delay of 1 second for aesthetics
animations:
{
sourceViewController.view.transform = CGAffineTransformScale(sourceViewController.view.transform, 100.0, 100.0);
destinationViewController.view.alpha = 1;
},
completion:
{ (finished:Bool)->Void in
destinationViewController.didMoveToParentViewController(sourceViewController);
}
)
}
}
I've tried autoresizesSubviews=false, but that doesn't seem to do anything.
I've tried setting the destination transform in the animation to be 1/100 (and set the options to UIViewAnimationOptions.CurveLinear which has the final result correct, but the transition effect is wrong (map in the background scaled up then down again)
I'm sure this should be easy, and I'm missing a trick as I'm new to this.
Anyone got any ideas?
Update:
I've found that (somewhat obviously) I should use sourceViewController.view.superview?.insertSubview( destinationViewController.view, atIndex:0) to insert it alongside the original source view, rather than as a child of it, that way, obviously, the transform is independent, not with respect to the views parent (which it will be as a subview). The problem then is swapping to the new view. Using the method I had, viewWillAppear and similar are not called, and the changing over the views does not work. If I call presentViewController, then we get a glitch when viewWillAppear is called.
Solution so far
Forget using custom segues. Followed the suggestion and placed a UIImageView on top of the map view, and had a beautiful animating fade in about 5 minutes of coding.
I think you are a bit confused with parent and child view controllers etc. It is sufficient to temporarily add the second view controller's view to the first one, perform the transitions and then clean up.
I tested the following in a simple sample project. Note that I am adding the second view controller's view to the window rather than the first view controller's view because otherwise it would also get scaled up.
override func perform() {
let first = self.sourceViewController as ViewController
let second = self.destinationViewController as ViewController
second.view.alpha = 0
first.view.window!.addSubview(second.view)
UIView.animateWithDuration(2.0, delay: 0.0,
options: .CurveEaseInOut, animations: { () -> Void in
first.view.transform = CGAffineTransformMakeScale(100.0, 100.0)
second.view.alpha = 1.0
})
{ (finished: Bool) -> Void in
second.view.removeFromSuperview()
first.presentViewController(second, animated: false, completion: nil)
}
}

Is it possible to have embed segue/containing view in a scrollview with code?

On an iPhone project I'm using Xcode storyboard to embed a few containing views in a main scroll view. I've heard containing view is also an "embed segue". Now I don't necessarily have to embed other child controllers, I could have just created custom views and have the original child controllers' logic in those custom subviews. (I think I'm just going to do that after posting here, because it seems easier.) But I've already done the code and want to know how easy or hard it is to stay with it.
Because scroll view content is greater than the main screen bounds, it's harder to layout the container view in storyboard. I can think of three ways to solve it. I can either
Drag the scroll view up and down and put my container views there.
Just drag some view in the scroll view, and then resize the frame in the main controller's viewDidLoad. (And if I'm using auto layout then I would add auto layout there) But just seems to defy the advantage of having storyboard and embed segue in the first place. But it seems easier than #3 if I have to interact with child view controllers.
Forget storyboard and just write a Containing controller logic (as described in WWDC 2012 video Implementing UIViewController Containment) but this appears to be complicated.
Is there a way to create embed segue in Xcode, but NOT putting it in but to do something like a "manual segue" as with other view transitions? I wouldn't be able to see the layout in storyboard but at least it'll be easier than #3 and I don't have to drag up and down like #2 which seems silly.
I understand that the WWDC has an hour of video on it. But if you have watched any of their other videos it should become quite clear that time does not directly relate to complexity. This is how you use a container (or a child sub view controller) programmatically:
[self addChildViewController:child]; // 1
[self.view addSubview:child.view]; // 2
[child didMoveToParentViewController:self]; // 3
Pretty simple and only two extra lines of code compared to adding a subview. As you said, there are storyboard solutions but depending on your complexity, doing this through code may be easier. It really comes down to your preference though.
If you intend to animate adding the view, you should make the last call to didMoveToParentViewController in the completion block (i.e. after the animation has been completed).
Here are the helper functions I use to programmatically embed a child view controller in a view.
struct MyChildViewController {
static func embed(
viewControllerId: String,
storyboardName: String,
containerViewController: UIViewController,
containerView: UIView) -> UIViewController? {
guard let viewController = initViewController(viewControllerId, storyboardName: storyboardName)
else { return nil }
containerViewController.addChildViewController(viewController)
containerView.addSubview(viewController.view)
viewController.view.translatesAutoresizingMaskIntoConstraints = false
MyConstraints.fillParent(
viewController.view, parentView: containerView, margin: 0, vertically: true)
MyConstraints.fillParent(
viewController.view, parentView: containerView, margin: 0, vertically: false)
viewController.didMoveToParentViewController(containerViewController)
return viewController
}
static func initViewController(viewControllerId: String, storyboardName: String) -> UIViewController? {
let storyboard = UIStoryboard(name: storyboardName, bundle: NSBundle.mainBundle())
return storyboard.instantiateViewControllerWithIdentifier(viewControllerId)
}
}
struct MyConstraints {
static func fillParent(view: UIView, parentView: UIView, margin: CGFloat = 0,
vertically: Bool) -> [NSLayoutConstraint] {
var marginFormat = ""
if margin != 0 {
marginFormat = "-\(margin)-"
}
var format = "|\(marginFormat)[view]\(marginFormat)|"
if vertically {
format = "V:" + format
}
let constraints = NSLayoutConstraint.constraintsWithVisualFormat(format,
options: [], metrics: nil,
views: ["view": view])
parentView.addConstraints(constraints)
return constraints
}
}
Usage:
let childWiewController = MyChildViewController.embed("MyViewControllerId", storyboardName: "MyStoryboardName", containerViewController: containerViewController, containerView: containerView)
Where:
"MyViewControllerId" - the storyboard ID of the child view controller that will be embedded.
"MyStoryboardName" - the name of the storyboard file with embedded view controller.
containerView - the view in your container view controller that will have the child view controller embedded.

Resources