How to use a predefined view (nib) in a UITableViewCell? - ios

My list consists of card views with an image and some text fields, all of which have a complex layout. This card is to be reused in the detail view, so containing it inside a UITableViewCell layout and then calling UITableView.registerNib() doesn't seem to work for me. I'd like to know the cleanest way to reuse the view I created in the NIB-file as both a list item and a part of my detail screen, while keeping as much of the view logic such as constraints and style in the NIB-file.

I'd like to know the cleanest way to reuse the view I created in the NIB-file as both a list item and a part of my detail screen
Do exactly what you're already doing: Use a nib (a .xib file) containing just one view, the UITableViewCell as designed, and register that nib with the table view - exactly the thing you say "doesn't seem to work for you". It does work. So now all your table rows consist of copies of this table view cell.
But there's the trick. When you want to show the view elsewhere, load the nib manually, pull out the view (the UITableViewCell), pull out its contentView, and add it as a subview to your interface.
// substitute correct name of xib file here
let arr = UINib(nibName: "MyTableViewCell", bundle: nil).instantiateWithOwner(
nil, options: nil)
let tvc = arr[0] as! UITableViewCell
let v = tvc.contentView // now it's a normal view! customize as needed
v.frame.origin = CGPointMake(100,100) // or whatever
self.view.addSubview(v) // and plop it into the interface! voilĂ !

Related

Collection View returns nil when called in function

I want to run a function which involves adding a sublayer to a collection view. However when I run the function the app crashes saying Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value When I print the collection view it shows up in the log as none so I know the collection view is the problem. The view has already been loaded when I call the function and I can see all of it's cells. The function is being called from another class, which I think might have something to do with the problem.
Here is the function that I am calling...
func displayCircle() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
//change the fill color
shapeLayer.fillColor = UIColor.green.cgColor
//you can change the stroke color
shapeLayer.strokeColor = UIColor.green.cgColor
//you can change the line width
shapeLayer.lineWidth = 3.0
print(shapeLayer)
print(collectionView)
collectionView!.layer.addSublayer(shapeLayer)
}
Here is how I am calling this function from another class...
ViewController().displayCircle()
EDIT: This is my storyboard layout...
What could the problem be?
As you can see, I am using a page view controller. Hope this helps
There's likely a few issues here.
If you wanted to say ViewController.displayCircle() then displayCircle would need to be a static function. But I don't think that was your intention, you probably don't want to do that in this case, and also your static function syntax is wrong (ViewController().displayCircle() is wrong). But moving on... :)
ViewController().displayCircle() isn't how you properly reference the collectionView. First you need a reference to the other view controller. Then inside displayCircle you need to grab a reference to the collectionView if it's in another View Controller. So that would be otherViewController.collectionView provided the collectionView is public of course, and provided you have a reference to that other view controller somehow. Note that you can't just make a new reference of the other view controller, otherwise you'll be adjusting the layer on the new instance, not the original.
Last but not least, you're force unwrapping the collectionView - don't do that. Your app will crash if it's ever nil. Instead, take advantage of Swift's paradigms:
if let collView = collectionView {
collView.layer...// etc
}
This last bit isn't the issue, but just good practice.
If the collectionView is part of this same viewController, use self:
self.collectionView.layer.addSublayer(shapeLayer)
If the collectionView is in a different viewController, as it seems to be, you would need to get a reference to that view controller. How you do this depends on the structure of your app. You mention using a UIPageViewController and presumably both the view controllers are presented on it.
From one view controller, you can refer to another like this:
let pvc = self.parent as? UIPageViewController // Or your custom class
let targetViewController = pvc.viewControllers[index] as? YourTargetViewControllerClass
You might need to figure out what index you need. An alternative is to make sure each child view controller of the UIPageViewController has its own subclass, then find the one you want like this:
let pvc = self.parent as? UIPageViewController
let viewControllers = pvc.viewControllers.filter { $0 is CustomSubclass }
if let viewController = viewControllers.first as? CustomSubclass {
viewController.displayCircle()
}
As the other answer states, using ViewController() creates a brand new view controller instance, not the one that already exists.
This
ViewController()
creates a new instance other than the presented one ,when you reference the collectionView from it it's actually nil as you have to load the VC either from storyboard / xib , so you have to use delegate to reference the current VC that contains the collectionView

Replace ViewController's uiview with xib

I am struggling to figure out how to load a xib from within a storyboard using Swift in XCode.
My main storyboard (Called TabBarNav.storyboard) is a Tab Bar View controller with 4 items (Home, Weight, Meals and Calories).
I have created a seperate XIB UIView called ViewWelcome.xib with corresponding class file ViewWelcome.swift.
The Home items view controller has a class file called "ViewControllerHome.swift"
When the Home tab bar item is touched I want to replace the existing view with the one within ViewWelcome.xib.
In ViewWelcome.xib I have made the files owner ViewControllerHome but when I run the app it is still showing the view that was created with the Home item when I originally created the storyboard.
Reason I am doing it this way: (A Mix of storyboard and xibs) I wanted each section (Home, Weight, Meals and Calories) to be seperate from the storyboard so that the SB doesnt become cluttered and to also later avoid merge issues in git when more than one person works on interface files.
Simplest way is to create & add your nib view in viewDidLoad
func viewDidLoad() {
super.viewDidLoad()
// create and add view from nib
if let viewWelcome = UINib(nibName: " ViewWelcome", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as? ViewWelcome {
viewWelcome.frame.size = self.view.size
self.view.addSubview(viewWelcome)
}
}
Otherwise refer to this
This is working for me . Give a try . .
let newView = Bundle.main.loadNibNamed("ViewWelcome", owner: self, options: nil)?.first as! ViewWelcome
self.view = newView

Simultaneously change display parameters on all table view cells

I am trying to implement a table view design where a user can click a button outside of a table view cell and the display mode of all the buttons should change. However this is not the 'selected' mode for a given cell (that will be yet a third state that becomes accessible via switching to this second state). What's the proper way to accomplish this?
I am using dequeueReusableCellWith so I don't want to simply cycle through every cell because some that are out of sight probably shouldn't be modified. I simply want any cell that is visible, or becomes visible, while the table view cell is in this second display mode to follow a second design rather than the first design.
The second design, for now, is being modified via a method I added to a subclass of UITableViewCell like so:
- (void) p_refreshDisplay {
if (self.editing) {
self.buttonToClearWidth.constant = 20;
self.buttonToClearLeadingWidth.constant = 20;
} else {
self.buttonToClearWidth.constant = 0;
self.buttonToClearLeadingWidth.constant = 0;
}
}
However, I'm not sure how to trigger this p_refreshDisplay for every visible (and to become visible) cell. It seems unwise to call this many times and refresh the table view. What would be the proper way to accomplish what I want to do?
You do what should be done for any table view change:
Update your data model or some flag as needed.
Either call reloadData on the table view or call reloadRowsAtIndexPaths:withRowAnimation: passing in indexPathsForVisibleRows as the list of rows to reload.
Implement cellForRowAtIndexPath to provide appropriate cells for the given data/flags.
It sounds like you should have a custom cell class that has one or more properties that can be set on the cell in cellForRowAtIndexPath so the cell can render itself properly based on the specified state.
You can achieve this by doing three things:
Establish some property that indicates the "mode" of the table, either a boolean or perhaps an enum if there are more than three states
Ensure that cellForRowAtIndexPath configures the cell appropriately based on the value of this property. This will ensure that newly displayed cells are configured correctly.
When the "mode" changes you can use the tableview's visibleCells property to update any currently visible cells:
for cell in tableview.visibleCells {
if let myCell = cell as? MyCustomCellClass {
myCell.setButtonStyle()
}
}

How to programmatically populate a static UITableView from the storyboard?

I am trying to create a UITableView similar to the About section of the iOS settings. I have my initial table view, which I populate dynamically (already working), and which has disclosure indicators for each of its items. When I click on an item in this view, I tell the navigation controller to slide over to a static UITableView (pictured below). I instantiate the static table view from the storyboard using its ID, and before I transition to it, I'd like to fill it with dynamic values according to which item it's describing from the initial table view. Basically, how do I set the Right Detail portions of cells in a static table view in my code.?
The pseudocode I'm looking for goes something like this (exists inside the controller class for the initial table view):
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let customData: [String:String] = getDataForCell(atIndexPath: indexPath)
let descriptionView = storyboard?.instantiateViewControllerWithIdentifier("keyDetails") as! KeyDetailsController
descriptionView.getCell(forRow: 0, inSection: 0).rightDetail.text = customData["serverName"]! // changes "A Fake Server" to an actual server name
}
Just to summarize our discussion in chat: To update those labels, you just create IBOutlet reference to the labels.
The key issue here is that you're trying to update these outlets immediately after instantiating the view controller. But the outlets are not configured until the view is loaded, something that happens during the process of transitioning to the new scene. So you should:
Create String (or whatever) properties in the destination controller;
Populate these properties after the view controller is instantiated. Where yo do this depends upon how you're transitioning to the next scene:
If transitioning to the next scene programmatically (via showViewController, presentViewController or navigationController.pushViewController), then set these right after you call instantiateViewControllerWithIdentifier; or
If using a segue, set these String properties in prepareForSegue.
In viewDidLoad of the destination view controller, take these String properties and update the controls for which you have `IBOutlet references.
For more information, see Passing Data between View Controllers.

How to create multiple UIViews using the same function

I would like to create multiple UIViews that can be reproduced by using a single function. I have a UIView that is placed on a storyboard and connected to my class with an IBOutlet:
#IBOutlet weak var contentView: UIView!
I have a function that loads a xib into my UIView:
func createView(layoutConstant: CGFloat) {
if let customView = NSBundle.mainBundle().loadNibNamed("TestView", owner: self, options: nil).first as? TestView {
contentViewTopLayoutConstraint.constant = layoutConstant
contentView.addSubview(customView)
}
}
I am now trying to add two of them to my view, but only one shows up:
createView(0)
createView(70)
Any ideas?
I think both views are added, although they happen to be in the same spot, so it looks like there is only one! A quick and dirty way to verify that would be updating your createView method with this line:
contentView.frame.origin.y = layoutConstant
Basically your contentViewTopLayoutConstraint is not connected to the views you are creating, so setting its constant value will not have any impact.
Because frames for all those views will of same size. Origin(x,y) will be same for all the views, so they are overlapping one on another and you can only see the top one view.
In your code example it looks like you're only setting a layout constraint on the contentView you are placing your two new views inside of. What you need to do is set layout constraints on the two views your are placing inside in relation to their superview i.e. the contentView.
Basically, add the layout constraints to the customView views.
its quite simple.. iterate a loop by creating uiview along with adding those into the array and customize your particular view by getting them using array index.
Happy code ..

Resources