Passing data to ConrainerView from ViewControlller always nil - ios

I have CurrentYearViewController in that I used container to show multiple views. The problem is value that I am sending to ContainerViewController is getting always nil
CurrentYearViewController
//calling this function after getting response, not presenting because it is called from Segmented Control
func refreshCurrentYearListView(dict: NSDictionary) {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let containerViewController = mainStoryboard.instantiateViewController(withIdentifier: "containerView") as!
ContainerViewController
containerViewController.dataDictionary = dict
}
ContainerViewController
class ContainerViewController: UIViewController {
#IBOutlet weak var billedValueLbl: UILabel!
var dataDictionary: NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
if let data = dataDictionary {
populateJSON(dict: data)
}
}
func populateJSON(dict: NSDictionary) {
let N_A: String = "N/A"
let parser = CommonParser()
let currentYearSalesSummaryDict: NSDictionary = parser.parse(dictionary: dict, key: "currentYearInvoiceSummary")
if currentYearSalesSummaryDict.count > 0 {
billedValueLbl.text = parser.parse(dictionary: currentYearSalesSummaryDict, key: "totalInvoiceAmount", exceptionString: N_A)
}
}

Related

UILabel in segue.destination - Unexpectedly found nil while implicitly unwrapping an Optional value

I've got a ViewController class, and two IBOutlet UILablels that I created from storyboard.
Here is the code of the class:
import UIKit
class AnnouncementViewController: UIViewController {
#IBOutlet var nameLabel: UILabel!
#IBOutlet var infoTV: UITextView!
var mail: String = ""
var url: String = ""
override func viewDidLoad() {
super.viewDidLoad()
}
func set(_ announcement: [String: String]) {
print(announcement)
nameLabel.text = announcement["name"] // here goes the error
self.infoTV.text = announcement["info"]
self.mail = announcement["email"]!
self.url = announcement["url"]!
}
}
And here is the code from another class (TableViewController) I've got:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard segue.identifier == "announcementSegue" else { return }
let indexPath = tableView.indexPathForSelectedRow!
let announcement = tasks[indexPath.row]
let destinationVC = segue.destination as! AnnouncementViewController
destinationVC.set(announcement)
}
The error I get is:
Unexpectedly found nil while implicitly unwrapping an Optional value
So, nameLabel is nil. I cannot get why it is.
Solution 1 call loadViewIfNeeded()
let destinationVC = segue.destination as! AnnouncementViewController
destinationVC.view.loadViewIfNeeded()
destinationVC.set(announcement)
Solution 2
let destinationVC = segue.destination as! AnnouncementViewController
destinationVC.ann = announcement
var ann = [String: String]()
override func viewDidLoad() {
super.viewDidLoad()
set(ann)
}
func set(_ announcement: [String: String]) {
print(announcement)
nameLabel.text = announcement["name"] // no error
self.infoTV.text = announcement["info"]
self.mail = announcement["email"]!
self.url = announcement["url"]!
}

Dynamically assign ViewController to Navigate

In my case I have UITableView and have View all button for the listing of all the items in separate screens. So I added target for UIButton action method in cellForRowAt. Now what I am doing in action method:
#IBAction func btnViewAllOffer(_ sender: UIButton) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tblOfferView)
let indexPath = self.tblOfferView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
if let type = self.homeData[indexPath!.section].type {
if type == HomeDataType.SponserProduct.rawValue {
let vc1 = self.storyboard?.instantiateViewController(withIdentifier: "ViewController1") as! ViewController1
if let title = self.homeData[indexPath!.section].title {
vc1.title = title
}
self.navigationController?.pushViewController(vc1, animated: true)
} else if type == HomeDataType.Offer.rawValue {
let vc2 = self.storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
if let title = self.homeData[indexPath!.section].title {
vc2.title = title
}
self.navigationController?.pushViewController(vc2, animated: true)
} else if type == HomeDataType.BestSeller.rawValue {
let vc3 = self.storyboard?.instantiateViewController(withIdentifier: "ViewController3") as! ViewController3
if let title = self.homeData[indexPath!.section].title {
vc3.title = title
}
self.navigationController?.pushViewController(vc3, animated: true)
}
}
}
}
What I need, is there any way I can minimize the code and assign viewcontrollers dynamically so there is no need to instantiate each view controller and push them everytime?
Something like:
var vc = UIViewController()
if let type = self.homeData[indexPath!.section].type {
if type == HomeDataType.SponserProduct.rawValue {
vc = ViewController1()
}
else if type == HomeDataType.Offer.rawValue {
vc = ViewController2()
} else if type == HomeDataType.BestSeller.rawValue {
vc = ViewController3()
}
}
self.navigationController?.pushViewController(vc, animated: true)
Use a protocol (SimilarViewController) to define the common properties like title:
protocol SimilarViewController {
var title: String? { get set }
}
class ViewController1: UIViewController, SimilarViewController {
var title: String?
}
class ViewController2: UIViewController, SimilarViewController {
var title: String?
}
class ViewController3: UIViewController, SimilarViewController {
var title: String?
}
#IBAction func btnViewAllOffer(_ sender: UIButton) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tblOfferView)
let indexPath = self.tblOfferView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
if let type = self.homeData[indexPath!.section].type {
var vcGeneric: SimilarViewController?
if type == HomeDataType.SponserProduct.rawValue {
vcGeneric = self.storyboard?.instantiateViewController(withIdentifier: "ViewController1") as! ViewController1
} else if type == HomeDataType.Offer.rawValue {
vcGeneric = self.storyboard?.instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
} else if type == HomeDataType.BestSeller.rawValue {
vcGeneric = self.storyboard?.instantiateViewController(withIdentifier: "ViewController3") as! ViewController3
}
if let title = self.homeData[indexPath!.section].title {
vcGeneric?.title = title
}
if let vcGeneric = vcGeneric as? UIViewController {
self.navigationController?.pushViewController(vcGeneric, animated: true)
}
}
}
}
1: create a struct and assign the value to it.
struct TitleDetails {
static var title : String = ""
}
2: create an extension of viewController and use it to avoid code repetition.
extension UIViewController {
func pushVC(_ vcName : String) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: vcname)
self.navigationController?.pushViewController(vc, animated: true)
}
}
3: now you can call it directly as,
TitleDetails.title = yourTitleValue
self.pushVC("ViewController1")
and in your ViewDidLoad() method of your destination view controller,
self.title = TitleDetails.title
Create BaseViewController and derived other ViewController from BaseViewController
class BaseViewController: UIViewController {
var viewTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func pushVC(_ vcName : String) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
self.navigationController?.pushViewController(vc, animated: true)
}
}
And use below code on ViewController you need like:
#IBAction func btnViewAllOffer(_ sender: UIButton) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tblOfferView)
let indexPath = self.tblOfferView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
if let type = self.homeData[indexPath!.section].type {
self.viewTitle = self.homeData[indexPath!.section].title
if type == HomeDataType.SponserProduct.rawValue {
self.pushVC("ViewController1")
} else if type == HomeDataType.Offer.rawValue {
self.pushVC("ViewController2")
} else if type == HomeDataType.BestSeller.rawValue {
self.pushVC("ViewController3")
}
}
}
}

UILabel don't display

I have UILabel in my storyboard wanna show a very long string.But it did not show up.
When I insert a "hello world ",it shows up.however, insert a long string what i want to show , it did not work.
import UIKit
class VersionInformationViewController: UIViewController {
#IBOutlet weak var version: UILabel!
static func instantiateViewController() -> VersionInformationViewController {
let storyboard = UIStoryboard(name: "VersionInformation", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "versionInformation") as! VersionInformationViewController
return vc
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
fetchLibraryLicense()
}
// MARK: - private
private func fetchLibraryLicense(){
var VersionInformation:String = "";
let dictionary = NSDictionary(contentsOfFile: Bundle.main.path(forResource: "CarthageLicenseList", ofType: "plist")!);
let array = dictionary?["PreferenceSpecifiers"] as! NSArray
for dic in array {
let dictionary = dic as! NSDictionary
let title:String = (dictionary["Title"] as? String)!
let footerText:String = (dictionary["FooterText"] as? String)!
VersionInformation = VersionInformation + title + footerText
}
print(VersionInformation)
version.text = "hello world" //it works
version.text = VersionInformation // it did not work
}
}

Dynamic number of ChildViewControllers for XLPagerTabStrip

I am using XLPagerTabStrip to create a category based reading app in Swift 4. I learnt static number of ViewControllers can be easily created using the following function.
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] { }
However, the number of categories in my case depends on server response, which may change as per the need. I tried to create dynamic number of tabs by creating view controllers based on the name of categories I parsed from the json response. This is the method I did a hit and trial.
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
var childrenVC = [UIViewController]()
for eachCategory in postCategories {
print(eachCategory)
let newVC = self.storyboard?.instantiateViewController(withIdentifier: "FirstTVC") as? FirstTVC
newVC?.childName = eachCategory.name
childrenVC.append(newVC!)
self.reloadPagerTabStripView()
self.reloadInputViews()
}
return childrenVC
}
Yes, it failed. How can I achieve dynamic number of tabs in this case? If not I am also open to any other alternative. I am done with json response thing but stuck in this step. This SO answer and Github Issue didn't help as well.
I had the exact same situation. If you fetch results from the server asynchronously, as you should, you will have a crash since xlpagertabstrip needs at least one child viewcontroller.
The solution i found is to return one dummy viewcontroller at the start of the application, and after fetching data from the server to reloadPagerTabStripView.
In your parent viewcontroller you should make an empty array of your objects which you fetch from the server, for example:
var menuItems = [MenuObject]()
Next, viewControllers(for pagerTabStripController ... ) method should look like this:
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
var children = [UIViewController]()
if menuItems.count == 0 {
let child = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DummyVC") as! DummyVC
children.append(child)
return children
} else {
let menuItemsUrls = menuItems.map{$0.url}
for menuItem in menuItems {
let child = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainVC") as! TelegrafMainVC
child.name = menuItem.name.uppercased
child.url = menuItem.url
children.append(child)
}
return children
}
}
And after you fetch data from the server, whether using URLSession, or Alamofire, or whatever else, in the function or a closure you should put something like:
self.menuItems = menuItems
DispatchQueue.main.async {
self.reloadPagerTabStripView()
}
Tell me if i need to clarify something, but i believe this will be sufficient to help you.
Cheers
Step 1 : Do this in your initial viewcontroller,
class initialViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
self.loadMainView(viewControllerArray: self.setMainViewTabParameters())
}
func setMainViewTabParameters() -> NSMutableArray {
let viewControllerArray = NSMutableArray()
var tabArray = ["Tab1", "Tab2", "Tab3", "Tab4", "Tab5", "Tab6", "Tab7"]
var tabIndex = NSInteger()
for item in tabArray {
let tabString = tabArray[tabIndex]
let tabview = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "YourTabControllerIdentifier") as! YourTabController
tabview.tabHeadingTitle = tabString as NSString
viewControllerArray.add(tabview)
tabIndex = tabIndex + 1
}
return viewControllerArray
}
func loadMainView(viewControllerArray : NSMutableArray) -> Void {
let mainView = self.storyboard?.instantiateViewController(withIdentifier: "YourMainControllerIdentifier") as! YourMainController
mainView.viewControllerList = viewControllerArray
self.navigationController?.pushViewController(mainView, animated: false)
}
}
Step 2:
class YourMainController: ButtonBarPagerTabStripViewController {
var viewControllerList = NSArray()
var isReload = false
//MARK: - PagerTabStripDataSource
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
guard isReload else {
return viewControllerList as! [UIViewController]
}
var childViewControllers = viewControllerList as! [UIViewController]
for (index, _) in childViewControllers.enumerated(){
let nElements = childViewControllers.count - index
let n = (Int(arc4random()) % nElements) + index
if n != index{
swap(&childViewControllers[index], &childViewControllers[n])
}
}
let nItems = 1 + (arc4random() % 8)
return Array(childViewControllers.prefix(Int(nItems)))
}
override func reloadPagerTabStripView() {
isReload = true
if arc4random() % 2 == 0 {
pagerBehaviour = .progressive(skipIntermediateViewControllers: arc4random() % 2 == 0, elasticIndicatorLimit: arc4random() % 2 == 0 )
} else {
pagerBehaviour = .common(skipIntermediateViewControllers: arc4random() % 2 == 0)
}
super.reloadPagerTabStripView()
}
}
Step 3: Your tab view controller:
class YourTabController: UIViewController, IndicatorInfoProvider {
var tabHeadingTitle = NSString()
// MARK: - Top Tab Bar Method - IndicatorInfoProvider
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
return IndicatorInfo(title: tabHeadingTitle as String)
}
}
Try this
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
var childrenVC: [UIViewController] = []
for eachCategory in postCategories {
let newVC = UIStoryboard(name: "Main (YOUR-STORYBOARD-NAME)", bundle: nil).instantiateViewController(withIdentifier: "FirstTVC") as? FirstTVC
newVC?.childName = eachCategory.name
childrenVC.append(newVC!)
}
return childrenVC
}

Retrieving firebase data of a tableview cell to a view controller's label

I have like a social app with a sort of newsfeed. if u click on the users name from a post in the newsfeed, you will go to his profile. Now i can't retrieve the data from that specific cell/post to the other viewController.
so i have to display the user's profile, with he's username, etc. but that doesn't work?
i have a Post model:
class Post {
private var _postDescription: String!
private var _profileImageURL: String?
private var _likes: Int!
private var _username: String!
private var _postKey: String!
private var _timeStamp: String!
private var _postRef: Firebase!
var postDescription: String? {
return _postDescription
}
var likes: Int {
return _likes
}
var username: String {
return _username
}
var postKey: String {
return _postKey
}
var profileImageURL: String? {
return _profileImageURL
}
init(description: String, username: String, profileImageURL: String?) {
self._postDescription = description
self._username = username
self._profileImageURL = profileImageURL
}
init(postKey: String, dictionary: Dictionary<String, AnyObject>) {
self._postKey = postKey
if let likes = dictionary["likes"] as? Int {
self._likes = likes
}
if let desc = dictionary ["description"] as? String {
self._postDescription = desc
}
if let imgUrl = dictionary["profileImg"] as? String {
self._profileImageURL = imgUrl
}
if let user = dictionary ["username"] as? String {
self._username = user
} else {
self._username = ""
}
self._postRef = DataService.ds.REF_POST.childByAppendingPath(self._postKey)
}
}
this is my profileVC:
class ProfileVC: UIViewController {
#IBOutlet weak var username: UILabel!
var post: Post?
override func viewDidLoad() {
super.viewDidLoad()
username.text = post.username // gives me a nil error.
}
}
and i use a TapGestureRecognizer in my tableViewCell to perform the segue.
in my cellForRowAtIndexPath:
let profileLblTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(NewsVC.goToProfileScreen(_:)))
profileLblTapRecognizer.numberOfTapsRequired = 1
profileLblTapRecognizer.delegate = self
cell.usernameLabel.tag = indexPath.row
cell.usernameLabel.userInteractionEnabled = true
cell.usernameLabel.addGestureRecognizer(profileLblTapRecognizer)
and the goToProfileScreen function:
func goToProfileScreen(gesture: UITapGestureRecognizer) {
self.performSegueWithIdentifier("ProfileScreen", sender: self)
}
this is my datamodel on firebase:
UPDATE:
i tried this instead:
let profileLblTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(NewsVC.prepareForSegue(_:sender:)))
with this function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ProfileScreen" {
if let cell = sender as? NewsCell, row = tableView.indexPathForCell(cell)?.row, vc = segue.destinationViewController as? ProfileVC {
vc.post = posts[row]
}
}
}
but that gave me an error on appDelegate: Thread 1: EXC_BAD_ACCESS(code=1, address = 0x1)
I've added this as an answer rather than a comment so that I can add and format some code examples.
When you call performSegueWithIdentifier, a NEW instance of the view controller identified by that segue is created, so all of its properties will be their defaults.
You have two ways of instantiating this view controller and setting properties before it loads. The first is the prepareForSegue option, in your case it may look something like this:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "ProfileScreen") {
let vc = segue.destinationViewController as! ProfileVC
vc.post = post
}
}
Another option is to create and present the view controller yourself, this example uses a storyboardID
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewControllerWithIdentifier("profileVC") as! ProfileVC
vc.post = post
presentViewController(vc, animated: false, completion: nil)
Update:
I'm not sure why you are adding a tap gesture recogniser to this, you could just use didSelectRowAtIndexPath, have a look at this other question and answer
you could have a property on your table view controller called selectedItem or something similar. and then in didSelectRowAtIndexPath set selectedItem to the item at the current index. Then in prepare for segue you would just do vc.post = selectedItem
Update Two:
After the op sharing their code privately, I noticed that the issue is that the user is using tapGestureRecogniser in the tableView. I added some code into the called function to get the row in which contained the tapped view, once I had the indexPath it was then easy to store it in a temporary property and retreive later in the prepareForSegue method, details below
// temp property
var selectedPost:Post?
// function called on tap
func viewProfile(sender:UITapGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.Ended) {
let point = sender.locationInView(self.tableView)
if let indexPath = self.tableView.indexPathForRowAtPoint(point) {
selectedPost = self.posts[indexPath.row]
performSegueWithIdentifier("ProfileScreen", sender: self)
}
}
}
// Prepare for segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "ProfileScreen") {
let vc = segue.destinationViewController as! ProfileVC
if let post = selectedPost {
vc.post = post
}
}
}

Resources