UIViewController xib file containing a collectionview with a custom cell - ios

I have a xib file containing an UIViewController called FullScreenGalleryVC. It contains a UICollectionView that contains a custom UICollectionViewCell, which identifier is fullscreen_cell.
When I want to create a new FullScreenGalleryVC, I call FullscreenGalleryVC.display(from: self).
Here's the func:
class func display(from sourcevc:UIViewController){
let vc=UINib(nibName: "FullscreenGalleryVC", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! FullscreenGalleryVC
sourcevc.present(vc, animated: true, completion: nil)
}
I get this error:
could not dequeue a view of kind: UICollectionElementKindCell with
identifier fullscreen_cell - must register a nib or a class for the
identifier or connect a prototype cell in a storyboard
It works fine if I put the custom cell in another xib file and call register(nibClass, forCellWithReuseIdentifier: cellId).
It works fine if I don't put this FullScreenGalleryVC class in a separate xib file (and keep it in my main storyboard).
But I use this class from both the app and an action extension so that's why I'd like to use a common file instead of duplicating everything. Is there a way to do that or do I imperatively have to put the custom cell in a different xib file to make it work?

If you a xib file for FullScreenGalleryVC controller then you should use register(nibClass, forCellWithReuseIdentifier: cellId) to tell the collection view how to create a new cell of the given type.
let cellNib = UINib(nibName: "FullScreenGalleryCell", bundle: .main)
collectionView.register(cellNib, forCellWithReuseIdentifier: "fullscreen_cell")
If you use a storyboard, then it's already known to the collection view with the specified cellId.
In my opinion, you can use a storyboard for FullScreenGalleryVC and reuse the same for other classes.
Present a viewController from a specific storyboard like,
let storyboard = UIStoryboard(name: "YourStoryboardName", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "storyboardId")
self.present(controller, animated: true, completion: nil)

Related

Reusable nib for multiple classes

I have a case in which I would like to use one NIB (for UITableViewCell to be precise) for multiple classes. I have tried to define outlets as 'File owner' as then instantiate the cell, but I always receive crash, where xCode telling me that class is not key/value compliant for some outlet.
So my question is: is it possible to use one NIB, define outlets and then instantiate nib & class in a way, that Outlets are linked to the class?
The code I have tried to use:
let cell = UINib(nibName: "SampleTableViewCell", bundle: nil).instantiate(withOwner: DummyTableViewCell.self, options: nil).first as? SampleTableViewCell
The SampleTableViewCell nib has UITableViewCell defined for cell class (default) and FileOwner as NSObjet (default again). All IBOutlet are linked as FileOwner.

How to Open Multiple Xibs with the same method?

I have configured my UIView using Nib's .So, I just want to pass the name of nib in a method and it will open the particular ViewController.
For opening a particular UIView made with Xib, I have done it like this
func presentMyViewControllerNib(){
let controller = MyViewController(nibName: "MyViewController", bundle: nil)
let navigationController = UINavigationController(rootViewController: controller)
let currentController = getCurrentViewController()
currentController!.present(navigationController, animated: false, completion: nil)
getCurrentViewController here is just fetching the viewcontroller displayed.
So, i just want a method like this func presentGeneralisedNib(nibName:String) in which I pass the name of my Xib.
I think it's not possible but if someone can help.

Swift: UI Elements equal nil when programatically creating split view controller

I am programmatically creating a split view controller using the following code when a table view cell is touched:
let rootViewController: UIViewController = RootTableViewController()
let navVC: UINavigationController = UINavigationController(rootViewController: rootViewController)
let detailViewController: UIViewController = DetailTableViewController()
let splitVC: UISplitViewController = UISplitViewController()
splitVC.viewControllers = [navVC, detailViewController]
self.present(splitVC, animated: true, completion: nil)
but when I tap the tableViewCell I get the error: 'fatal error: unexpectedly found nil while unwrapping an Optional value' which appears to be linked to a UITextField (and all UI Elements) on the RootTableViewController. The first failure is in the viewDidLoad of RootViewController after executing the above code when a value is passed to a ui element
Where exactly does this happen? Wheres the error originated? I had a similar issue where I tried to access IBOutlets before they were created by the system which caused my app to crash.
Specifically I had a UI update function which was called after setting a property of the ViewController.
I got around this by checking for nil in the update function and since it was called before viewDidLoad was called the first time, I called the update function in viewDidLoad manually to make sure that when it shows for the first time, everything is correctly updated.
UPDATE
I think I have an idea of whats going on. You created a Storyboard, setup your UI and chose your ViewControllers as the classes of the ViewControllers in the Storyboard.
If you now want to use the ViewControllers, you have to instantiate them via the Storyboard rather than manually initializing them (something like this):
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "someViewController")
You also could use Segues instead of instantiating something at all. Build your complete UI using the Storyboard and then use a Segue to present the SplitViewController.
The last method I can think of is, that if you want to instantiate the ViewControllers manually and still make use of the Storyboard or a nib, you have to do some custom initialization in in the init functions of your ViewControllers (this code is from a custom view I have in a separate .xib):
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initializeSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeSubviews()
}
func initializeSubviews() {
UINib(nibName: "DatePickerKeyboard", bundle: nil).instantiate(withOwner: self, options: nil)
addSubview(view)
view.frame = self.bounds
}

How to bring up a xib as a "pop up" in a ViewController?

I have designed a nice Login box as a xib file in Xcode and when a user taps "Log In" on the landing page on my app, I'd like the Login Box to pop up and let them type in their info.
At the moment though I am having difficulties getting it to show up, as this is my first time working with xibs. Any help would be appreciated. It seems that my IBAction isn't connected because I made a second class, which disconnected the login button from the IBAction. Not sure how to go about fixing that.
Thanks in advance! Using Swift 3 and the latest version on Xcode.
import UIKit
class AuthMainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.isHidden = true
}
}
class loginClass: UIView {
class func instanceFromNib() -> UIView {
return UINib(nibName: "LoginView.xib", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
#IBAction func loginBtnTapped(_ sender: Any) {
let loginView = loginClass.instanceFromNib()
self.addSubview(loginView)
print("Login got pressed")
}
}
Problem with accepted answer is that in only takes care of the view itself, which is not enough to keep UIKit functioning fully correctly.
For having fully functional UIViewController, you should use this:
//adding UIViewController to another UIViewController:
addChildViewController(viewController)
view.addSubview(viewController.view)
viewController.view.frame = view.bounds
viewController.didMove(toParentViewController: self)
//removing UIViewController from another UIViewController:
viewController.willMove(toParentViewController: nil)
viewController.view.removeFromSuperview()
viewController.removeFromParentViewController()
Without the other methods you may find malfunctions in many aspect, like:
appearance methods
rotation methods
transition methods
responder chain
navigation controller, tab bar controller properties
Reference:
https://developer.apple.com/reference/uikit/uiviewcontroller
"Implementing a Container View Controller"
Your container view controller must associate a child view controller with itself before adding the child's root view to the view hierarchy
In your project Pop-up controller is a subclass of UIViewController with a couple of methods you can add view like this:
self.popViewController = PopUpViewControllerSwift(nibName: "PopUpViewController", bundle: nil)
self.view.addSubview(popViewController)
You can refer to tutorials for how to create Pop up window in Swift OR Adding a subview using a XIB and Storyboard
This is what you actually want.
Mainly we can use custom .xib to load custom UITableViewCell, UICollectionViewCell and Custom PopUp
Load Custom .xib as a UITableViewCell
Create the pair of custom .xib and its class file something like this.(eg CustomCell.Swift and CustomCell.xib)
go to project navigator and on right click create new class with the name of CustomCell with the subclass of UITableViewCell
go to project navigator and create new .xib file with the same class name
New File.. -> User Interface -> View -> Save as your class name "CustomCell" -> Create
now you will get the .xib file with the subclass of UIView but we need a UITableViewCell so what we will do? we delete that UIView file and add a UITableViewCell and give the class name CustomCell and Identifier too. (Command + Shift + L) UITableView and drag and drop to the xib window & design cell according your project requirement.
now we will move to the code part and initialized .xib in our ViewContoller Class file and load it in the UITableView.
we will get the cell from our bundle and registered the cell with UITableView.
UITableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell")
configured and load the cell in the cellForRowAt indexPath with this
let cell : CustomCell = self.(YourTableView).dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell
return cell
Load custom .xib as a UICollectionViewCell
Note : Repeat above steps replace UITableViewCell with UICollectionViewCell and register UICollectionViewCell like this
self.collectionView.register(UINib(nibName: "CollectoinCustCell", bundle: nil), forCellWithReuseIdentifier: "CollectoinCustCell")
configured and load the cell in the cellForRowAt indexPath with this
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : CollectoinCustCell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectoinCustCell", forIndexPath: indexPath) as! CollectoinCustCell
return cell
}
Custom View as a popup (Comes to an end with the appropriate answer for above question)
create pair of xib file and these time you do not have to remove the UIView from your .xib file.
get the xib from your bundle like:
let CustomXIB = Bundle.main.loadNibNamed("CustomXIB", owner: nil, options: nil)?.first as? CustomXIB
Define the frame of your xib file in viewWillAppear
CustomXIB?.frame = UIScreen.main.bounds
create the appDelegate instance file to add xib file on main window : your AppDelegate will look like this.
var AppInstance: AppDelegate!
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
AppInstance = self
}
}
and add your customXIB on some IBAction like :
AppInstance.window?.addSubview(self.CustomXIB!)
remove CustomXIB like
self.CustomXIB?.removeFromSuperview()

Make view controller in run time with xibs

I want to make view controller at run time with the nib name like this,
But this is being crashed.
Error
[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key imageViewApp.
let vc = UIViewController(nibName: nibName, bundle: nil)
When I try with the static values like this
let vc = MyViewController(nibName: "MyViewController", bundle: nil), it is working.
This is the IBOutlet in MyViewController
I googled it but haven't found enough solution please let me know the solution.
You're initializing a UIViewController and UIViewController has no imageViewApp property. You need to initialize your own subclass where you define that property, as you do in your second example.
Try something like this:
let vc = MyViewController(nibName: nibName, bundle: nil)
If your nib file has the same name as your class, you don't have to specify it:
let vc = MyViewController(nibName: nil, bundle: nil)
Remove imageViewApp property IBOutlet from that view controller and give that ImageView a tag and access it with the tag.

Resources