I'm trying to make a popup like I described in my previous question. I actually got an answer, but now I have a new problem. I can make the view appear if I don't make the instantiateWithOwner, but it is not responding to anything (just frozen).
In short, I have set up a 'popup.xib' file, which is just a view with a button and a label. My code below should make it appear and disappear with button clicks.
I have read the documentation that the instantiateWithOwner does all the magic of connecting the view to it's callback buttons, so it makes sense that nothing happens when it's not in the code. (reference)
Thing is that if I do include it in my code, I get a compiler error 'PopupViewConrtoller' does not have a member named 'instantiateWithOwner'.
I have tried searching the auto-complete list, but found nothing similar.
My code:
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBAction func showPopup(sender: AnyObject) {
// This line makes it appear on the screen, but not respond to anything.
var x = PickerPopupViewConrtoller(nibName: "PickerPopup", bundle: nil)
// This line does not compile.
var x = PickerPopupViewConrtoller(nibName: "PickerPopup", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as? PickerPopupViewConrtoller
x.show(self.view)
}
}
PopupViewController
import UIKit
class PickerPopupViewConrtoller : UIViewController {
func show(tView : UIView) {
tView.addSubview(self.view)
}
#IBAction func done(sender: AnyObject) {
self.view.removeFromSuperview()
}
}
Yes it's correct, instantiateWithOwner is not a UIViewController method, it is a UINib method.
You have to create a UINib object and then cast it to your UIViewController class
ex:
UINib( nibName: nibNamed, bundle: bundle).instantiateWithOwner(nil, options: nil)[0] as? UIViewController
That's why I use the extension I wrote in the previous answer, it's easier and more readable.
instantiateWithOwner is a method on UINib. You are calling it on a UIViewController instance.
The correct code would be along the following lines:
UINib(nibName: 'PickerPopup', bundle: UIBundle.mainBundle().instantiateWithOwner(nil, options: nil)[0] as? PickerPopupViewController
The instantiateWithOwner method will actually call the PickerPopupViewController constructor (PickerPopupViewController.init(coder:))
Related
I wish to create a concrete class from UIViewController type, something like this
func create(with type : UIViewController.Type)->UIViewController{
return type.init(coder: NSCoder())!
}
Apparently, UIViewController's designated initializer is only init(coder : NSCoder). And, when I try to pass in NSCoder() (as shown in the above case), the app crashes.
Anyone knows a better solution in creating a UIViewController concrete class from its type? Or am I pass in the wrong NSCoder in this case?
Code completion does not show this option, but this compiles and runs without a problem:
func create(with type : UIViewController.Type) -> UIViewController {
return type.init()
}
If you just want to create the view controller programmatically or from a XIB then just use the base constructor.
let viewController = MyViewController()
If you have a XIB with the same file name as the class then it will load it automatically.
If you want to load it from a storyboard then you need to define an identifier for the view controller in the storyboard and then call:
let storyboard = UIStoryboard(name: "storyboardName", bundle: nil)
storyboard.instantiateViewController(withIdentifier: "myViewControllerIdentifier")
EDIT
If you want a special create function, you can actually create an extension for UIViewController like this:
extension UIViewController {
static func create() -> Self {
return self.init()
}
}
Then you can call let myViewController = MyViewController.create(). However, unless you want to do something special in that create function it's a bit unnecessary.
I have created on custom View which contain one label and button
I have created outlet for both in UIView class
I have one UIViewController in which i have subview that custom view
now i want to change label name and want to do something on button click.
let commonView = UINib(nibName: "CommonView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! CommonView
firstView.addSubview(commonView)
commonView.lblCommon.text = "We can change label in particular Screen"
I am able to change label text but how can i get button action event in UIViewcontroller?
Use protocol
In your customView declare a protocol
protocol CustomViewProtocol : NSObjectProtocol{
func buttonTapped()
}
Now create a variable in custom view
weak var delegate : CustomViewProtocol? = nil
In your viewController confirm to protocol
extension ViewController : CustomViewProtocol {
func buttonTapped() {
//do whatever u waana do here
}
}
set self as delegate in view controller
let commonView = UINib(nibName: "CommonView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! CommonView
firstView.addSubview(commonView)
commonView.delegate = self
finally in IBAction of button in custom view trigger delegate
self.delegate?.buttonTapped()
In Swift 4:
If you want to catch the tap within your UIViewController do this in the viewDidLoad() after you instantiate your view from a nib (eg called aView):
let tap = UITapGestureRecognizer.init(target: self, action: #selector(tapAction))
aView.addGestureRecognizer(tap)
Below viewDidLoad declare the following function and do whatever you want within its body:
#objc func tapAction() {
//Some action here
}
(A note: In Swift 4 the #objc annotation is obligatory, because it denotes that you want the function to be visible to the Obj-C runtime, which in UIKit handles (among other things) user interactions through its messaging system.)
Since this UIView instance belongs in the view hierarchy of this UIViewController it makes sense to handle the taps within this UIViewController. This gives you the ability to reuse this UIView within any other UIViewController and handle its tap logic from the respective UIViewController.
In case there is a particular reason you want the taps handled within the UIView, you can use the delegate pattern, a closure, or in a more advanced implementation a Reactive framework (ReactiveSwift or RxSwift/RxCocoa)
The way to do it with RxSwift/RxCocoa:
view.rx
.tapGesture()
.when(.recognized)
.subscribe(onNext: { _ in
//react to taps
})
.disposed(by: stepBag)
more ways to manage Gestures, can be found at the RxSwift github page: https://github.com/RxSwiftCommunity/RxGesture
I hope someone can help me with this problem I am getting with swift.
I am trying to add an array of UIViewControllers to a UIPageViewController. However, whenever I try to access a view controller through the method instantiateViewController, I get a SIGABRT error.
let vc: UIViewController = self.storyboard?.instantiateViewController(withIdentifier: views[index]) as! UIPageViewController
Here is my entire ViewController.swift file just for the reference.
import UIKit
class ViewController: UIViewController {
var pageViewController: UIPageViewController!
var views: [String] = ["view1","view2"]
override func viewDidLoad() {
super.viewDidLoad()
self.pageViewController = self.storyboard?.instantiateViewController(withIdentifier: "pageViewController") as! UIPageViewController
var arr: [UIViewController] = []
for i in 0..<views.count{
arr.append(assignView(index: i))
}
self.pageViewController.setViewControllers(arr, direction: .forward, animated: true, completion: nil )
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
pageViewController.didMove(toParentViewController: self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func assignView (index: Int) -> UIViewController{
let vc: UIViewController = self.storyboard?.instantiateViewController(withIdentifier: views[index]) as! UIPageViewController
return vc
}
}
Can someone tell me why my code is throwing me this error?
Thank you so much!
From the line below:
self.storyboard?.instantiateViewController(withIdentifier: "pageViewController") as! UIPageViewController
It seems that storyboard is treated as optional. It may be that the storyboard variable is getting nil when you are trying to access it.Ideally it should not be nil and should not be optional.
If the optional is purposefully done then you can apply a "if let" to check before you access.
Let me know if you need further clarification.
SIGABRT is a controlled crash and the app terminated mostly on purpose. It mostly resulted from programmer's fault.
Look for following as there is two possible causes to crash.
self.storyboard doesn't properly got initialised as there is no corresponding storyboard or incorrectly referred.
View controller's identifier is incorrectly referred.
Consider the following example
var mainView: UIStoryboard!
mainView = UIStoryboard(name: "Main", bundle: nil)
let samplecontroller : UIViewController = mainView.instantiateViewController(withIdentifier: "iPhone")
In the above example mainView is my instance referring to StoryBoard which is implicitly unwrapped(this is done on purpose to specify that this cannot be nil)
samplecontroller is the instance of the scene in the storyBoard. "iPhone" is the name of the StoryBoardID of the scene.
This snippet will ensure that the mainView will never be nil provided my storyboard is setup correctly with the Ids and names used.
After this you can still have a safety check like
if let test = mainView {
//Do your stuff
}
Let me know if this helps.
I have two classes:
class ExplorerViewController: UIViewController {
lazy var studyButton: ExploreButton = {
let button = ExploreButton()
button.setTitle("Study", forState: .Normal)
return button
}()
}
and
class ViewController: UIViewController, UISearchBarDelegate, LocateOnTheMap, GMSMapViewDelegate {
}
I'm trying to make it so that when I click the studyButton, it sends the button title to ViewController and goes to that view.
I'm not using storyboards and am having trouble with segues since every tutorial seems to give different examples that are specific to the things they've been working with and 95% of them seem to be operating with storyboard. Can someone give me a general way of how to do this?
How do I give the starting view controller an identifier because it isn't instantiated like the other controllers that I 'move' to after. How can I move from ViewController to ExplorerViewController and then move back to that same ViewController (with all changes intact).
Create an initializer for your ViewController that receives the "title" variable:
class ViewController: UIViewController, UISearchBarDelegate, LocateOnTheMap, GMSMapViewDelegate {
var btnTitle: String?
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?, btnTitle:String?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.btnTitle = btnTitle
}
}
When creating the ViewController object use this initializer.
var viewController = ViewController(nibName: "ViewController", bundle: nil, btnTitle: title
You can initialize UIViewController that you want navigate to, assign data to properties in that controller and call this method:
presentViewController(viewController, animated: true, completion: nil)
For example:
let destinationViewController = ViewController()
destinationViewController.frame = self.view.frame
destinationViewController.buttonTitle = "title"
self.presentViewController(destinationViewController, animated: true, completion: nil)
Although I would suggest you to get familiar with Storyboards and perform navigation with Segues.
Make sure of two things:-
1.) You have given your viewController an StoryBoard ID lets say "viewControllerVC_ID" in it's Identity inspector
2.) You have NavigationController Embed in to your Initial entry point View Controller
In ViewController declare a variable
var btnLabelTxt : String!
Create an #IBAction of that button in ExplorerViewController :-
#IBAction func exploreBtnAction(sender : UIButton!){
let vcScene = self.navigationController?.storyboard?.instantiateViewControllerWithIdentifier("viewControllerVC_ID") as! ViewController
vcScene.btnLabelTxt = "Study"
//or you can just access the button itself in the viewController and set the title
//By vcScene.yourBtn.setTitle("Study", forState: .Normal)
self.navigationController?.pushViewController(vcScene, animated: true)
}
please see this question How to push viewcontroller ( view controller )? for how to switch between views.
to pass data once you have reference to the new view, you can assign the data to a property of that view.
I'm trying to make my App to automatically go back to the main menu after 2 minutes of inactivity. So far I have both parts working, but not together..
The App starts a counter if there's no touch input:
see user4806509's anwer on Detecting when an app is active or inactive through touches in Swift
And from my main viewcontroller I can control the segue I need with code:
func goToMenu()
{
performSegueWithIdentifier("backToMenu", sender: self)
}
I've implemented the code from Rob's answer on How to call performSegueWithIdentifier from xib?
So I've created the following class and protocol:
protocol CustomViewDelegate: class {
func goToMenu()
}
class CustomView: UIView
{
weak var delegate: CustomViewDelegate?
func go() {
delegate?.goToMenu()
}
}
The Function go() gets (successfully) called when the timer runs out. But the delegate?.goToMenu() doesn't work. If I change it to: delegate!.goToMenu(), the App crashes with:
fatal error: unexpectedly found nil while unwrapping an Optional value
My main viewcontroller is a CustomViewDelegate and the viewDidLoad contains:
let myCustomView = NSBundle.mainBundle().loadNibNamed("customView", owner: self, options: nil)[0] as! CustomView
myCustomView.delegate = self
I have the correct xib file, and that part is working.
I can't seem to find the solution to this seemingly easy problem, does anyone have a fix? Or better yet, a more elegant solution to my problem?
Thank you!
edit: SOLUTION:
I've removed all my old code and implemented the NSNotification method:
In the UIApplication:
let CallForUnwindSegue = "nl.timfi.unwind"
func delayedAction()
{
NSNotificationCenter.defaultCenter().postNotificationName(CallForUnwindSegue, object: nil)
}
In the main ViewController:
let CallForUnwindSegue = "nl.timfi.unwind"
func goToMenu(notification: NSNotification)
{
performSegueWithIdentifier("backToMenu", sender: self)
}
deinit
{
NSNotificationCenter.defaultCenter().removeObserver(self)
}
In the viewDidLoad: NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PeriodViewController.goToMenu), name:CallForUnwindSegue , object: nil)
I believe that your issue is that because your delegate is declared as a weak var, your delegate is getting disposed while you wait for the timer to complete, thus the reference to the optional is lost (and returns nil when you force unwrap it).
try removing the weak keyword from your CustomView class.
Another solution would be to use the notification center to notify your view to call for the segue.
Something along the lines of what I proposed here
I hope this helps.