Swift need to reload TableView that is contained within a containerView - ios

I have a tableViewController with in a containerView in the main view of my app. The user can elect one of three buttons which needs to pass a new value to the tableViewController and reload the tableView.
I can't seem to find an example of how best to do this programmatically.
Any suggestions or links to examples thanks.
Adding some code of what I have tried
#IBAction func checkins10(sender: AnyObject) {
statusImage10.hidden = false
statusImage1.hidden = true
statusImage5.hidden = true
self.hourWindow = 10.00
reloadCheckInView()
}
func reloadCheckInView() {
let checkInsTableViewController: FriendCheckInTableViewController = FriendCheckInTableViewController(nibName: "FriendCheckInTableViewController", bundle: nil)
checkInsTableViewController.checkInsTableView.reloadData()
}
Updated
func reloadCheckInView() {
// let checkInsTableViewController: FriendCheckInTableViewController = FriendCheckInTableViewController(nibName: "FriendCheckInTableViewController", bundle: nil)
var viewControllerStoryboardId = "FriendCheckInTableViewController"
var storyboardName = "Main"
var storyboard = UIStoryboard(name: storyboardName, bundle: NSBundle.mainBundle())
let checkInsTableViewController = storyboard.instantiateViewControllerWithIdentifier(viewControllerStoryboardId) as UIViewController!

You're getting a new instance of FriendCheckInTableViewController by loading it from a bundle. You need to have a reference to the one in the storyboard in your containing view and set up the connection when editing the storyboard.

You need to create a var for the tableview and connect it in your storyboard.
#IBOutlet weak var checkInsTableView: UITableView?
Connect it to your tableview in the storyboard and just refresh data as needed.
self.checkInsTableView.reloadData()

Related

Access same View Model across multiple tabs in Swift

Background
I have a workoutVM view model that would hold most of my view models, which I want to pass down to all other view controllers in my app. My app also has a tabbar controller, which I have used to store some data such as user information, etc.
Problem 1
Even though I have created the workoutVM view model (with values) in the MyTrainingViewController ("Home" tab), I am unable to pass the view model (with values) to the next ExerciseSetsViewController("Workout" tab) by using
Method 1 - By using Delegate MyTrainingViewControllerDelegate and/or
Method 2 - By instantiating ExerciseSetsViewController and loading the view.
The codes/action to pass workoutVM view model are run when user selects a particular tableview cell.
I am really not sure why either one of the methods work because similar approach worked for many other scenarios. Below is the Xcode debugger showing that my codes to utilize both methods didn't pass the workoutVM view model to the ExerciseSetsViewController
Problem 2
As a result, I found a workaround (Method 3 that were commented out in the below codes) to utilize tabbar to store the workoutVM view model (again relying on tabbar to pass & share data across multiple view controllers).
At this point, I am afraid that my app is practically using tabbar as a "singleton", even though I "sort of" understand that it is not quite "singleton".
I think, ideally, the view models should serve as some sort of data models, which are to be shared/manipulated/passed across multiple view controllers without the need to have tabbar as a middle layer. Wouldn't that be correct? Or is this the best/good practice that I am adopting by utilizing the tabbar?
protocol MyTrainingViewControllerDelegate {
func passWorkoutVM(workoutVM: WorkoutViewModel)
}
class MyTrainingViewController: UIViewController {
var workoutVM: WorkoutViewModel?
var delegate: MyTrainingViewControllerDelegate!
#IBOutlet weak var dayProgramTableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
dayProgramTableView.delegate = self
dayProgramTableView.dataSource = self
}
}
extension MyTrainingViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tabbar = tabBarController as! MainTabBarController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let ExerciseSetsViewController = storyBoard.instantiateViewController(withIdentifier: "ExerciseSetsView") as! ExerciseSetsViewController
guard let workoutVM = self.workoutVM else {
return
}
print("printing workoutViewModel dayprogram no.of exercise at HomeView \(workoutVM.dayprograms[indexPath.row].dayprogram.dayIntensity)")
//1.Instantiate view via storyboard Method
ExerciseSetsViewController.loadViewIfNeeded()
ExerciseSetsViewController.workoutVM = workoutVM
//2.Delegate Method
self.delegate?.passWorkoutVM(workoutVM: workoutVM)
//3.Tabbar Method
// tabbar.workoutVM = workoutVM
tabbar.selectedIndex = 1
}
}
class ExerciseSetsViewController: UIViewController {
var workoutVM: WorkoutViewModel?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// ** To do
//create workoutViewModel
//3.Tabbar Method
// self.workoutVM = tabbar.workoutVM
print("printing workoutViewModel to check if workoutVM had been passed from MyTrainingView \(String(describing: self.workoutVM))")
}
}
extension ExerciseSetsViewController: MyTrainingViewControllerDelegate {
func passWorkoutVM(workoutVM: WorkoutViewModel) {
self.workoutVM = workoutVM
print("passWorkoutDelegate Method executed")
}
}
class MainTabBarController: UITabBarController {
var workoutVM: WorkoutViewModel?
override func viewDidLoad() {
super.viewDidLoad()
}
}
With some external help, I was able to identify the source of the 1st problem and rectify them. I was also able to hear necessary advice for the 2nd problem.
Problem 1
The issue was that the exerciseSetsViewController that I "instantiated via storyboard" was different from the exerciseSetsViewController that would have been shown from TabbarController's 2nd tab (Workout Tab). Hence, the workoutVM was not passed to correct viewcontroller. Hence, below corrected codes needed to be used if I wanted to use either
Method 1 - By using Delegate MyTrainingViewControllerDelegate and/or
Method 2 - By instantiating ExerciseSetsViewController and loading
the view.
The corrected code ensured that the exerciseSetsViewController that was instantiated was placed as the TabbarController's 2nd tab.
protocol MyTrainingViewControllerDelegate {
func passWorkoutVM(workoutVM: WorkoutViewModel)
}
class MyTrainingViewController: UIViewController {
var workoutVM: WorkoutViewModel?
var delegate: MyTrainingViewControllerDelegate!
#IBOutlet weak var dayProgramTableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
dayProgramTableView.delegate = self
dayProgramTableView.dataSource = self
}
}
extension MyTrainingViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tabbar = tabBarController as! MainTabBarController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let ExerciseSetsViewController = storyBoard.instantiateViewController(withIdentifier: "ExerciseSetsView") as! ExerciseSetsViewController
guard let workoutVM = self.workoutVM else {
return
}
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let exerciseSetsViewController = storyBoard.instantiateViewController(withIdentifier: "ExerciseSetsView") as! ExerciseSetsViewController
guard let nav = tabbar.viewControllers?[1] as? UINavigationController, let exerciseSetsViewController = nav.viewControllers.first as? ExerciseSetsViewController else { return }
print("printing workoutViewModel dayprogram no.of exercise at MyTrainingView \(workoutVM.dayprograms[indexPath.row].dayprogram.dayIntensity)")
// 1.Instantiate view via storyboard Method
exerciseSetsViewController.loadViewIfNeeded()
exerciseSetsViewController.workoutVM = workoutVM
// 2.Delegate Method
self.delegate?.passWorkoutVM(workoutVM: workoutVM)
}
Problem 2
I was advised that my approach to utilize the "tabbar" is literally the same as using "Singleton" because there is one shared source of data where multiple views are accessing. Likewise, my approach to utilize view models that can be accessed via multiple view controller is same as using "global" variable, which has similar repercussion as using "Singletons".
While this approach can be acceptable in certain cases, it is not the "best practice" and I will need to change some of my codes/approach, where each view controller would have their own set of data/view models.

Pass data to only static storyboard embedded tableview controller on a UINavigation

So all my controller's are done programmatically to avoid segues and that sort of complicated stuff.
I have a viewcontroller (Call it ProfileViewController) that downloads data from the network.
So I have a method in ProfileViewController that instantiates a single storyboard file with a static tableview with cells that have textfields in them. Here is the method:
ProfileViewController:
func userSelectedUpdateProfile() {
// Obtain reference to the only storyboard file named EditProfileSB
let storyboard = UIStoryboard(name: "EditProfileSB", bundle: nil)
// Since the Tableview is embedded in a navigation controller (with ID set to "navigationID")
if let parentNavigationController = storyboard.instantiateViewController(withIdentifier: "navigationID") as? UINavigationController {
// Now find the embedded TableViewController and access it's properties to pass to.
if let childEditController = parentNavigationController.topViewController as? EditProfileTableViewController {
// ! Error here ! Found nil when doing this.
childEditController.nameTextfield.text = "Passed this to static cell"
}
present(parentNavigationController, animated: true, completion: nil)
}
}
So the code itself is self-explanatory to what I am trying to achieve here. The TableView is embedded in a Navigation (done on storyboard with "Editor > Embed In") so on the 2nd nested if let statement I am now checking to find that Edit controller and access its properties (nameTextfield).
I get a crash when I attempt to access the nameTextField.text property. This textfield is set using storyboard. Here is that EditProfileTableViewController:
class EditProfileTableViewController: UITableViewController {
#IBOutlet weak var nameTextfield: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
// Other methods ...
}
Here is the storyboard flow layout.
Am I missing something here? I keep getting a crash on childEditController.nameTextfield.text = "Passed this to static cell" on the method userSelectedUpdateProfile().
If your View Controller still not call viewDidLoad().
your textfield is not create.
#IBOutlet weak var nameTextfield: UITextField!
you can see it's attribute is weak here.
Try create a value and pass text to the value. Then in viewDidLoad(), you can set the value to your textField

Is there a way to connect or access my `scrollView`from my main class to another class?

in my MainViewController I have a scrollView and add a ChildView in it which works fine. In this example I instantiate my PlansViewController2and add it as a ChildViewto my scrollView.
After selecting a Cellit pushes a new controller. But I want to add the new Controller as a ChildView to my `scrollView.
#IBAction func planAction(_ sender: UIButton) {
let newPlan = sender.frame.origin.x
scrollView.subviews.forEach{$0.removeFromSuperview()}
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let nextController = storyBoard.instantiateViewController(withIdentifier: "PlansViewController2") as! PlansViewController2
addChildView(viewController: nextController, in: scrollView)
self.movingView.frame.origin.x = newPlan
}
Im my PlansViewController2 I push another ViewControllerbut I want to add it also in my scrollView from the previous controller.
let storyBoard = UIStoryboard(name:"Main", bundle:nil)
let planViewCtrl = storyBoard.instantiateViewController(withIdentifier: "PlanViewCtrl") as! PlanViewController
planViewCtrl.navigationItem.title = plan.title
self.show(planViewCtrl, sender: self)
_ = planViewCtrl.view // force view loading, so we can set it up properly
_ = self.updateLastUsed(plan: plan)
planViewCtrl.plan = plan
Is it possible to add - in my case here - the PlanViewController into my scrollViewfrom my MainViewController?
When you add view's PlansViewController2 to scrollview. Please add PlansViewController2 to childViewController of VC has scrollView(self.addChild(nextController)).
When you want to add PlanViewController to scrollView:
Step 1: First you need to get viewcontroller that have scrollView using parent property (self.parent).
Step 2: Final, You have access to scrollView instance though vc you get in step 1.
Good luck!

A passed data model is coming out as nil even though in debug I was the var get set before being passed

I am trying to pass a data model from my initial view controller to the view controller that is now on screen. I have a container view that shows a pdf. When I run the code the document that is passed into the container is nil for some reason.
I have used the debugger and watch it get set in the initial view controller, but when the next storyboard is loaded that var is now nil for some reason. I have tried it in viewDidAppear but I get the same issue.
My initial view controller (the homepage)
let documentGet = Data.documentModel[selectedRow - 1]
let storyboard = UIStoryboard(name: String(describing: NoteTakingViewController.self), bundle: nil)
let vc = storyboard.instantiateInitialViewController() as! NoteTakingViewController
vc.documentSet = documentGet
//self.navigationController?.pushViewController(vc, animated: true)
//self.present(vc, animated: true, completion: nil)
self.show(vc, sender: selectedRow)
The next view controller for the storyboard where I pass the previous vc.documentSet = documentGet
let pdfViewer = PDFView()
#IBOutlet weak var PDFClass: PDFViewClass!
var documentSet:DocumentModel!
override func viewDidLoad() {
super.viewDidLoad()
PDFClass.setDocument(document: self.documentSet) <----(this is where the error occurs)
self.title = documentSet.title
navigationController?.navigationBar.prefersLargeTitles = false
}
This is the view controller for the container view. It is a custom class since I have tried to get it to work on the previous controller but it will block the tool bar that I am trying to figure out underneath the navigation bar (yes directly under the navigation bar)
private func configurePDF() {
pdfViewer.translatesAutoresizingMaskIntoConstraints = true
view.addSubview(pdfViewer)
}
func setDocument(document: DocumentModel!) {
configurePDF()
let doc = PDFDocument(url: document.url)
pdfViewer.document = doc
}
I expect the pdf data to go from home page -> view controller -> container view controller. But I get nil on the view controller. I guess I just do not understand how the UIView are loaded. My thought process is the that the var in the view controller is set with vc.documentSet = documentGet then when that view is up it will pass the var to the container view.
I hope this is not something super simple but the way swift works is different from my experience with java.
You can solve this with some defensive programming and a property observer:
let pdfViewer = PDFView()
#IBOutlet weak var PDFClass: PDFViewClass!
var documentSet: DocumentModel? {
didSet {
self.title = documentSet?.title
if documentSet != oldValue {
setDocument(to: documentSet)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
setDocument(to: documentSet)
navigationController?.navigationBar.prefersLargeTitles = false
}
private func setDocument(to document: DocumentModel?) {
guard let validDocument = document else { return }
PDFClass?.setDocument(document: validDocument)
}

Update external XIB objects programmatically in Swift

I have two XIB views loaded that are accessed through swipe gestures and would like to update my second XIB's textbox from my first XIB.
Here is the swift code I have so far but it appears that it doesn't work from one view to the other:
// This code is placed in ViewController0.xib
let vc1 = ViewController1(nibName: "ViewController1", bundle: nil)
vc1.resultsTextBox?.text = "test"
// vc1.asd(1) //Tried calling a function that's in that view but it didn't work either.
If resultsTextBox is a UITextField then it is likely not yet initialized. That is done in viewDidLoad. The better strategy is to set some string variable to "test". Then in viewDidLoad, set the resultsTextBox.text
let vc1 = ViewController1(nibName: "ViewController1", bundle: nil)
vc1.textBoxStr = "test"
override func viewDidLoad() {
super.viewDidLoad()
vc1.resultsTextBox?.text = textBoxStr
...
}
Hope that makes sense.

Resources