How do I reload UIScrollView containing ViewControllers to add/remove view controllers - swift

I have a scrollView that contains a dynamic amount of WeatherViewControllers each displaying the weather data of a different city the user has saved. The user can segue from the WeatherViewControllers to a CityListViewController. Where they can add and remove cities from their list which in turn should add and remove WeatherViewControllers from the scrollView upon dismissing the CityListViewController, this is where I am running into a problem.
Currently I am trying to use a protocol to call viewDidLoad in the scrollViewController upon dismissing the CityListViewController but am getting an error:
Fatal error: Unexpectedly found nil while unwrapping an Optional
value: file)
when it gets to:
let weatherScreen = storyboard!.instantiateViewController(identifier: "View Controller") as! ViewController
Side Note: Upon initially opening the app the scrollView loads properly with all the correct WeatherViewControllers in the UIScrollView and the correct cities in the list.
class ScrollViewController: UIViewController, ScrollReloadProtocol {
func reloadScrollView() {
print("SCROLL RELOADED!!!!!*******")
self.viewDidLoad()
}
#IBOutlet weak var totalScrollView: UIScrollView!
var pages = [ViewController]()
var x = 0
var weatherScreensArray = [SavedCityEntity]()
var weatherScreenStringArray = [String]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var horizString = "H:|[page1(==view)]"
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
//userDefaults used to keep track of which screen is which to put different cities on different viewControllers
defaults.set(0, forKey: "screenNumber")
//load cities to get number of cities saved
loadCities()
var views : [String: UIView] = ["view": view]
//create all weatherWeatherControllers
while x <= weatherScreensArray.count {
pages.append(createAndAddWeatherScreen(number: x))
weatherScreenStringArray.append("page\(x+1)")
views["\(weatherScreenStringArray[x])"] = pages[x].view
let addToHoriz = "[\(weatherScreenStringArray[x])(==view)]"
horizString.append(addToHoriz)
x+=1
}
horizString.append("|")
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[page1(==view)]|", options: [], metrics: nil, views: views)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: horizString, options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
NSLayoutConstraint.activate(verticalConstraints + horizontalConstraints)
}
//Function to create and add weatherViewController
func createAndAddWeatherScreen(number: Int) -> ViewController {
defaults.set(number, forKey: "screenNumber")
let weatherScreen = storyboard!.instantiateViewController(identifier: "View Controller") as! ViewController
weatherScreen.view.translatesAutoresizingMaskIntoConstraints = false
totalScrollView.addSubview(weatherScreen.view)
addChild(weatherScreen)
weatherScreen.didMove(toParent: self)
return weatherScreen
}
}

You could try something like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let weatherScreen = storyboard.instantiateViewController(withIdentifier: "View Controller") as! ViewController
It's also probably better to use one word for the identifier: "ViewController", and give it the same name as the class. Make sure to set these values also in your actual storyboard.

Related

Change ViewController from Tabbar

I have a UITabBarController with 3 tabs and a SettingsVC where you can choose the appearance of MainVC (There are 2 different ViewControllers which based on what user prefers should be shown as the MainVC).
I made the application and it works but it's really buggy, cause I was just pushing the ViewControllers on top of each other, and in some certain conditions you see bugs while switching tabs.
Main.storyboard
Here is some part of my code(after trying a lot of stuff) which changes the VC's based on UserDefaults
CheckSettingsStatus.swift
//
// Check for Double Column Settings and navigate to Double Column VC
//
func checkMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 1{
//let appDel = UIApplication.shared.delegate as! AppDelegate
//appDel.goToDoubleMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "DoubleColumnMainVC") as! DoubleColumnMainVC
self.navigationController?.viewControllers[0] = mainVC
//navigationController?.pushViewController(mainVC, animated: false)
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
self.navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
//
// same as function above but goes to MainVC
//
func checkDoubleColumnMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 0{
// let appDel = UIApplication.shared.delegate as! AppDelegate
// appDel.goToMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "MainVC") as! MainVC
self.navigationController?.viewControllers[0] = mainVC
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
With this in ViewWillAppear of both this Controllers I call these functions to switch accordingly.
Any ideas what I'm doing wrong? Can I push controllers from UITabBarController and not NavigationController? something like:
tabBarController.pushViewController(mainVC, animated: false)
instead of:
navigationController.pushViewController(mainVC, animated: false)
Thank you in advance
UPDATE:
In my best case when everything works nicely, on first launch of the app (to the second mainVC) the Buttons in NavBar doesn't work.
EDIT:
I just realised that the NavigationBar that I'm seeing above my SecondMainVC is the NavigationBar from MainVC, that is why the buttons are not working. how is that possible?
I faced same issue while trying to develop a tab bar controller in storyboard , for this reason I found that the best way is to implement it by code.
so you can make your code look like this :
1) create a a sub class of UITabController and let it conform to UITabBarControllerDelegate
class TabBarVC: UITabBarController , UITabBarControllerDelegate {
2)then write this method in your class to initialize the tabs in your project:
private func settingUpTabBarAndInitializingViewControllers(){
//self delagting
self.delegate = self
//home tab
let homeStoryboard = UIStoryboard(name: Constants.StoryBoards.homeStoryboard, bundle: nil)
let homeVC = homeStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.homeScreen) as! HomeVC
// shop tab
let shopStoybaord = UIStoryboard(name: Constants.StoryBoards.shopStoryboard, bundle: nil)
let shopVC = shopStoybaord.instantiateViewController(withIdentifier: Constants.ControllersIDs.shopScreen) as! ShopVC
//offers tab
let offersStoryboard = UIStoryboard(name: Constants.StoryBoards.offersStoryboard, bundle: nil)
let offersVC = offersStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.offersScreen) as! OffersVC
//more tab
let moreStoryboard = UIStoryboard(name: Constants.StoryBoards.MoreScreenStoryboard, bundle: nil)
let moreVC = moreStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.moreVCScreen) as! MoreOptionsTVC
//setting VCs
self.viewControllers = [homeVC , shopVC , offersVC , moreVC]
//setting buttons
//buttons images
let homeImage = UIImage(named: "TabHome")
let shopImage = UIImage(named : "TabShop")
let offerImage = UIImage(named: "TabOffers")
let moreImage = UIImage(named: "TabMenu")
// buttons shakhsiyan
let homeButton = UITabBarItem(title: homeText, image: homeImage, selectedImage: homeTappedImage)
let shopButton = UITabBarItem(title: shopText, image: shopImage, selectedImage: shopTappedImage)
let offersButton = UITabBarItem(title: offersText, image: offerImage, selectedImage: offersTappedImage)
let moreButton = UITabBarItem(title: moreText, image: moreImage, selectedImage: moreTappedImage)
homeVC.tabBarItem = homeButton
shopVC.tabBarItem = shopButton
offersVC.tabBarItem = offersButton
moreVC.tabBarItem = moreButton
}
P.S. you can add tabs as much as you want and while scrolling in them you wont face any bug,
make sure to have only one navigation controller before your tab bar controller.
Check this hope it helps
Ok you can create a DEMO to see how thing works.After that you can implement it in your project.I know it may not look good to you but read it once and see image attached i am sure will get your answer.
1.I have created two UIViewControllers with the UITabBarController.
2.I have created class ItemFirstVC,ItemTwoVC and assigned to UIViewControllers in storyboard
3.Each controller is embedded in UINavigationController
4.Create a custom class of UITabBarController
5.Assign it to UITabBarController in storyboard
6.Now Each UIViewController has a button named as Firs Button,Second Button
7.From each button i have connected Action Segue to one more controller as you can see in image.
8.Now i have created a third UIViewController which is separate from UITabBarController.
9.Now we will load that Third UIViewController in UITabBarController
10.Create one more class ItemThirdVC and assign it to third UIViewController.
11.It also has a button named as third button action segued to one more UIViewController
12.Now switch to custom class of UITabBarContoller
// variable required to see work in action
var count = 0
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// remember to assign delegate
delegate = self
}
// this delegate indicates whether this tab should be selected or not
extension CustomTabBarController : UITabBarControllerDelegate{
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// switch to UIViewController tabs multiple times
count = count + 1
if(count == 5){
// when count reaches 5 a new controller will load
let navigationController = viewController as? UINavigationController
guard navigationController != nil else {return true}
let viewController = storyboard?.instantiateViewController(withIdentifier: "ItemThreeVC")
guard let vc = viewController else {return true}
navigationController?.setViewControllers([vc], animated: false)
}
return true
}
}

How to access a function in another class being within the same instance?

I am currently creating a login project in Swift 3.
I have a main view controller which contains
a container, which includes a page viewcontroller and multiple pages
a custom "menu" at the bottom which includes two buttons for navigating the page view controller and a custom page control
This is my storyboard setup
The view the user will be presented with is made of the pages controlled by the page viewcontroller, at the top, and the control menu, at the bottom. When the user clicks on the buttons in the control menu this code will be executed in the main viewcontroller:
#IBAction func buttonNext(_ sender: UIButton) {
print("buttonNext has been pressed!")
nextPageWithIndex(index: nextIndex)
}
and here is the function it calls:
func nextPageWithIndex(index: Int) {
let nextWalkthroughVC = pages[index]
pageContainer.setViewControllers([nextWalkthroughVC], direction: .forward, animated: true, completion: nil)
print("nextPageWithIndex has been executed with Index \(index)!")
if currentIndex as Int? == 2 {
currentIndex = 2
} else if currentIndex as Int? == 1 {
currentIndex = 2
} else if currentIndex as Int? == 0 {
currentIndex = 1
} else {
currentIndex = 0
print("currentIndex was in inpossible state!")
}
checkButtonNavigation()
assignIndexes()
if let index = currentIndex {
pageControl.currentPage = index
print("PageControl updated")
}
}
checkButtonNavigation() checks if the buttons at the bottom should be displayed depending on the page which is currently being displayed.
I am trying to implement a button of one of the pages (in this case page1) which navigates the user to the next page.
I tried to execute the function nextPageWithIndex(index: 1) with varAccountRegisterMainViewController.nextPageWithIndex(index: 1) on the action of pressing the button in the page1 viewcontroller class by defining:
var varAccountRegisterMainViewController: accountRegisterMainViewController!
in the AppDelegate,
and:
varAccountRegisterMainViewController = accountRegisterMainViewController()
in the page1 viewcontroller.
Sadly that doesn't seem to work. I get "fatal error: Index out of range" at the point:
let nextWalkthroughVC = pages[index]
in the function nextPageWithIndex(index: 1) in the main viewcontroller. I checked what count the array "pages" has. It's 0. When I execute the function right from the main viewcontroller, though, it has 3 as it should have. Here is the error as an image.
Thank you for your help!
EDIT:
here is how the pages Array is set-up:
At the beginning of the main viewcontroller class the following code is added:
var pages = [UIViewController]()
In the viewDidLoad() that code:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let page1: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage1ViewController")
let page2: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage2ViewController")
let page3: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage3ViewController")
pages.append(page1)
pages.append(page2)
pages.append(page3)
ANOTHER EDIT:
I put the declaration of "pages" also into the externalNext() function. Now that problem is solved but there is another one... this is how the function looks now:
func externalNext(index: Int) {
var pageContainer: UIPageViewController!
var pages = [UIViewController]()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let page1: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage1ViewController")
let page2: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage2ViewController")
let page3: UIViewController! = storyboard.instantiateViewController(withIdentifier: "IDAccountRegisterPage3ViewController")
pages.append(page1)
pages.append(page2)
pages.append(page3)
// Create the page container
pageContainer = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
pageContainer.delegate = self
pageContainer.dataSource = self
pageContainer.setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
// Add it to the view
view.addSubview(pageContainer.view)
pageContainer.view.addSubview(pageControlView)
print("Current array count of pages is \(pages.count)")
print("Function: nextPageWithIndex has been called with index \(index)")
let nextWalkthroughVC = pages[index]
pageContainer.setViewControllers([nextWalkthroughVC], direction: .forward, animated: true, completion: nil)
print("nextPageWithIndex has been executed with Index \(index)!")
pageControl.currentPage = index
print("PageControl updated")
print("Hello")
}
I copied the code from the top of the main viewcontroller class and just pasted it into the function. I alway get the error "fatal error: unexpectedly found nil while unwrapping an Optional value", though.
Is there any way I can redefine the objects in the function?
Or is there any elegant way? My way looks very messy...
Thanks to Phillip Mills I know that is has something to do with being another instance of the class main viewcontroller. How can I access the function in there but within the same instance? The main viewcontroller is visible all the time.
Thank you!
When you do this: accountRegisterMainViewController() you are creating a new object, a different instance of that class. Since the new one is not being displayed, viewDidLoad is never called and your array is empty.
You need to arrange links between objects so that the one loaded from your storyboard is also the one that nextPageWithIndex is called on.

Correct release NSViewController embedded in subviews

I have a task to write a small app where various controllers must be embedded in default controller. All controllers stored in one storyboard.
Sample code of embedding controllers in subviews
if let id = getControllerId(pageIndex) { // get controller's storyboard id by segmented index
let storyBoard = NSStoryboard(name: "Main", bundle: nil)
let controller = storyBoard.instantiateControllerWithIdentifier(id) as! NSViewController
controller.view.translatesAutoresizingMaskIntoConstraints = false
if self.view.subviews.count > 0 {
let prevView = self.view.subviews[0]
prevView.removeFromSuperview() // here should be releasing previous controller
}
self.view.addSubview(controller.view)
// make all side constraints
let views = ["view": controller.view]
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-(0)-[view]-(0)-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(0)-[view]-(0)-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
} else {
NSLog("ERROR: Unable get controller storyboard id for index \(pageIndex)")
}
And I noticed that embedded controller don't execute viewWillDisappear. I need this event for clearing observers and some other stuff.
I'm not sure it's correct way to show controllers as embedded in subviews, but i don't found any other solution.
I make sample project to test this situation
https://github.com/avvensis/embeddedviewcontrollers
Could anybody help me with this trouble?
viewWillDisappear wont execute as you dont hide anything.
You controller dies quickly after you create him. So step #1 is to hold a reference on it:
class ViewController: NSViewController {
// MARK: - Custom properties
let pageIds: [String] = ["redController", "yellowController", "greenController"]
var currentControler : NSViewController!
...
private func showEmbeddedController(pageIndex: Int) {
if let id = getControllerId(pageIndex) { // get controller's storyboard id by segmented index
let storyBoard = NSStoryboard(name: "Main", bundle: nil)
currentControler = storyBoard.instantiateControllerWithIdentifier(id) as! NSViewController
currentControler.view.translatesAutoresizingMaskIntoConstraints = false
if self.view.subviews.count > 0 {
let prevView = self.view.subviews[0]
prevView.removeFromSuperview() // here should be releasing previous controller
}
self.view.addSubview(currentControler.view)
// make all side constraints
let views = ["view": currentControler.view]
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-(0)-[view]-(0)-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(0)-[view]-(0)-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
} else {
NSLog("ERROR: Unable get controller storyboard id for index \(pageIndex)")
}
}
}
Then just have deinit method in your base controller:
class EmbeddedViewController: NSViewController {
....
deinit {
print("DEBUG: \(self.className) deinit")
}
}

Having issues setting delegate with Observer Pattern

I'm trying to realize the Observer Pattern and I'm experiencing some difficulty as my delegate doesn't seem to be setting properly.
In my Main.storyboard I have a ViewController with a container view. I also have an input box where I'm capturing numbers from a number keypad.
Here's my storyboard:
I'm trying to implement my own Observer Pattern using a protocol that looks like this:
protocol PropertyObserverDelegate {
func willChangePropertyValue(newPropertyValue:Int)
func didChangePropertyValue(oldPropertyValue:Int)
}
My main ViewController.swift
class ViewController: UIViewController {
#IBOutlet weak var numberField: UITextField!
// observer placeholder to be initialized in implementing controller
var observer : PropertyObserverDelegate?
var enteredNumber: Int = 0 {
willSet(newValue) {
print("//Two: willSet \(observer)") // nil !
observer?.willChangePropertyValue(5) // hard coded value for testing
}
didSet {
print("//Three: didSet")
observer?.didChangePropertyValue(5) // hard coded value for testing
}
}
#IBAction func numbersEntered(sender: UITextField) {
guard let inputString = numberField.text else {
return
}
guard let number : Int = Int(inputString) else {
return
}
print("//One: \(number)")
self.enteredNumber = number // fires my property observer
}
}
My ObservingViewController:
class ObservingViewController: UIViewController, PropertyObserverDelegate {
// never fires!
func willChangePropertyValue(newPropertyValue: Int) {
print("//four")
print(newPropertyValue)
}
// never fires!
func didChangePropertyValue(oldPropertyValue: Int) {
print("//five")
print(oldPropertyValue)
}
override func viewDidLoad() {
super.viewDidLoad()
print("view loads")
// attempting to set my delegate
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
print("//six \(pvc)")
pvc.observer = self
}
}
Here's what my console prints:
What's happening
As you can see when my willSet fires, my observer is nil which indicates that I have failed to set my delegate in my ObservingViewController. I thought I set my delegate using these lines:
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
print("//six \(pvc)")
pvc.observer = self
However, I must be setting my delegate incorrectly if it's coming back nil.
Question
How do I properly set my delegate?
You are calling into the storyboard to instantiate a view controller and setting it as the observer, however that instantiates a new instance of that view controller, it doesn't mean that it is referencing the one single "view controller" that is in the storyboard. ObservingViewController needs another way to reference the ViewController that has already been created.
So #Chris did reenforce my suspicions which helped me to figure out a solution for assigning my delegate to my view controller properly.
In my ObservingViewController I just need to replace the code in my viewDidLoad with the following:
override func viewDidLoad() {
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let vc = app.window?.rootViewController as! ViewController
vc.observer = self
}
Rather than creating a new instance of my view controller, I'm now getting my actual view controller.

Sending array data from one view controller to another

I am trying to copy an array from one view controller to another view controller, but somehow it does not work.. I have tried using this code:
let otherVC = TerningspilletViewController()
mineSpillere = otherVC.mineSpillere2
println("otherVC")
In the view controller i want to send the data from has this code:
var mineSpillere = ["Spiller 1", "Spiller 2", "Spiller 3"]
The view controller that is going to received this data, has this code:
var mineSpillere2 = [String]()
the "var mineSpillere" is going to show the text, but when i try to show it, it says that the "var mineSpillere" is empty. Any suggestions/ideas?
If you want to access array of another viewController then you can save it in memory and for that you can use NSUserDefaults this way:
//save
var mineSpillere = ["Spiller 1", "Spiller 2", "Spiller 3"]
var defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(mineSpillere, forKey: "YourKey")
Now you can read it from anywhere this way:
//read
if let testArray : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("YourKey") {
var readArray : [NSString] = testArray! as! [NSString]
println(readArray)
}
And if you want to do with your old way here is code:
FirstViewController.swift
import UIKit
class FirstViewController: UIViewController {
var mineSpillere = ["Spiller 1", "Spiller 2", "Spiller 3"]
override func viewDidLoad() {
super.viewDidLoad()
}
}
SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
var mineSpillere2 = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let otherVC = FirstViewController()
mineSpillere2 = otherVC.mineSpillere
println(mineSpillere2) //[Spiller 1, Spiller 2, Spiller 3]
}
}
first checkout are you using story board
if you are using storyboard
let otherVc = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("storyboardIdendifier") as? TerningspilletViewController
otherVC.mineSpillere2 = mineSpillere
println(otherVc)
otherwise do like this
let otherVC = TerningspilletViewController()
otherVC.mineSpillere2 = mineSpillere
println("otherVC")
if you are using storyboards, I suggest passing the data in prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "TerningspilletViewSegue" {
let otherVc = segue.destinationViewController as! TerningspilletViewController
otherVc.mineSpillere = mineSpillere
}
}
Try this code working fine sending data from one viewController to another viewcontroller.just reference of storyboard id you can send data from one view to another view .Then we create that class object based on that object reference we can access that array and those class properties then just append it.
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let secondView = storyBoard.instantiateViewController(withIdentifier: "tableView") as! TableViewController.
self.present(secondView, animated: true) {
let data = nameArray
secondView.models.append(data!)
secondView.tableView.reloadData()
}