I am in the process of making my first app. Right now I am trying to make a settings page. So far it looks like this.
I have the split view controller hooked up to a master view controller and a detail view controller (code below). As you can see, right now there is a table view on the left side of the split view controller containing different buttons. When each button is pressed I want it to display a list of words on the right side of the view controller. It does display a list of words already but I want it to display the words stacked horizontally in a table view and I am not sure how to go about doing this.
//
// MasterViewController.swift
// firstapp
//
// Created by Anthony Rubin on 7/18/17.
// Copyright © 2017 rubin. All rights reserved.
//
import UIKit
protocol WordSelectionDelegate: class {
func wordSelected(newWord: Word)
}
class MasterViewController: UITableViewController {
var words = [Word]()
weak var delegate: WordSelectionDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.words.append(Word(name: "initial /l/ 1 syllable", description: "lake lamb lamp lark leaf leash left leg lime lion lips list lock log look love lunch"))
self.words.append(Word(name: "initial /l/ multisyllabic", description: "words, words, words, words"))
self.words.append(Word(name: "intersyllabic /l/", description: "words, words, words, words"))
self.words.append(Word(name: "final /l/", description: "words, words, words, words"))
self.words.append(Word(name: "initial /pl/", description: "words, words, words, words"))
self.words.append(Word(name: "initial /bl/", description: "words, words, words, words"))
self.words.append(Word(name: "initial /fl/", description: ""))
self.words.append(Word(name: "initial /gl/", description: "words, words, words, words"))
self.words.append(Word(name: "initial /kl/", description: ""))
self.words.append(Word(name: "initial /sl/", description: ""))
self.words.append(Word(name: "final /l/ clusters", description: ""))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.words.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Configure the cell...
let word = self.words[indexPath.row]
cell.textLabel?.text = word.name
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt
indexPath: IndexPath) {
let selectedMonster = self.words[indexPath.row]
self.delegate?.wordSelected(newWord: selectedMonster)
if let Detail = self.delegate as? Detail {
splitViewController?.showDetailViewController(Detail, sender: nil)
}
}
__
import UIKit
class Detail: UIViewController {
#IBOutlet weak var descriptionLabel: UILabel!
var word: Word! {
didSet (newWord) {
self.refreshUI()
}
}
func refreshUI() {
descriptionLabel?.text = word.description
}
override func viewDidLoad() {
super.viewDidLoad()
refreshUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
extension Detail: WordSelectionDelegate {
func wordSelected(newWord: Word) {
word = newWord
}
}
--
class Word {
let name: String
let description: String
init(name: String, description: String) {
self.name = name
self.description = description
}
}
--
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let splitViewController = self.window!.rootViewController as! UISplitViewController
let leftNavController = splitViewController.viewControllers.first as! UINavigationController
let MasterViewController = leftNavController.topViewController as! MasterViewController
let Detail = splitViewController.viewControllers.last as! Detail
let firstWord = MasterViewController.words.first
Detail.word = firstWord
MasterViewController.delegate = Detail
return true
}
PS. If you look at the MasterViewController code you will see where it says "description". the lists contained in description is what should be displayed in the table view on the right.
In the func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath), of the masterviewcontroller, you need to replace the viewcontroller.
Get the splitviewcontroller instance from the appdelegate, now to the splitview controllers view controller property assign your desired viewcontroller object, and make sure your desired view controller object has a navigation controller.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 1 {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let splitViewController = appDelegate.window!.rootViewController as! UISplitViewController
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "yourviewcontrollername") as! UINavigationController
splitViewController.viewControllers[1] = nextViewController
}
Related
How is it possible that the navigation controller shown in the screenshot is described in the memory debugger as having 1 children (of type ContactsTableViewController) and at the same time in the lldb prompt is described as having 0 elements?
If you want more context or the code to reproduce it, please look at the code shown in: here
UPDATE:
I haven't run any other commands between the time I captured the memory graph and the time I ran the po command.
The minimal reproducible example is the following playground snippet ( I only have seen this strange behaviour when using a UIViewControllerRepresentable.Coordinator, that's why the minimal example is a bit complex):
import PlaygroundSupport
import SwiftUI
import UIKit
protocol Coordinator {
var nav: UINavigationController { get }
func start()
}
final class FlowCoordinator: Coordinator {
struct Dependencies {
let nav: UINavigationController
let screen1Factory: () -> UIViewController
let screen2Factory: () -> UIViewController
}
var nav: UINavigationController
let screen1Factory: () -> UIViewController
let screen2Factory: () -> UIViewController
init(dependencies: FlowCoordinator.Dependencies) {
self.nav = dependencies.nav
self.screen1Factory = dependencies.screen1Factory
self.screen2Factory = dependencies.screen2Factory
}
func start() {
let vc = screen1Factory() as! ViewController
vc.flowCoordinator = self
nav.pushViewController(vc, animated: false)
print("FlowCoordinator.started: flowCoordinator.nav: ", nav, "VCs count: ", nav.viewControllers.count)
}
func screen2() {
print("Pushing screen2")
let vc = screen2Factory() as! ViewController
vc.flowCoordinator = self
nav.pushViewController(vc, animated: false)
print("Coordinator.viewController.flowCoordinator.nav: ", "VCs count: ",
nav.viewControllers.count)
}
}
final class ViewController: UITableViewController {
weak var flowCoordinator: FlowCoordinator?
// Array of cities to display in the table view
let cities = ["New York", "Paris", "Tokyo"]
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "CityCell")
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section
return cities.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CityCell", for: indexPath)
// Configure the cell...
cell.textLabel?.text = cities[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
flowCoordinator?.screen2()
}
}
final class AppDependencies {
lazy var flowCoordinator: FlowCoordinator = {
let navigationController = UINavigationController()
navigationController.navigationBar.prefersLargeTitles = true
let flowCoordinatorDependencies = FlowCoordinator.Dependencies(
nav: navigationController,
screen1Factory: makeViewController1,
screen2Factory: makeViewController2)
return FlowCoordinator(dependencies: flowCoordinatorDependencies)
}()
func makeViewController1() -> UIViewController {
let vc = ViewController()
vc.title = "View 1"
return vc
}
func makeViewController2() -> UIViewController {
let vc = ViewController()
vc.title = "View 2"
return vc
}
}
[![enter image description here][1]][1]
struct View1: UIViewControllerRepresentable {
let flowCoordinator: FlowCoordinator
func makeUIViewController(context: Context) -> ViewController {
return context.coordinator.viewController
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {
}
}
extension View1 {
final class Coordinator: NSObject, UITableViewDelegate {
var viewController: ViewController
var flowCoordinator: FlowCoordinator
init(flowCoordinator: FlowCoordinator) {
self.flowCoordinator = flowCoordinator
self.flowCoordinator.start()
self.viewController = self.flowCoordinator.nav.viewControllers.first as! ViewController
super.init()
viewController.tableView.delegate = self
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("View1.tableView(_:didSelectRowAt:indexPath)")
print("Coordinator.viewController.flowCoordinator.nav: ", viewController.flowCoordinator?.nav ?? "nil", "VCs count: ",
viewController.flowCoordinator?.nav.viewControllers.count ?? "nil")
viewController.flowCoordinator?.screen2()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(flowCoordinator: flowCoordinator)
}
}
struct ContentView: View {
let deps = AppDependencies()
var body: some View {
View1(flowCoordinator: deps.flowCoordinator)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
let deps = AppDependencies()
//deps.flowCoordinator.start()
let nav = deps.flowCoordinator.nav
//let host = nav
let host = UIHostingController(rootView: View1(flowCoordinator: deps.flowCoordinator) )
PlaygroundPage.current.liveView = host
The updateSearchResults function isn't getting called in my view table controller view for some reason and I don't know why? I've seen this answer but it's nothing similar to mine and I don't want to use the textDidChange function.
I have this view SearchViewController where it has a search bar at the top and it will have a collection view below it. (Making instagrams explore page). Once the search bar has been tapped, it should display view SearchTableViewController which shows the results of the search query in a table view.
This is my code:
SearchViewController (the main view):
import UIKit
class SearchViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let searchResultsViewController = storyboard.instantiateViewController(withIdentifier: "SearchResultsViewController") as? SearchTableViewController else { return}
let searchController = UISearchController(searchResultsController: searchResultsViewController)
searchController.searchResultsUpdater = searchResultsViewController
view.addSubview(searchController.searchBar)
definesPresentationContext = true
}
}
SearchTableViewController (the view that gets displayed with the results):
import UIKit
import Firebase
class SearchTableViewController: UIViewController {
let languages = ["Mandarin Chinese", "English", "Hindustani", "Spanish", "Arabic", "Malay", "Russian", "Bengali", "Portuguese", "French", "Hausa", "Punjabi", "German", "Japanese", "Persian", "Swahili", "Telugu", "Javanese", "Wu Chinese", "Korean"]
var searchResults: [String] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
}
}
extension SearchTableViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
print("typing")
guard let searchText = searchController.searchBar.text else { return }
searchResults = languages.filter { $0.contains(searchText) }
tableView.reloadData()
}
}
extension SearchTableViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchCell", for: indexPath)
let language = searchResults[indexPath.row]
cell.textLabel?.text = language
return cell
}
}
the updateSearchResults in the extension SearchTableViewController: UISearchResultsUpdating isn't being called which I think is the cause of the table view with the search results not loading the search query data?
I forgot to add the assignment to the navigation controller too. Even if you're showing the search results on another screen you still need to add it:
navigationItem.searchController = theSearchControllerYouInstantiated
I am a beginner to iOS coming from the android background and just learned about table view (for me it's an Android ListView). I am trying to separate data source & delegate from view controller. I found some tutorials on how to do so but stuck at figuring out how to send the clicked item to another view controller. The code is below:
class PictureTableViewController: UIViewController {
#IBOutlet weak var pictureTableView: UITableView!
private let picsDataSource: PicturesDataSource
required init?(coder aDecoder: NSCoder) {
self.picsDataSource = PicturesDataSource()
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
pictureTableView.dataSource = picsDataSource
pictureTableView.reloadData()
pictureTableView.delegate = picsDataSource
}
}
class PicturesDataSource: NSObject, UITableViewDataSource, UITableViewDelegate{
private var pictureModels = [PictureModel]()
override init(){
let picModelsDataController = PictureModelsDataController()
pictureModels = picModelsDataController.pictureModels
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pictureModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PictureCell.self)) as! PictureCell
let picModel = pictureModels[indexPath.row]
cell.pictureName = picModel.pictureName
cell.imageItem = picModel.imageItem
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//1 - try loading the "Detail" view controller and typecasting it to be DetailViewController
if let detailViewController = storyboard.instantiateViewController(withIdentifier: "PictureDetailView") as? PictureDetailViewController {
//2 - success! set its selecteImage property
detailViewController.selectedImgName = pictureModels[indexPath.row].pictureName
//3 - now push it onto the navigation controller
navigationController?.pushViewController(detailViewController, animated: true)
}
}
}
Error in: func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){ }. since "storyboard" & "navigationController" are not available in PicturesDataSource class, how can I send clicked item(picture name) to the DetailsViewController
There are StackOverflow answers about separating data source and delegate but did not solve my problem.
Using: Xcode 8.3 beta 6
You can include a reference to main view controller at your table view events handler. Below is a playground code I derived from your example:
import UIKit
// MARK: - Model
struct Picture {
let title: String
let image: UIImage
}
struct PictureModelsDataSource {
let pictures = [
Picture(title: "exampleTitle", image: UIImage(named: "exampleImage")!),
Picture(title: "exampleTitle", image: UIImage(named: "exampleImage")!)
]
}
// MARK - View
class PictureCell: UITableViewCell {
#IBOutlet weak var pictureTitleLabel: UILabel!
#IBOutlet weak var pictureImage: UIImageView!
}
// MARK: - Controller
class PictureTableViewController: UIViewController {
// MARK: - Properties
#IBOutlet weak var pictureTableView: UITableView!
private var pictureListController: PictureListController?
// MARK: - View lifecycle
override func viewDidLoad() {
super.viewDidLoad()
pictureListController = PictureListController()
pictureListController?.viewController = self
pictureTableView.dataSource = pictureListController
pictureTableView.delegate = pictureListController
pictureTableView.reloadData()
}
}
class PictureDetailViewController: UIViewController {
var selectedPictureTitle: String?
}
class PictureListController: NSObject, UITableViewDataSource, UITableViewDelegate {
// MARK: - Properties
weak var viewController: PictureTableViewController?
private let pictures: [Picture] = {
let pictureModelsDataSource = PictureModelsDataSource()
return pictureModelsDataSource.pictures
}()
// MARK: - View setup
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// MARK: - Event handling
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pictures.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PictureCell.self)) as? PictureCell else {
return UITableViewCell()
}
let picture = pictures[indexPath.row]
cell.pictureTitleLabel.text = picture.title
cell.pictureImage.image = picture.image
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let pictureTitle = pictures[indexPath.row].title
let storyboard = UIStoryboard(name: "exampleStoryboard", bundle: nil)
if let pictureDetailViewController = storyboard.instantiateViewController(withIdentifier: "PictureDetailView") as? PictureDetailViewController {
pictureDetailViewController.selectedPictureTitle = pictureTitle
viewController?.navigationController?.pushViewController(pictureDetailViewController, animated: true)
}
}
}
See StoryBoard object can be obtained by using this
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
Now your second question is about how to get navigation controller. It means how to get currentViewController in your case. This can be get by below code
func getCurrentViewController() -> UIViewController? {
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
Now your didSelectRowAt code will look like this
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let detailViewController = storyboard.instantiateViewController(withIdentifier: "PictureDetailView") as? PictureDetailViewController
detailViewController.selectedImgName = pictureModels[indexPath.row].pictureName
self.getCurrentViewController()!.pushViewController(detailViewController, animated: true)
}
You are trying to adhere to MVC, but you are confusing what your actual data source is.
PicturesDataSource is not your data source. It's the code that tells your table how to set itself up.
PictureModelsDataController() is the source from which you get the data that actually populates that table.
All of your posted code should be in the same class:
class PictureTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
Change these lines:
pictureTableView.dataSource = picsDataSource
pictureTableView.delegate = picsDataSource
to
pictureTableView.dataSource = self // Note use of self because this is now the dataSource, not another class
pictureTableView.delegate = self // Note use of self because this is now the delegate, not another class
and remove:
private let picsDataSource: PicturesDataSource
required init?(coder aDecoder: NSCoder) {
self.picsDataSource = PicturesDataSource()
super.init(coder: aDecoder)
}
I would like to ask you guys how to make a data structure for multi-level drill-down TableView in Swift. As of myself, I know how to do it and pass data this way TableView -> TableView -> DetailViewController using NSObject for creating Swift models. But several weeks now I struggle with finding of how to do this way: TableView -> TableView -> DetailViewController -> TableView again.
As the app I am doing now is education related, the first TableView would contain sections of topics and leesons in them, then when the user selects the lesson, the segue performs and shows content as ViewController, then the user press the button and he is redirected to another TableView with tasks related to the lesson in it.
So in all it should be like this: LessonsTableView -> LessonDetailViewController -> TasksTableView -> TaskDetailViewController.
I can say that the main problem is to get the tableView of tasks listed from LessonDetailViewController. As I know how to make 2 levels of data, but can't do 3 or more.
I have searched all the internet, I have found examples in Obj-C, but simply I don't understand that programming language (tried to convert the code in XCode). Could anyone guide me of how to achieve this? Perhaps there's a link with brief tutorial that you guys know.
Update
I've managed to do the first TableView and ViewController, but stuck with passing data from LessonViewController to TasksTableView, getting this Error: Expression type '[Lesson]' is ambiguous without more context.
Perhaps anyone could help me with this, my Model:
import Foundation
struct task {
var name: String
}
struct Lesson {
var name: String
var info: String
var tasks: [task]
}
class LessonList {
var name: String
var lessons = [Lesson]()
init(name: String, lessons: [Lesson]) {
self.name = name
self.lessons = lessons
}
class func lessonsSection() -> [LessonList] {
return [self.intro(), self.can()]
}
private class func intro() -> LessonList {
let tasks: [task] = [task(name: "hello"), task(name: "Hey")]
let tasks1: [task] = [task(name: "is He?"), task(name: "are they?")]
var lessons = [Lesson]()
lessons.append(Lesson(name: "I am", info: "This class is I am", tasks: tasks))
lessons.append(Lesson(name: "He is", info: "This class is He is", tasks: tasks1))
return LessonList(name: "Intro", lessons: lessons)
}
private class func can() -> LessonList{
let tasks: [task] = [task(name: "bye"), task(name: "can have")]
let tasks1: [task] = [task(name: "Can he?"), task(name: "They can't")]
var lessons = [Lesson]()
lessons.append(Lesson(name: "I can", info: "This class is I can", tasks: tasks))
lessons.append(Lesson(name: "He can't", info: "This class is He can't", tasks: tasks1))
return LessonList(name: "Can", lessons: lessons)
}
}
The CourseTableView (first)`import UIKit
class CourseTableVC: UITableViewController {
let lessonLists : [LessonList] = LessonList.lessonsSection()
override func viewDidLoad() {
super.viewDidLoad()
title = "Horizon English"
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return lessonLists.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lessonLists[section].lessons.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "lessonCell", for: indexPath)
let lessonList = lessonLists[indexPath.section]
let lessons = lessonList.lessons
let lesson = lessons[indexPath.row]
cell.textLabel?.text = lesson.name
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let lessonList = lessonLists[section]
return lessonList.name
}
//MARK: - UITableViewdelegate, navigation segue
var selectedLesson: Lesson?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let lessonLine = lessonLists[indexPath.section]
var lesson = lessonLine.lessons[indexPath.row]
selectedLesson = lesson
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "lessonDetailView" {
let lessonDetailVC = segue.destination as! LessonViewController
lessonDetailVC.lesson = selectedLesson
}
}
}`
LessonViewController where I get error (in prepare for segue)`import UIKit
class LessonViewController: UIViewController {
#IBOutlet weak var infoLabel: UILabel!
#IBAction func toTasksButton(_ sender: Any) {
}
var lesson: Lesson?
override func viewDidLoad() {
super.viewDidLoad()
title = lesson?.name
infoLabel.text = lesson?.info
}
// MARK: - navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showTasks" {
var taskTV = segue.destination as! TasksTableVC
taskTV.tasksList = lesson?.tasks as! [Lesson]
// Error: Expression type '[Lesson]' is ambiguous without more context
}
}
}`
and TasksTableView: `import UIKit
class TasksTableVC: UITableViewController {
var tasksList = [Lesson]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Tasks"
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasksList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath)
let taskList = tasksList[indexPath.section]
let tasks = taskList.tasks
let task = tasks[indexPath.row]
cell.textLabel?.text = String(describing: task)
return cell
}
}`
Thanks in advance.
I can say that the main problem is to get the tableView of tasks listed from LessonDetailViewController.
I don't know what steps you already did so I'll say how I'd do it:
1 - Have the structure implemented. That's my suggestion:
struct Task {
var name = ""
}
struct Lesson {
var name = ""
var tasks: [Tasks] // list of tasks for this lesson
}
OR
struct Task {
var name = ""
var lesson: Lesson // the leasson which the task belongs to
}
struct Lesson {
var name = ""
}
2 - I suppose you know how to pass data between view controllers, so when the user select the cell of the lesson he wants, you'd pass the lesson object to the LessonDetailVC and show the data. Something like
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedLesson = lessons[indexPath.row]
// code to pass to the next vc here or in prepareForSegue
}
3 - To create the tableview for the tasks you just have to filter(or query, or fetch, I don't know how you're getting the data) in order to obtaining the tasks. Let's suposed you used the first code from item 1 so you just simply show the lesson.tasks array in your table view. If you chose the second code from item 1 you will have to perform some query with a filter passing the lesson your in, something like query.where("lesson", lesson) and you will have the tasks array.
UPDATE
Since you're new on Swift here's an idea on how to model your view controller:
// Your models
struct Task {
var name = ""
var lesson: Lesson // the leasson which the task belongs to
}
struct Lesson {
var name = ""
}
// This is just a DataManager to help you get your data
class DataManager {
func getLessons() -> [Lesson]? {
return [Lesson(name: "Lesson 1"), Lesson(name: "Lesson 2"), Lesson(name: "Lesson 3")]
}
func getTasks(for lesson: Lesson) -> [Task]? {
guard let lessons = getLessons() else {
return nil
}
let allTasks = [
Task(name: "T1", lesson: lessons[0]),
Task(name: "T2", lesson: lessons[0]),
Task(name: "T3", lesson: lessons[0]),
Task(name: "T1", lesson: lessons[1]),
Task(name: "T2", lesson: lessons[1]),
Task(name: "T1", lesson: lessons[2]),
Task(name: "T2", lesson: lessons[2]),
Task(name: "T3", lesson: lessons[2]),
Task(name: "T4", lesson: lessons[2])
]
return allTasks.filter{ $0.lesson == lesson }
}
}
class LessonsViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
private var lessons: [Lesson]? {
didSet {
tableView.reloadData()
}
}
private var selectedLesson: Lesson?
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
lessons = DataManager().getLessons()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lessons?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "lessonCell", for: indexPath)
let lesson = lessons[indexPath.section]
cell.textLabel?.text = lesson.name
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var lesson = lessons[indexPath.row]
selectedLesson = lesson
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "lessonDetailView" {
let lessonDetailVC = segue.destination as! LessonViewController
if selectedLesson != nil {
lessonDetailVC.lesson = selectedLesson!
}
}
}
}
class TasksViewController: UIViewController, UITableViewDataSource {
var lesson: Lesson!
private var tasks: [Task]? {
didSet {
tableView.reloadData()
}
}
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tasks = DataManager().getTasks(for: lesson)
}
}
I am new to swift (and new to StackOverflow) and learning to use nib files now as I did quite a bit of learning with storyboard already.
In storyboard we can instantiate segues and unwindSegues to fetch data from one VC to another. How can I do the same WITHOUT using storyboard?
I currently have a scrollview(MainViewController.xib) that loads a tableview(ContactView.xib) on it. I have an add button overlaying the tableview which is currently unresponsive.
I want to be able to load another view (AddContact.xib) when I click on the add button to add a new data to my tableview and then unwind to reflect the new data in the table, but WITHOUT using storyboard.
I can provide my code if needed, but am only looking for someone to point me in the correct direction. I know this can be achieved using Navigation Controllers but I can't seem to find a fitting tutorial for it (most are old and use Obj-C. I am unfamiliar with Obj-C).
I even looked at some similar questions like: this and this
but they failed to answer my question.
Any help is appreciated. Thank you.
My AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mainVC: MainViewController? = nil
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
mainVC = MainViewController(nibName: "MainViewController", bundle: nil)
let frame = UIScreen.main.bounds
window = UIWindow(frame: frame)
window!.rootViewController = mainVC
window!.makeKeyAndVisible()
return true
}
MainViewController.swift:
class MainViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let contactView: ContactView = ContactView(nibName: "ContactView", bundle: nil)
let dialerView: DialerView = DialerView(nibName: "DialerView", bundle: nil)
self.addChildViewController(dialerView)
self.scrollView.addSubview(dialerView.view)
dialerView.didMove(toParentViewController: self)
self.addChildViewController(contactView)
self.scrollView.addSubview(contactView.view)
contactView.didMove(toParentViewController: self)
var contactViewFrame : CGRect = contactView.view.frame
contactViewFrame.origin.x = self.view.frame.width
contactView.view.frame = contactViewFrame
self.scrollView.contentSize = CGSize(width: self.view.frame.width * 2, height: self.view.frame.height)
}
}
My ContactView.xib:
class ContactView: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var searchBar: UISearchBar!
var names = ["Rohan", "Rahul", "Sneh", "Paa", "Maa", "Vatsal", "Manmohan"]
var numbers = ["9830000001", "9830000002", "9830000003", "9830000004", "9830000005", "9830000006", "9830000007"]
let navVC: UINavigationController? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.rowHeight = CGFloat(60)
tableView.register(UINib(nibName: "ContactCell", bundle: nil), forCellReuseIdentifier: "Contact")
let cell = tableView.dequeueReusableCell(withIdentifier: "Contact", for: indexPath) as! ContactCell
cell.nameLabel.text = names[indexPath.row]
cell.numLabel.text = numbers[indexPath.row]
return cell
}
// Make delete-able in scroll view
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
names.remove(at: indexPath.row)
numbers.remove(at: indexPath.row)
tableView.reloadData()
}
}
My AddContact.xib only has the outlets defined.
Edit: I found a solution thanks to the commenters.
To Anyone wondering how I did it, I'll post the steps below:
Step 1: Create a protocol (I did so in my MainViewController.xib)
protocol NewContactDelegate {
func add_Contact (name: String, num: String)
}
Step 2: Instantiate a delegate for it in the senderVC. AddContact.xib was the senderVC for me
class AddContact: UIViewController {
var delegate: NewContactDelegate? = nil
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var numField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func saveContact(_ sender: UIButton) {
if delegate != nil {
if let name = nameField?.text, let num = numField?.text {
delegate?.add_Contact(name: name, num: num)
dismiss(animated: true, completion: nil)
}
}
}
Step 3: Make ReceivingVC conform to the delegate and accept data.
class ContactView: ....., NewContactDelegate {
func add_Contact(name: String, num: String) {
names.append(name)
numbers.append(num)
addressBook.reloadData() // This is my TableView outlet
}
}
**
NOTE: Remember to assert the delegate to a non-nil value in the RecievingVC to make sure #IBAction in SenderVC works as expected.
**