i'm having a problem where my first view controller is just repeating itself and not showing the second view controller, I've watched videos on how to pass data from one view controller to another and i have it all set up the way its supposed to be. it transfers the data to the second view controller properly and I've tested it with Printing the information I'm passing, but any other ui elements won't show up on the second view controller, i think they are being covered by the table view but it doesn't make sense to me and I'm not sure how to test this.
when i press on a table view cell its supposed to open the second view controller
this is the code that sends and presents the second view controller:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
//open another view contoller and show the recipe
let secondvc = self.display![indexPath.row]
let secondvcresources = secondvc.resource
let secondvcdirections = secondvc.directions
let secondvcname = secondvc.name
let vc = CustomSecondViewController(resources: secondvcresources!, directions: secondvcdirections!, name: secondvcname!)
present(vc,animated: true)
}
this is the second view controller:
import UIKit
class CustomSecondViewController: ViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGray
title = name.uppercased()
let textview = UITextView()
textview.frame = view.bounds
}
private let name: String
private let directions: String
private let resources: String
init(resources: String, directions: String, name: String ){
self.resources = resources
self.directions = directions
self.name = name
super.init(nibName: nil, bundle: nil)
print(resources)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Your current code doesn’t work because your CustomSecondViewController‘s init method,
init(resources:directions:name:)
…doesn’t do anything to load the view controller’s views. A CustomSecondViewController you create with that init won’t have any views, and won’t display to the screen.
If you want to load your CustomSecondViewController’s views from a storyboard, you need to use the function instantiateViewController(withIdentifier:) to create it.
Your rewritten tableView(_:didSelectRowAt:) function might look like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
//open another view contoller and show the recipe
let secondvc = self.display![indexPath.row]
let secondvcresources = secondvc.resource
let secondvcdirections = secondvc.directions
let secondvcname = secondvc.name
guard let vc = self.storyboard?.instantiateViewController(withIdentifier: “CustomSecondViewController”) else {
fatalError(“CAn’t load view controller from storyboard”)
}
// you’ll need to refactor your CustomSecondViewController so it’s properties are public vars, not private “lets”
vc.directions = secondvc.directions
vc.resources = seconded.resources
vc.name = secondvc.name
present(vc,animated: true)
}
Related
This is my screens ....
I want to pass data from Offline View Controller to InstantVC. I don't know how to do that 🤔.
Basically, I have segmented Controller. When user tab Instant it show the segmented view controller and hide the Schedule view controller. And pass data according to selected segmented.
Here is the Offline View controller to pass data
switch response.result {
case .success:
if let result = response.data {
do {
let resultIs = try JSONDecoder().decode(GetActiveOrderModel.self, from:result)
print("Massage: \(resultIs.state)")
if let results = resultIs.orderData{
let storyboard = UIStoryboard(name: "Rider", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "instantVC") as! InstantVC
print(" Order ID is \(results.orderId)")
vc.arryOfOrder.append(results.orderId!)
//navigationController?.pushViewController(vc, animated: true)
}
} catch {
print(error)
}
}
case .failure(let error):
print(error)
}
And here is Instant view controller that restive data from Offline VC
var arryOfOrder = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
print("---------->>>>>>>>> \(arryOfOrder)")
}
// MARK: - Custom Functions
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arryOfOrder.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! CellForInstantVC
cell.orderNum.text = "\(arryOfOrder[indexPath.row])"
return cell
}
func addOrderNumber(orderNumber : Int){
arryOfOrder.append(orderNumber)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
You can use prepare Segue to access Container controller and save its reference
class Master: UIViewController, ContainerToMaster {
#IBOutlet var containerView: UIView!
var containerViewController: Container?
#IBOutlet var labelMaster: UILabel!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "containerViewSegue" {
containerViewController = segue.destinationViewController as? Container
}
}
And then you can use containerViewController to pass data
containerViewController?.arryOfOrder = ArrayOfOrder
or you can have function in container ... pass ArrayOfOrder to that function .. which will also reload tableView
containerViewController?.updateData(dataArray: ArrayOfOrder)
If you want to pass data from parent to child on an action that happened in the parent class, you can access the children of the current UIViewController using
children.forEach { (viewController) in
if let temp = viewController as? InstantVC{
temp.property = dataToPass
}
}
The above will only work if both your view controllers are kept as children to the parent at all times and are never removed. If you remove them as children and re-add them at a later stage you will need to maintain references to the child and update that reference since the children array wont have that UIViewController. But since you are using storyboards, this should work fine.
If you want to access data from on action on the children class, you can access the parent view controller using:
let dataToUse = (parent as? OfflineVC).someProperty
I have an app with three tabs as the root navigation, and there's a table view in the first tab. So when a user clicks on a table cell user should navigate to another View which contains two tabs. How do I achieve this?
Current Storyboard
This is what I have so far. I'm still learning ios development and I want to know if this is possible
Remove the storyboard segue from the table view to the second tab bar controller. And present the second tab bar controller from the table view controller's didSelectRowAt method. And you can pass data to the embedded view controllers like this
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let secondTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "SecondTabBarController") as? SecondTabBarController {
if let navControllers = secondTabBarController.viewControllers as? [UINavigationController] {
if let fourthVC = navControllers.first(where: { $0.topViewController is FourthVC })?.topViewController as? FourthVC {
fourthVC.name = "name"
}
if let fifthVCvc = navControllers.first(where: { $0.topViewController is FifthVC })?.topViewController as? FifthVC {
fifthVCvc.id = 20
}
}
self.present(secondTabBarController, animated: true, completion: nil)
}
}
Replace the class names and storyboard identifiers with proper class names and identifiers
class FourthVC: UIViewController {
var name: String?
override func viewDidLoad() {
super.viewDidLoad()
print(name)
}
}
class FifthVC: UIViewController {
var id: Int?
override func viewDidLoad() {
super.viewDidLoad()
print(id)
}
}
Yes you set any check for that in a single tab controller by this condition which tab you want to show reset from their.
When clicking on the row from the table view, you can use some code like this:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Segue to the other UITabBarController, pass selected information
let selectedCell = tableView.cellForRow(at: indexPath.row)
if let viewController = self.storyboard?.instantiateViewController(withIdentifier: "YourTabBarControllerIdentifier") as? YourTabBarController {
viewController.selectedIndex = 2 // or the index you want by default
viewController.modalTransitionStyle = .crossDissolve // or transition you want
self.present(viewController, animated: true, completion: nil)
}
}
I need to refresh my collectionView when user returns to that VC because what he/she did in the detailVC has affect on the previous VC data. I tried collectionView.reloadData() in both viewDidLoad() and viewDidAppear() of my VC has the collectionView in it. And It came up that when user taps the 'Back' in detailVC both viewDidLoad() and viewDidAppear() do not work. So, I tried to call one of them in detailVC with instantiate the firstVC(which has the collectionView)
then I got an runtime error which said collectionView is nil. Any thoughts? (BTW, the segue between them is ShowPush, and I can not change it because I have to have the transition of this segue in my app.)
Here is the firstVC:
class SkillsController: UIViewController{
#IBOutlet weak var collectionView: UICollectionView!
var TAGS: [TAG] = []
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "TagCell", bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: "tagCell")
self.sizingCell = (nib.instantiate(withOwner: nil, options: nil) as NSArray).firstObject as! TagCell?
self.loadMore()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("back to skills")
self.TAGS = TagManager.shared.tagList
collectionView.reloadData()
}
}
TAGS is my data which is stored in Realm database.
Here is the detailVC:
class SeeSelectedController: UICollectionViewController {
var TAGS: [TAG] = []
#IBOutlet weak var layout: FSQCollectionViewAlignedLayout!
override func viewDidLoad() {
super.viewDidLoad()
if currentTab.shared.isSkill {
self.title = "Selected Skills"
//init tags
let list = RealmManager.shared.skills
if let list = list {
for element in list {
TAGS.append(TAG(n: element.value!, iS: true))
}
}
collectionView?.reloadData()
}else{
self.title = "Selected Needs"
//init tags
let list = RealmManager.shared.needs
if let list = list {
for element in list {
TAGS.append(TAG(n: element.value!, iS: true))
}
}
collectionView?.reloadData()
}
let nib = UINib(nibName: "TagCell", bundle: nil)
collectionView?.register(nib, forCellWithReuseIdentifier: "tagCell")
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = TAGS[indexPath.row].name!
let currentState = TAGS[indexPath.row].isSelected!
TAGS[indexPath.row].isSelected = currentState ? false:true
if currentState {
print("deselect")
//remove from realm
RealmManager.shared.deleteItemFromList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}else{
print("select")
//add to realm
RealmManager.shared.addItemToList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}
if currentTab.shared.isSkill {
let VC: SkillsController = storyboard?.instantiateViewController(withIdentifier: "SkillsController") as! SkillsController
VC.viewDidAppear(true)
}
collectionView.reloadData()
//addd
}
}
So how it is working? in the SkillsVC user can select some tags from a pool, in the detailVC which is SeeSelecteVC he/she can drop selected tags. It is constantly changing in the Realm as you can see. The problem when user has dropped some tags in detailVC and press the Back button, the dropped tags are still looking as selected in SkillsVC. However when if user goes another VC and comes back to SkillsVC (by this way the viewDidLoad() is gonna work) the dropped tags are seems to be unselected. That's all.
If what you are looking for is just to reload on back button
What you can do is create your own custom UIBarButtonItem that will make you navigate backwards from your "detail view controller". What you should do next after adding your own back button is add an IBAction for UIBarBUttonItem and pop your "detail view controller".
Right before you do this, you should create a delegate that will be executed before the popping happens that will reload your UICollectionView.
The following is not the best way to achieve what you want:
In your didSelectItem for your second view controller, you are creating a new view controller here and you shouldn't force call viewDidAppear. Since you are creating a new UIViewController, you are not referencing the previous UIViewController that you came from and soo your UICollectionView is nil.
if currentTab.shared.isSkill {
//remove the below lines and call the delegate here
let VC: SkillsController = storyboard?.instantiateViewController(withIdentifier: "SkillsController") as! SkillsController
VC.viewDidAppear(true)
}
collectionView.reloadData()
What you should be doing is:
You should use delegates to send callbacks to previous view controllers or perform actions.
To create a delegate-
Using the first approach (using your own back button)-
protocol delegateVC{
func reloadCollectionView()
}
class SeeSelectedController: UICollectionViewController{
//add this inside this class
var delegate : delegateVC?
...
//implement your IBAction for back button and inside it-
... {
self.delegate.reloadCollectionView()
}
}
OR the second approach i pointed out (Just change your didSelectItem and it will reload the collectionView, no need to fret about back button at all and save the hassle, i strongly recommend this approach)
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = TAGS[indexPath.row].name!
let currentState = TAGS[indexPath.row].isSelected!
TAGS[indexPath.row].isSelected = currentState ? false:true
if currentState {
print("deselect")
//remove from realm
RealmManager.shared.deleteItemFromList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}else{
print("select")
//add to realm
RealmManager.shared.addItemToList(type: getTypeOfTag(isSkill: currentTab.shared.isSkill), item: item)
}
if currentTab.shared.isSkill {
self.delegate.reloadCollectionView()
}
}
}
And in your first view controller-
func reloadCollectionView(){
collectionView.reloadData()
}
Note: In your prepareForSegue remember to set the delegate of your detail view controller to be your first view controller
I am new of the swift3. So, please forgive me if you think it is easy question. Do not have a clear idea when searching internet or stack overflow with my situation, so I ask this question.
Goal: Passing data from a tableview to webview
Example: data 1,2,3... in the table, press 1, then jump into webview with value 1
Information:
In main.storyboard, looks like:
class oneViewController for a view controller with tableview
class twoViewController for a view controller with webview
In oneViewController, things are well set and select row at:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Array[indexPath.row] //This value expected to pass
//some code to execute passing data...
//Also, jump into another view controller with webview
}
In twoViewController, everything got ready:
//let passData = some code to get the data....
let URL = NSURL(string: "https://www.example.com?data=\(passData)")
webView.loadRequest(NSURLRequest(url: URL! as URL) as URLRequest)
Finally, PHP can get the value
echo $_GET["data"];
Questions:
How to set the relationship between tableview and webview in main.storyblard?
Connect view controller to anther view controller? Or connect Table view Cell to view controller? Or something else.....
How to implement passing data in class oneViewController and twoViewController?
Follow the below steps:-
Step 1: Below code will launch and pass the data in your TwoViewController. Please note that in your Main.Storyboard->TwoViewContoller, give the identifier as TwoViewController
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyBoard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let twoVC:TwoViewController = storyBoard.instantiateViewController(withIdentifier: "TwoViewController") as! TwoViewController
twoVC.passData = Array[indexPath.row]
self.present(twoVC, animated: true, completion: nil)
}
Step 2: In your TwoViewController class define a variable as below
var passData: String = ""
you can do three solutions :
1- send data in segue
2- forget the segue relation and call the new controller in didSelectRowAt, and set data like this :
let vc = UIStoryboard(name: "BarcodeScanner", bundle: nil).instantiateInitialViewController()! as! BarcodeScannerViewController
vc.passData = valueWantToSend
self.presentViewController(vc, animated: true, completion: nil)
3- use struct like this and you be able to use your data from any place in your project :
struct Variables
{
static var value = false
static var passData = ""
}
for exemple : Variables.passData=#"new value"
I have a tableView(top) and a view controller(bottom) within container views in the same view. I need to send info and refresh the bottom view when selecting a table row. I'd like it to work like the apple stocks app. I originally had the bottom view on another page and used a segue and it worked great. But I'm not sure how to do it without a segue now.
Here is the code for selecting the row:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
let item = items[indexPath.row]
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
What I had before for the segue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let controller = segue.destinationViewController as SNPDetailViewController
if let indexPath = tableView.indexPathForCell(sender as UITableViewCell) {
controller.itemToEdit = items[indexPath.row]
}
}
And what was working when I used the segue in the (now) bottom view:
override func viewDidLoad() {
super.viewDidLoad()
if let item = itemToEdit {
title = item.name
snpDetails.text = item.details
}
}
Any help is greatly appreciated!
In your table view controller, self.parentViewController will point to the container view controller. Then you can get a reference to the existing detail view controller via the childViewControllers property of the container:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
let item = items[indexPath.row]
if let vc = self.parentViewController {
let siblings = vc.childViewControllers
if siblings.count > 1 {
if let detailVC = siblings[1] as? SNPDetailViewController {
detailVC.itemToEdit = item
} else {
println("Odd, that detail view controller is not the right class")
abort()
}
} else {
println("Odd, there is no detail view controller")
abort()
}
} else {
println("Strange, I'm not embedded in a parent view controller")
abort()
}
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
This assumes your table view controller is at index 0 of childViewControllers, and the detail view controller is at index 1. Amend siblings[1] to siblings[0] if it's the other way around.
You may need to implement a setter method for itemToEdit in order to reload the labels etc when the value changes.
I guess your bottom view is SNPDetailViewController
As you can see in your previous code you had a variable called controller. That variable is missing in your actual code. Try to create it in your first snippet of code:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
let item = items[indexPath.row]
}
let controller = SNPDetailViewController()
// then you can call the _itemToEdit_ method in your other view
if let indexPath = tableView.indexPathForCell(sender as UITableViewCell) {
controller.itemToEdit = items[indexPath.row]
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
I'm not completely sure if that works but the idea is that you need like an instance of your bottom view to access its methods. Let me know if that helps.