How to programmatically populate a static UITableView from the storyboard? - ios

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.

Related

SWIFT: Connect UITableViewCell to two different detail view controllers

Suppose I have a SWIFT app and it contains a UIViewController with a table. The table of course has prototype UITableViewCells. The information contained in the cell can be one of two internal object types, lets say Widget and Sprocket. Widget and Sprocket are objects that derive from the same base class Thing.
My table will be a list of Things, where each Thing is either a Widget or a Sprocket. What I want to happen is that if a user selects a table cell that is a Widget, it should show a details ViewController for a Widget, ie WidgetViewController. If however the user selects a Sprocket then the app should show a SprocketViewController.
How exactly can I make this happen? My understanding is that if I go into the storyboard and click-drag to make a segue from the main VC to either WidgetViewController or SprocketViewController then that segue will occur in the app automagically, ie without me adding any code. So if I click-drag to create two such segues then I have no idea what will happen but I assume that the app will crash from trying to call both segues.
The problem I am facing is that my current app has a WidgetTableViewController with a storyboard segue to a WidgetViewController and also has a SprocketTableViewController with a storyboard segue to a SprocketViewController, but now I have to put Widgets and Sprockets into the same VC (ie ThingTableViewController) and have the app conditionally launch either WidgetViewController or SprocketViewController.
So how do I do this?
One way to do this could be:
In the tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) function of your UITableViewDelegate check if the selected cell corresponds to an object Widget or an object Sprocket
Then, present the corresponding UIViewController with the necessary configuration with this code:
let vc = UIStoryboard(name: "The name of your storyboard here", bundle: nil).instantiateViewController(withIdentifier: "Your VC identifier") as! YourViewController
// pass your data and configure the viewcontroller here
navigationController?.pushViewController(vc, animated: true)
In storyboard, assign the identifier that you think is convenient to your ViewController:
Select the ViewController -> 3rd item -> Identity -> StoryboardId and check "Use Storyboard ID"
Note: Delete the segues what are you currently using
1) To configure you cells you have your DataSource. So basically you could just configure your tableView with array of structures like this.
struct ViewModel {
var type: CellType
...
}
When you tap your cell with type you could easily find what detail controller you need.
2) In didSelectRow you cold get tapped cell
let cell = tableView.cellForRow(at: indexPath)
and than just check if its is WidgetsCell or SprocketsCell

Is there any way to scroll to the selected UICollectionViewCell in a collection?

I've got a UICollectionView in a modal view controller in my app. When the modal view is brought up, one of the collection's cells is set as selected based on certain data I pass into the modal view from my home view.
I need to programmatically scroll the selected collection cell into view once the modal view with the collection appears... but using scrollToItem(at:at:animated:) while the collection view is being populated (in cellForItemAt) doesn't seem to work.
So while I could easily just use the indexPath available to me in cellForItemAt to scroll to the selected cell while populating the collection, since that isn't possible, I can't figure out how to scroll to the currently selected collection cell after the collection is fully populated and presented.
I can't even use a heavy-handed approach like looping through all collection cells and checking which is selected manually, since it doesn't appear possible to loop through cells that aren't currently visible.
Help?
Add a variable on Top.
private var didLayoutFlag: Bool = false
Just pass the index number (indexNO) in viewDidLayoutSubviews method. I am using horizontal scrolling.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if self.colllecitonView != nil {
if !self.didLayoutFlag {
self.colllecitonView.scrollToItem(at:IndexPath(item: indexNO, section:0), at:.right, animated: false)
self.didLayoutFlag = true
}
}
}

Segue to a New Instance of the Current View Controller

I have a storyboard view embedded in a navigation controller that displays a record from a database along with a link to view a related record. The related record needs to use the same view to display its data while still maintaining a navigation stack so the user can go back to the previous record. Keeping in mind that some data needs to be passed to the new viewController and the UI is composed of a tableView with each element in a row, how can this segue be accomplished?
Below is the view. If possible, please respond with any sample code in Swift.
With some inspiration from this answer and guidance by #Jassi, here is the final product:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let vc = storyboard?.instantiateViewControllerWithIdentifier("inventoryItemDetail") as InventoryDetail
vc.fmRecordId = item["inContainerRecordId"]! //this is the data which will be passed to the new vc
self.navigationController?.pushViewController(vc, animated: true)
}
An idea-
Give the view controller an identifier. And override the below function.
prepareForSegue
In that function instantiate the view controller using the identifier you have and then pass the necessary data to that controller. And push it on navigation controller.
I hope it will work.

Reusing a detail UIViewController involved in a storyboard segue

I have a master/detail application running on an iPad. When in landscape mode, I have both views up side-by-side. The right/detail view controller contains an MKMapView.
The issue is that when selecting a different table cell in the left/master view controller, and essentially re-performing the segue, the entire detail view controller is reinstantiated.
This means that the MKMapView I was using loses the user's position, and essentially starts from scratch, zooming in from the country scale to the street scale.
Is there a way to determine, prior to performing the segue, whether the detail view being displayed is already the one I want, and simply providing it new data and telling it to refresh?
For example:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
segueParkName = parkNames[indexPath.row]
self.performSegueWithIdentifier("showParkDetails", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showParkDetails" {
let controller = (segue.destinationViewController as UINavigationController).topViewController as ParkDetailsController
NSLog("Controller: \(controller)") // Different instance every time!
controller.parkName = segueParkName
}
}
I would like to either:
Somehow tell iOS that by the time prepareForSegue is reached, I'm okay with being provided a reused view controller, especially (!) if it's already displayed.
In the didSelectRowAtIndexPath method, perform a custom segue and do my own pushing. But I really like the idea of using the built-in system segues so I don't have to be specific about what I'm pushing and where. It seems more device-agnostic to use Show Detail (eg. Replace) than defining my own.
I think, in your first suggestion, it will be troublesome if not impossible to abandon the segue once you are in prepareForSegue. So I would go with your second option. But you don't need to trigger a segue at all, if the detail viewController you want is already in place. So rather than
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
segueParkName = parkNames[indexPath.row]
self.performSegueWithIdentifier("showParkDetails", sender: self)
}
you might have something like...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
segueParkName = parkNames[indexPath.row]
self.detailViewController.parkName = segueParkName
}
This assumes that you already have a property detailViewController pointing to your detail ViewController. It also assumes that the detailViewController will always be the one you need - if necessary, check the detailViewController class to see whether it is the MKMapView you want. Finally, if setting parkName doesn't achieve everything you need (e.g. animating the change), then just implement a new method in your MkMapView and call that in place of setting parkName.
EDIT Just to expand on that, you can use:
if self.detailViewController.isKindOfClass(yourMKMapViewSubclass) {
self.detailViewController.parkName = segueParkName
}
to test whether detailViewController is indeed your MkMapView.
You can cancel a segue by implementing shouldPerformSegue however that is for the case where the park name is invalid for some reason, to prevent showing a view controller for an invalid park.
In this case the solution is use the reference to the detail controller in your master controller that the built-in master/detail template does for you. Then in prepareForSegue take the map from the old detail controller and put it on the new one.
As your app gets more complex it may no longer be suitable for the master to maintain a reference to the detail controller. For example, if you make a root controller that pushes a new master, then the master will not find the detail when the app is in portrait like the template app can. Thus in that case your class that implements the split controller delegate can also maintain the context for your master/detail (something that is initWithSplitViewController). By setting an owningContext param self on the splitViewController via a category in the init for this class, then you can access it from where you need to. E.g. setting the mapView on it from the master. And getting the mapView from it in the loadView of the detail.

How to build swipe view controller in swift

I have a UITableViewController and when I click on it, it will show a DetailViewUIController.
I want to add a functionality which when I am in DetailViewUIController and I swipe to right, it will show the DetailViewUIController of next item and when left, it will show the previous item.
I find a link which kind of do that in swift. But it only has 3 static subviewcontroller.
https://medium.com/swift-programming/ios-swipe-view-with-swift-44fa83a2e078
The number of entries in my TableView can be pretty long, how can I do the same thing dynamically, i.e. without having static number of subviewcontroller created and add as 'addChildViewController'?
Update:
Thanks again #rdelmar for your help.
I am having trouble getting the ' set the contentOffset of the collection view to (collection view width) * selectedRow' to work.
In my prepareForSegue function, I have added:
x = Float(indexPath.row) * 375.0
var point = CGPointMake(CGFloat(x), 0)
println ("***x=")
println (x)
detailController.collectionView?.setContentOffset(point , animated: false)
where detailController is UICollectionViewController.
and in the
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as DetailViewCell
// Configure the cell
println("DetailViewController in cellForItemAtIndexPath()")
println(indexPath.row)
cell.myitem = allItems[indexPath.row]
return cell
}
And I always see cellForItemAtIndexPath trying to get 0th element of the allItems (that is the whole collections of all my objects.
So setContentOffset() does not change what I am displaying in the Detail View regardless which item I click in my TableView to launch the Detail View.
Any idea to solve this?
Thank you.
There are a lot of different ways you could do this. One way would be to make your detail view controller's view be a collection view (with paging enabled), whose cells are the same size as the screen. You would need to pass in the array that you use to populate your table so the collection view could populate its cells. This would be quite efficient, since the collection view would only ever need to instantiate two cells.

Resources