I am new to iOS Development and I want to build some workout app and want to have some zoom-in animation after launching the app. I searched for it on the internet and found some YouTube video, where I saw how to do the animation immediately after launching the app. So I wrote down the code, that was presented in the video. So in the ViewController.swift I got imageView variable, which is the logo. And in ViewController.swift my code looks like this:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
imageView.center = view.center
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {self.animate()} )
}
private func animate() {
UIView.animate(withDuration: 1, animations: {
let size = self.view.frame.size.width * 1.82
let diffX = size - self.view.frame.size.width
let diffY = self.view.frame.size.height - size
self.imageView.frame = CGRect(
x: -(diffX/2),
y: diffY/2,
width: size,
height: size ) })
UIView.animate(withDuration: 1.9, animations: {
self.imageView.alpha = 0 }, completion: {done in
if done {
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
let viewController = HomeViewController()
viewController.modalTransitionStyle = .crossDissolve
viewController.modalPresentationStyle = .fullScreen
self.present(viewController, animated: true)
})
}
})
}
And as I understand, it will load the HomeViewController, where I programmatically added a label "Hello!" in the center. And then I run the app, the logo zooms in and the screen changes to "Hello!".
But if I create a View Controller in Storyboard and link it to HomeViewController and also add there some label, it will not show in the app when I run it, even if I connect the ViewController Storyboard to HomeViewController by dragging it.
And for testing purposes I just created a second ViewController and called it SecondViewController (swift file, as well as a ViewController in the Storyboard, and linked it to the swift file), so I connected the HomeViewController to SecondViewController and in the Storyboard I added some Label to SecondViewController to see, if it going to be presented. But after launching the app, it did not present it.
And it throws a Warning in the Console like "Attempt to present SecondViewController on HomeViewController (from HomeViewController) whose view is not in the window hierarchy.
Attempt to present HomeViewController on ViewController (from ViewController) whose view is not in the window hierarchy.
How can I fix this and work later through the storyboard, design views, add buttons and so on?
welcome to stackOverflow. The problem is that you are not grabbing the correct instance of HomeViewController. By doing let viewController = HomeViewController() you are creating a new one rather than grabbing the instance you created in the storyboard.
Change that line to
let storyboard = UIStoryboard(name: "yourStoryboardName", bundle: nil)
let homeVC = storyboard.instantiateViewController(withIdentifier: "ViewControllerIdentifier") as! HomeViewController //set the VC's identifier from the storyboard identity inspector
Related
I implemented the share extension and I want animate my View Controller with a crossDissolve, so i set the modalPresentationStyle = .overFullScreen and modalTransitionStyle = crossDissolve but it seems not working. The VC still appear from the bottom to the top and with the new iOS 13 modal style (not completly full screen).
Anyone know how to solve it? It tried both with and without storyboard.
NB: I'm not talking about a normal VC presentation, but the presentation of the share extension, it means that it's another app that present my VC.
One way to do it would be to have the system presented viewcontroller as a container.
And then present your content viewcontroller inside modally.
// this is the entry point
// either the initial viewcontroller inside the extensions storyboard
// or
// the one you specify in the .plist file
class ContainerVC: UIViewController {
// afaik presenting in viewDidLoad/viewWillAppear is not a good idea, but this produces the exact result you are looking for.
// meaning the content slides up when extension is triggered.
override func viewWillAppear() {
super.viewWillAppear()
view.backgroundColor = .clear
let vc = YourRootVC()
vc.view.backgroundColor = .clear
vc.modalPresentationStyle = .overFullScreen
vc.loadViewIfNeeded()
present(vc, animated: false, completion: nil)
}
}
and then use the content viewcontroller to show your root viewcontroller and its view hierarchy.
class YourRootVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let vc = UIViewController() // your actual content
vc.view.backgroundColor = .blue
vc.view.frame = CGRect(origin: vc.view.center, size: CGSize(width: 200, height: 200))
view.addSubview(vc.view)
addChild(vc)
}
}
Basically a container and a wrapper in order to get the control over the views being displayed.
Source: I had the same problem. This solution works for me.
how to add mathwidget as a subview inside another UIViewController
Currently, mathwidget is working fine when loading UIViewController.
let subViewEE = MathWidgetClassName()
self.present(subViewEE, animated: true, completion: nil)
But when I am trying to add it as a subview inside present view controller nothing shows up, here is the code:
let mathWidget= MathWidgetClassName()
self.addChildViewController(mathWidget)
self.view.addSubview(mathWidget.view)
mathWidget.didMove(toParentViewController: self)
Can anyone help to display MathWidget as a subview in present UIViewController?
you are creating viewcontroller programmatically then you need to set frame and background color of it like,
let mathWidget = MathWidgetClassName()
mathWidget.view.bounds = self.view.bounds
mathWidget.view.backgroundColor = UIColor.green // you should set white here , it is for demonstration
self.addChildViewController(mathWidget)
self.view.addSubview(mathWidget.view)
mathWidget.didMove(toParentViewController: self)
If you have view controller in storyboard then you should do like,
let mathWidget = self.storyboard?.instantiateViewController(withIdentifier: "storyBoardID") //storyBoardID is Storyboard id - can be set from identity inspector of storyboard
// mathWidget?.view.bounds = self.view.bounds
// mathWidget?.view.backgroundColor = UIColor.green
self.addChildViewController(mathWidget!)
self.view.addSubview((mathWidget?.view)!)
mathWidget?.didMove(toParentViewController: self)
I encountered an unusual behavior on which I am stuck a little, the problem is the following.
I'm using BWWalkthrough library in order to have a 4 slides as launch screen. So in my appdelegate i have the following code which initialize the viewcontrollers:
let storyboard = UIStoryboard(name: "SlidesFlow", bundle: nil)
let walkthrough = storyboard.instantiateViewController(withIdentifier: "SlidesView") as! BWWalkthroughViewController
let page_zero = storyboard.instantiateViewController(withIdentifier: "page_1")
let page_one = storyboard.instantiateViewController(withIdentifier: "page_2")
let page_two = storyboard.instantiateViewController(withIdentifier: "page_3")
let page_three = storyboard.instantiateViewController(withIdentifier: "page_4")
walkthrough.delegate = self
walkthrough.addViewController(page_zero)
walkthrough.addViewController(page_one)
walkthrough.addViewController(page_two)
walkthrough.addViewController(page_three)
Everything works as intended, so no problem here. On the viewController page_three i have a button which redirect me to an other view controller using a custom segue animation
class sentSegueFromRight: UIStoryboardSegue {
override func perform()
{
let src = self.source as UIViewController
let dst = self.destination as UIViewController
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: UIViewAnimationOptions.curveEaseInOut,
animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { finished in
src.present(dst, animated: false, completion: nil)
}
)
}
}
Now the problem is, if i use the same code on a normal viewcontroller the button and the animation work without issues. The problem is when i use the segue defined above from the last slide of my BWWalkthrough. The first time i tapp the button the viewcontroller which should appear does appear but without the corresponding animation. After closing it and taping on the button again the animation is played but an error is returned:
Presenting view controllers on detached view controllers is
discouraged
If i use the button with the standard animation ( without using my custom animation code ) i get no error and the default animation is played.
I can't seem to find a solution to this problem. Does anybody stumbled upon something like this?
The problem here lies in the BWWalkthrough library which is using a scrollview to present all the views of the various ViewControllers that you add.
As such, you add the dst.view at the beginning of the scrollview (at offset screenwidth,0) which you then transform to offset (0,0).
All of this is offscreen as you are currently in the third screen of the walkthrough (at offset (screenwidth*3,0)). As such you don't get to see the animation and directly see the presented view controller when the segue ends.
To remedy this, add your dst.view in the segue to the superview of the scrollview. i.e. instead of src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
write src.view.superview?.superview?.insertSubview(dst.view, aboveSubview: src.view) in the segue. (Assuming that you are using the segue only from the walkthrough)
If you intend to use the segue in other places too, then you can maybe add a type check in the segue to check if the superview of src.view is a scrollview, if yes, add dst.view to the superview of the scrollview.
I have a segue from ViewController A to ViewController B. It uses a custom segue class RightSegue. This is the code for the RightSegue class:
class RightSegue: UIStoryboardSegue {
override func perform() {
// Assign source and destination view controllers to local variables.
let vc1: UIViewController = self.sourceViewController
let vc2: UIViewController = self.destinationViewController
// Get screen width and height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// get the source view controller's frame.
let vc1Frame = vc1.view.frame
let vc2NavController = vc2.navigationController
let vc2NavFrameHeight = vc2NavController!.navigationBar.frame.size.height
// Specify the initial position of the destination view. (x, y, width, height)
vc2.view.frame = CGRectMake(screenWidth, vc2NavFrameHeight, screenWidth, screenHeight)
// Push the destinationViewController onto sourceViewController.
vc1.navigationController!.pushViewController(vc2, animated: false)
// Specify the initial position of the source view. Add destination view as a subview of the source view.
vc1.view.frame = CGRectMake(vc1.view.frame.origin.x, vc1.view.frame.origin.y, screenWidth, vc1Frame.size.height)
vc2.navigationController?.view!.addSubview(vc1.view!)
// Animate!
UIView.animateWithDuration(0.25, animations: {() -> Void in
vc1.view.frame = CGRectMake(-screenWidth, vc1.view.frame.origin.y, vc1.view.frame.size.width, vc1.view.frame.size.height)
vc2.view.frame = CGRectMake(0.0, 0.0, vc2.view.frame.size.width, vc2.view.frame.size.height)
}, completion: {(finished: Bool) -> Void in
vc1.view!.removeFromSuperview()
})
}
The view controllers are set up as shown in the image.
As can be seen, the FirstViewController contains a button which when clicked shows the SecondViewController.
The next image shows the segue's properties:
When I execute the app and press the button in FirstViewController, the app crashes with the error message:
fatal error: unexpectedly found nil while unwrapping an Optional value
I checked vc2NavController in the RightSegue class. It has a return type of UINavigationController?. Force unwrapping it returns nil.
As indicated in this answer (Custom Segue in Swift), I added the #objc() and ran the app. It still didn't work. The app crashes, this time giving the error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not create a segue of class '(null)''
I don't know what to do. Please help.
Edit:
You need to push your second viewController for it to have a navigationController.
The custom segue is what causing your problem. You need to return the navigationController, or better - read this:
http://netsplit.com/custom-ios-segues-transitions-and-animations-the-right-way
If you want to stay with the custom, this may help -
Custom Push Segue removes navigation bar and tab bar in story board
I have found few posts for this problem but none of them solved my issue.
Say like I've..
ViewControllerA
ViewControllerB
I tried to add ViewControllerB as a subview in ViewControllerA but, it's throwing an error like "fatal error: unexpectedly found nil while unwrapping an Optional value".
Below is the code...
ViewControllerA
var testVC: ViewControllerB = ViewControllerB();
override func viewDidLoad()
{
super.viewDidLoad()
self.testVC.view.frame = CGRectMake(0, 0, 350, 450);
self.view.addSubview(testVC.view);
// Do any additional setup after loading the view.
}
ViewControllerB is just a simple screen with a label in it.
ViewControllerB
#IBOutlet weak var test: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
test.text = "Success" // Throws ERROR here "fatal error: unexpectedly found nil while unwrapping an Optional value"
}
EDIT
With the suggested solution from the user answers, ViewControllerB in ViewControllerA is going off the screen. Grey border is the frame I have created for the subview.
A couple of observations:
When you instantiate the second view controller, you are calling ViewControllerB(). If that view controller programmatically creates its view (which is unusual) that would be fine. But the presence of the IBOutlet suggests that this second view controller's scene was defined in Interface Builder, but by calling ViewControllerB(), you are not giving the storyboard a chance to instantiate that scene and hook up all the outlets. Thus the implicitly unwrapped UILabel is nil, resulting in your error message.
Instead, you want to give your destination view controller a "storyboard id" in Interface Builder and then you can use instantiateViewController(withIdentifier:) to instantiate it (and hook up all of the IB outlets). In Swift 3:
let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
You can now access this controller's view.
But if you really want to do addSubview (i.e. you're not transitioning to the next scene), then you are engaging in a practice called "view controller containment". You do not just want to simply addSubview. You want to do some additional container view controller calls, e.g.:
let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
addChild(controller)
controller.view.frame = ... // or, better, turn off `translatesAutoresizingMaskIntoConstraints` and then define constraints for this subview
view.addSubview(controller.view)
controller.didMove(toParent: self)
For more information about why this addChild (previously called addChildViewController) and didMove(toParent:) (previously called didMove(toParentViewController:)) are necessary, see WWDC 2011 video #102 - Implementing UIViewController Containment. In short, you need to ensure that your view controller hierarchy stays in sync with your view hierarchy, and these calls to addChild and didMove(toParent:) ensure this is the case.
Also see Creating Custom Container View Controllers in the View Controller Programming Guide.
By the way, the above illustrates how to do this programmatically. It is actually much easier if you use the "container view" in Interface Builder.
Then you don't have to worry about any of these containment-related calls, and Interface Builder will take care of it for you.
For Swift 2 implementation, see previous revision of this answer.
Thanks to Rob.
Adding detailed syntax for your second observation :
let controller:MyView = self.storyboard!.instantiateViewControllerWithIdentifier("MyView") as! MyView
controller.ANYPROPERTY=THEVALUE // If you want to pass value
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChildViewController(controller)
controller.didMoveToParentViewController(self)
And to remove the viewcontroller :
self.willMoveToParentViewController(nil)
self.view.removeFromSuperview()
self.removeFromParentViewController()
This code will work for Swift 4.2.
let controller = self.storyboard!.instantiateViewController(withIdentifier: "secondViewController") as! SecondViewController
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)
For Add and Remove ViewController
var secondViewController :SecondViewController?
// Adding
func add_ViewController() {
let controller = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController")as! SecondViewController
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)
self.secondViewController = controller
}
// Removing
func remove_ViewController(secondViewController:SecondViewController?) {
if secondViewController != nil {
if self.view.subviews.contains(secondViewController!.view) {
secondViewController!.view.removeFromSuperview()
}
}
}
Thanks to Rob, Updated Swift 4.2 syntax
let controller:WalletView = self.storyboard!.instantiateViewController(withIdentifier: "MyView") as! WalletView
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)
func callForMenuView()
{
if(!isOpen)
{
isOpen = true
let menuVC : MenuViewController = self.storyboard!.instantiateViewController(withIdentifier: "menu") as! MenuViewController
self.view.addSubview(menuVC.view)
self.addChildViewController(menuVC)
menuVC.view.layoutIfNeeded()
menuVC.view.frame=CGRect(x: 0 - UIScreen.main.bounds.size.width, y: 0, width: UIScreen.main.bounds.size.width-90, height: UIScreen.main.bounds.size.height);
UIView.animate(withDuration: 0.3, animations: { () -> Void in
menuVC.view.frame=CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width-90, height: UIScreen.main.bounds.size.height);
}, completion:nil)
}else if(isOpen)
{
isOpen = false
let viewMenuBack : UIView = view.subviews.last!
UIView.animate(withDuration: 0.3, animations: { () -> Void in
var frameMenu : CGRect = viewMenuBack.frame
frameMenu.origin.x = -1 * UIScreen.main.bounds.size.width
viewMenuBack.frame = frameMenu
viewMenuBack.layoutIfNeeded()
viewMenuBack.backgroundColor = UIColor.clear
}, completion: { (finished) -> Void in
viewMenuBack.removeFromSuperview()
})
}
Please also check the official documentation on implementing a custom container view controller:
https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html#//apple_ref/doc/uid/TP40007457-CH11-SW1
This documentation has much more detailed information for every instruction and also describes how to do add transitions.
Translated to Swift 3:
func cycleFromViewController(oldVC: UIViewController,
newVC: UIViewController) {
// Prepare the two view controllers for the change.
oldVC.willMove(toParentViewController: nil)
addChildViewController(newVC)
// Get the start frame of the new view controller and the end frame
// for the old view controller. Both rectangles are offscreen.r
newVC.view.frame = view.frame.offsetBy(dx: view.frame.width, dy: 0)
let endFrame = view.frame.offsetBy(dx: -view.frame.width, dy: 0)
// Queue up the transition animation.
self.transition(from: oldVC, to: newVC, duration: 0.25, animations: {
newVC.view.frame = oldVC.view.frame
oldVC.view.frame = endFrame
}) { (_: Bool) in
oldVC.removeFromParentViewController()
newVC.didMove(toParentViewController: self)
}
}
Swift 5.1
To Add:
let controller = storyboard?.instantiateViewController(withIdentifier: "MyViewControllerId")
addChild(controller!)
controller!.view.frame = self.containerView.bounds
self.containerView.addSubview((controller?.view)!)
controller?.didMove(toParent: self)
To remove:
self.containerView.subviews.forEach({$0.removeFromSuperview()})