I need a toolbar underneath my table so here I am.
xcode 7.3.1
Swift 2
I've got a Table View added to a basic View Controller.
I dragged the delegate and datasource outlets from the table view to the view controller in storyboard.
I dragged a table cell to my table view. I set my reuseIdentifier to "cell"
I set the cell type to Details
Table type is Plain
Here's my customized View Controller class (it has explanatory comments throughout):
import Foundation
import UIKit
import SwiftyJSON
import Alamofire
class ScheduleViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let cellIdentifier = "cell" // also tried: let cellIdentifier = "cellIdentifier"
#IBOutlet var tableView: UITableView!
var Titles:Array< String > = Array < String >()
var Details:Array< String > = Array < String >()
override func viewDidLoad() {
super.viewDidLoad()
var height = UIApplication.sharedApplication().statusBarFrame.size.height
var insets = UIEdgeInsets(top: height, left: 0, bottom: 0, right: 0)
/* if any of the following four lines are not commented out it throws an error after successful build, in simulator: fatal error: unexpectedly found nil while unwrapping an Optional value */
// self.tableView.contentInset = insets
// self.tableView.scrollIndicatorInsets = insets
// self.tableView.separatorColor = UIColor.grayColor();
// self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine;
manager.request(.POST, getUrl(), parameters:["dev": 1]).responseJSON {response in
print(response)
var json = JSON(response.result.value!);
print(json)
if !json["logged_in"]
{
self.performSegueWithIdentifier("LoginController", sender: self)
return;
}
for (_,subJson):(String, JSON) in json["shifts"]
{
self.Titles.append(subJson["title"].string!);
self.Details.append(subJson["detail"].string!);
}
// everything in this async function goes swimmingly until the next line
self.do_table_refresh();
// see do_table_refresh function for error detail
}
}
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData() /* < this line here throws the same error as the four lines mentioned above: "fatal error: unexpectedly found nil while unwrapping an Optional value." it builds fine (no error indicated until the simulator gets to the point of running the function) */
return
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Titles.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = Titles[indexPath.row]
cell.detailTextLabel?.text = Details[indexPath.row]
if Details[indexPath.row] == "OFF"
{
cell.textLabel!.textColor = UIColor(red: 214/255, green: 214/255, blue: 214/255, alpha: 1)
cell.detailTextLabel!.textColor = UIColor(red: 214/255, green: 214/255, blue: 214/255, alpha: 1)
}
else
{
cell.textLabel!.textColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 1)
cell.detailTextLabel!.textColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 1)
}
return cell
}
}
These functions all work fine when I'm using a TableView Controller, but I need a toolbar.
Any one of the following five lines (the first four found in the viewDidLoad function and last in the do_table_refresh function), the app will fail (after successful build) with: fatal error: unexpectedly found nil while unwrapping an Optional value
// self.tableView.contentInset = insets
// self.tableView.scrollIndicatorInsets = insets
// self.tableView.separatorColor = UIColor.grayColor();
// self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine;
// self.tableView.reloadData()
Back in the Table View Controller, these lines don't give me that error, so I'm assuming something is empty in tableView.
What am I overlooking?
Inside your viewDidLoad() method, you need to add the delegate and dataSource to link your table to the view controller.
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableview.dataSource = self
}
Try this.
dispatch_async(dispatch_get_main_queue(),{
self.do_table_refresh();
})
Related
I am trying to build an app which has object of Items and a controller for it which is called ItemsController. Also on my screen I suppose to have a table view and a button on it. The functionality of button is when it is clicked it needs to check favoriteItems array in ItemsController if it is found in that array remove element, if it isn't found there then append it to array. Operations for remove and append good but unfortunately all cells are not working together. They creates their own favoriteItems array.
To achieve this I tried to make ItemsController to be a singleton. I changed class to a struct and called ItemsController.sharedInstance everywhere. It actually solved a lot of my problem and I believe that the source I learnt this is not wrong about it. But why my tableview cells doesn't use ItemsController's favoriteItems array instead create theirs?
struct ItemsController {
var favoriteItems = [Items]()
static var sharedInstance = ItemsController()
init(){
itemsArray = [...items...]
}
}
class TableViewCell: UITableViewCell {
#IBOutlet weak var favoriteButton: UIButton!
let buttonFirstColor : UIColor = UIColor.clear
let buttonSecondColor : UIColor = UIColor(red: 224/255.0, green: 74/255.0, blue: 94/255.0, alpha: 1.0)
var itemsController = ItemsController.sharedInstance
var itemDedicated = Items(name: "", pic: "", aciklama: "")
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
favoriteButton.layer.cornerRadius = 0.5 * favoriteButton.bounds.size.width
favoriteButton.backgroundColor = UIColor.clear
favoriteButton.layer.borderColor = UIColor(red: 224/255.0, green: 74/255.0, blue: 94/255.0, alpha: 1.0).cgColor
favoriteButton.layer.borderWidth = 4.0
favoriteButton.clipsToBounds = true
setFavoriteButton()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func favoriteButtonClicked(_ sender: Any) {
var favoriteFound = false
for items in itemsController.favoriteItems{
if(items.name == itemDedicated.name){
favoriteFound = true }
}
if(favoriteFound){
itemsController.favoriteItems.remove(at: itemsController.favoriteItems.index(of: itemDedicated)!)
} else {
itemsController.favoriteItems.append(itemDedicated)
}
setFavoriteButton()
}
private func setFavoriteButton(){
var favoriteFound = false
for items in itemsController.favoriteItems{
if(items.name == itemDedicated.name){
favoriteFound = true }
}
if(favoriteFound){
favoriteButton.backgroundColor = buttonSecondColor
favoriteButton.setTitleColor(buttonFirstColor, for: .normal)
} else {
favoriteButton.backgroundColor = buttonFirstColor
favoriteButton.setTitleColor(buttonFirstColor, for: .normal)
}
}
}
I have a tableView which is a subclass of a Parallax table view I found on Github. Done all table view set up as normal (Deleted some of it since its irrelevant).
class EventDetailTableViewController: ParallaxTableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Set the image:
self.imageView.image = eventImage
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.translucent = true
}
// MARK: - UITableViewDelegate
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 4
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// SET UP CELL
}
#IBAction func backButtonPressed(sender: AnyObject) {
print("backButtonPressed")
self.navigationController?.navigationBar.translucent = false
self.navigationController?.navigationBar.barTintColor = UIColor(red: 227/255, green: 38/255, blue: 54/255, alpha: 1.0)
self.navigationController?.popViewControllerAnimated(true)
}
} // END OF VIEWCONTROLLER CLASS
Important things to note here is I have made the navigationBar transparent. When backButtonPressed is called, I wish to change back the navigationBar.Color to the color of the previous View Controller (Red). So I have coded that in the backButtonPressed()
Here is the Parallax code I have. Please view the last func moveImage(). I have the simple effect of when the tableview scrolls down far enough to the description the navigationBar appears with the title "Event Description"
class ParallaxTableViewController: UITableViewController {
// Create the UIView
//var bottomFloatingView = UIView()
// Create the UIImageView
let imageView = UIImageView()
// Set the factor for the parallaxEffect. This is overwritable.
var parallaxFactor:CGFloat = 2
// Set the default height for the image on the top.
var imageHeight:CGFloat = 320 {
didSet {
moveImage()
}
}
// Initialize the scrollOffset varaible.
var scrollOffset:CGFloat = 0 {
didSet {
moveImage()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Set the contentInset to make room for the image.
self.tableView.contentInset = UIEdgeInsets(top: imageHeight, left: 0, bottom: 0, right: 0)
// Change the contentMode for the ImageView.
imageView.contentMode = UIViewContentMode.ScaleAspectFill
// Add the imageView to the TableView and send it to the back.
view.addSubview(imageView)
view.sendSubviewToBack(imageView)
}
override func viewDidLayoutSubviews() {
// Update the image position after layout changes.
moveImage()
}
// Define method for image location changes.
func moveImage() {
let imageOffset = (scrollOffset >= 0) ? scrollOffset / parallaxFactor : 0
let imageHeight = (scrollOffset >= 0) ? self.imageHeight : self.imageHeight - scrollOffset
imageView.frame = CGRect(x: 0, y: -imageHeight + imageOffset, width: view.bounds.width, height: imageHeight)
if imageOffset > 150 {
print("imageOffSet")
self.navigationItem.title = "Event Description"
self.navigationController?.navigationBar.translucent = false
self.navigationController?.navigationBar.barTintColor = UIColor.whiteColor()
self.navigationController?.navigationBar.shadowImage = nil
} else {
print("else")
self.navigationItem.title = ""
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.translucent = true
// This is being called after the back button is being pressed. Which means this else function overrides my backButtonPressed() therefore making my navigation bar in the new VC not the correct color.
}
}
}
As you can see in my notes, this else function is being called again after the backButtonPressed() is called. Therefore it does not give me the desired red in the new VC but it leaves it translucent instead. How can I stop this else function being called when the backButtonPressed is being called?
viewDidLayoutSubviews should never be involved in responding to user events. It is an internal lifecycle method that is rarely used unless you're adding or modifying programmatic NSLayoutConstraints to those already added in Storyboard. It is called quite frequently and apparent redundancy.
Use table delegate methods instead. In particular UITableViewDelegate inherits from UIScrollViewDelegate so reference:
UITableView Scroll event
Then query the tableView itself for visibility information e.g. indexPathsForVisibleRows:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/indexPathsForVisibleRows
You should not use viewDidLayoutSubviews() like this as BaseZen has explained in his answer.
You can use scrollViewDidScroll to achieve what you want.
Replace this
#IBAction func backButtonPressed(sender: AnyObject) {
print("backButtonPressed")
self.navigationController?.navigationBar.translucent = false
self.navigationController?.navigationBar.barTintColor = UIColor(red: 227/255, green: 38/255, blue: 54/255, alpha: 1.0)
self.navigationController?.popViewControllerAnimated(true)
}
With this
var backButtonWasPressed = false
#IBAction func backPressed(sender: AnyObject) {
print("backButtonPressed")
backButtonWasPressed = true
self.navigationController?.navigationBar.translucent = false
self.navigationController?.navigationBar.barTintColor = UIColor(red: 227/255, green: 38/255, blue: 54/255, alpha: 1.0)
self.navigationController?.popViewControllerAnimated(true)
}
And replace this
override func viewDidLayoutSubviews() {
// Update the image position after layout changes.
moveImage()
}
With this
override func scrollViewDidScroll(scrollView: UIScrollView) {
if !backButtonWasPressed {
moveImage()
}
}
And put backButtonWasPressed = false in your viewDidLoad()
I am implementing custom horizontal multiple segments with table view controller as content of each segment. But how to implement only one controller for all segments, because view is same for all segments but only data is change.
I want to pass current index of segment to table view controller to get particular data.
I am using CAPSPageMenu.swift file from -https://codeload.github.com/uacaps/PageMenu/zip/master?
here is my code-
class Entrance_main_controller: UIViewController {
var pageMenu : CAPSPageMenu?
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// MARK: - UI Setup
self.title = "Entrance Exam"
// Initialize view controllers to display and place in array
var controllerArray : [UIViewController] = []
let controller1 : Entrance_Content_ViewController = Entrance_Content_ViewController(nibName: "TestTableViewController", bundle: nil)
controller1.title = "Engineering"
controllerArray.append(controller1)
let controller2 : Entrance_Content_ViewController = Entrance_Content_ViewController(nibName: "TestTableViewController", bundle: nil)
controller2.title = "Medical"
controllerArray.append(controller2)
let controller3 : Entrance_Content_ViewController = Entrance_Content_ViewController(nibName: "TestTableViewController", bundle: nil)
controller3.title = "Management"
controllerArray.append(controller3)
// Customize menu (Optional)
let parameters: [CAPSPageMenuOption] = [
.ScrollMenuBackgroundColor(UIColor(red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0)),
.ViewBackgroundColor(UIColor(red: 20.0/255.0, green: 20.0/255.0, blue: 20.0/255.0, alpha: 1.0)),
.SelectionIndicatorColor(UIColor.orangeColor()),
.BottomMenuHairlineColor(UIColor(red: 70.0/255.0, green: 70.0/255.0, blue: 80.0/255.0, alpha: 1.0)),
.MenuItemFont(UIFont(name: "HelveticaNeue", size: 13.0)!),
.MenuHeight(40.0),
.MenuItemWidth(90.0),
.CenterMenuItems(true)
]
// Initialize scroll menu
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 0.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)
self.addChildViewController(pageMenu!)
self.main_content_view.addSubview(pageMenu!.view)
pageMenu!.didMoveToParentViewController(self)
}
//here I am getting current index
func get_current_page() -> Int {
let currentIndex = pageMenu!.currentPageIndex
return currentIndex;
}
// MARK: - Container View Controller
override func shouldAutomaticallyForwardAppearanceMethods() -> Bool {
return true
}
override func shouldAutomaticallyForwardRotationMethods() -> Bool {
return true
}
}
here I am using Entrance_Content_ViewController class for all segments.In this controller I am calling get_current_page() method for current page index.
class Entrance_Content_ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let entrance_main_controller = Entrance_main_controller()
let page = entrance_main_controller.get_current_page()
print("page no\(page)")
self.tableView.registerNib(UINib(nibName: "EntranceTableViewCell", bundle: nil), forCellReuseIdentifier: "EntranceTableViewCell")
}
But I am getting this error- fatal error: unexpectedly found nil while unwrapping an Optional value(lldb)
Please help me to solve this problem.
thank you
The basic idea you can implement is, maintain array of data for each segment. Then use switch over the segment index and as per segment index assign tableView DataSource.
The rough code should be like this:
switch(segmentIndex)
{
case: 0 yourDatasourceArray = segment0_DataArray
:
:
//like this for all segments.
}
I am using uacaps/CAPSPageMenu in my app and I cannot figure out how to get the number of the active page so I can show a webpage corresponding to the page. In the code CAPSPageMenu.swift the following code is used.
import UIKit
#objc public protocol CAPSPageMenuDelegate {
// MARK: - Delegate functions
optional func willMoveToPage(controller: UIViewController, index: Int)
optional func didMoveToPage(controller: UIViewController, index: Int)
}
I think the index: Int part in the optional func didMoveToPage is giving me a number of the current page. Is this right? How do I get that index number in the ViewController.swift code?
EDIT
In CAPSPageMenu I found this code.
public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
if scrollView.isEqual(controllerScrollView) {
// Call didMoveToPage delegate function
let currentController = controllerArray[currentPageIndex]
delegate?.didMoveToPage?(currentController, index: currentPageIndex)
The ViewController.swift code as shown below.
//
// ViewController.swift
// PageMenuDemoStoryboard
//
// Created by Niklas Fahl on 12/19/14.
// Copyright (c) 2014 CAPS. All rights reserved.
//
import UIKit
var websiteArray = ["http://website_0", "http://website_1", "http://website_2"]
//class ViewController: UIViewController {
class ViewController: UIViewController,CAPSPageMenuDelegate {
var pageMenu : CAPSPageMenu?
var pageNumber = 0
#IBOutlet weak var showWebsite: UIWebView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// MARK: - UI Setup
self.title = "App Title"
self.navigationController?.navigationBar.barTintColor = UIColor(red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.barStyle = UIBarStyle.Black
self.navigationController?.navigationBar.tintColor = UIColor.whiteColor()
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.orangeColor()]
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "<-", style: UIBarButtonItemStyle.Done, target: self, action: "didTapGoToLeft")
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "->", style: UIBarButtonItemStyle.Done, target: self, action: "didTapGoToRight")
// MARK: - Scroll menu setup
// Initialize view controllers to display and place in array
var controllerArray : [UIViewController] = []
let controller1 : TestCollectionViewController = TestCollectionViewController(nibName: "TestCollectionViewController", bundle: nil)
controller1.title = "Item_0"
controller1.photoNameArray = ["Item_0.png"]
controllerArray.append(controller1)
let controller2 : TestCollectionViewController = TestCollectionViewController(nibName: "TestCollectionViewController", bundle: nil)
controller2.title = "Item_1"
controller2.photoNameArray = ["Item_1.png"]
controllerArray.append(controller2)
let controller3 : TestCollectionViewController = TestCollectionViewController(nibName: "TestCollectionViewController", bundle: nil)
controller3.title = "Item_2"
controller3.photoNameArray = ["Item_2.png"]
controllerArray.append(controller3)
// Customize menu (Optional)
let parameters: [CAPSPageMenuOption] = [
.ScrollMenuBackgroundColor(UIColor(red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0)),
.ViewBackgroundColor(UIColor(red: 20.0/255.0, green: 20.0/255.0, blue: 20.0/255.0, alpha: 1.0)),
.SelectionIndicatorColor(UIColor.orangeColor()),
.BottomMenuHairlineColor(UIColor(red: 70.0/255.0, green: 70.0/255.0, blue: 80.0/255.0, alpha: 1.0)),
.MenuItemFont(UIFont(name: "HelveticaNeue", size: 13.0)!),
.MenuHeight(30.0),
.MenuItemWidth(90.0),
.CenterMenuItems(true)
]
// Initialize scroll menu
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, self.view.frame.height * 0.71, self.view.frame.width, self.view.frame.height * 0.33), pageMenuOptions: parameters)
self.addChildViewController(pageMenu!)
self.view.addSubview(pageMenu!.view)
pageMenu!.didMoveToParentViewController(self)
pageMenu!.delegate = self
func willMoveToPage(controller: UIViewController, index: Int) {
let subview=controller as! ViewController
subview.pageNumber=index;
}
}
func didTapGoToLeft() {
var currentIndex = pageMenu!.currentPageIndex
if currentIndex > 0 {
pageMenu!.moveToPage(currentIndex - 1)
}
setWebPage()
}
func didTapGoToRight() {
var currentIndex = pageMenu!.currentPageIndex
if currentIndex < pageMenu!.controllerArray.count {
pageMenu!.moveToPage(currentIndex + 1)
}
setWebPage()
}
func setWebPage() {
var currentIndex = pageMenu!.currentPageIndex
switch currentIndex {
case 0:
let url = NSURL (string: "\(websiteArray[0])");
let requestObj = NSURLRequest(URL: url!);
showWebsite.loadRequest(requestObj);
break
case 1:
let url = NSURL (string: "\(websiteArray[1])");
let requestObj = NSURLRequest(URL: url!);
showWebsite.loadRequest(requestObj);
break
case 2:
let url = NSURL (string: "\(websiteArray[2])");
let requestObj = NSURLRequest(URL: url!);
showWebsite.loadRequest(requestObj);
break
default:
break
}
}
// MARK: - Container View Controller
override func shouldAutomaticallyForwardAppearanceMethods() -> Bool {
return true
}
override func shouldAutomaticallyForwardRotationMethods() -> Bool {
return true
}
}
When I tap the buttons it is working like it should. The navigation bar shows the active page and the picture (collectionViewController) that belongs to the active page is shown and also the webpage that belongs to the active page is shown. But I do not want to use the buttons. I want to use the collectionViewController so that when I swipe the image to the next page the new image is shown (this is working already) and the corresponding webpage also (this is not working because of not setting the pageMenu!.currentPageIndex).
How to activate the didTapGoToLeft and Right methods when I swipe the picture?
The CAPSPageMenu provides a delegate method that inform's its delegate that the page has changed. These are the delegate methods you listed in your question.
So, in your ViewController class you need to implement these delegate methods.
First, tell the compiler that your class implements the protocol:
class ViewController:UIViewController,CAPSPageMenuDelegate
Now, you need to set the view controller as the delegate. Where you create the CAPSPageMenu you will need something like this -
pageMenu.delegate=self
Finally, implement the delegate methods. The CAPSPageMenu is provided with an array of view controllers that it manages. You haven't given the precise details of these, but I am assuming that they implement some class which I am calling SubViewController. Define an integer property pageNumber in this class and then your delegate method can simply be -
func willMoveToPage(controller: UIViewController, index: Int) {
let subview=controller as! SubViewController
subview.pageNumber=index;
}
Then, in your SubViewController you can implement a setter on pageNumber that does whatever it needs to when the page number changes. In your delegate method you could also use the index to index into an array of strings (URLs say) and then set a string/URL property on the sub view controller - it depends on what you are trying to do.
Please bear with me. I'm new to programming and new to StackOverflow. I hope that my question will grant me a warm response for entering the programming community. An acquaintance, whose opinion I trust, told me to post this email to him on StackOverflow.
What do I have to do to get two instances of NSObject to work in the same ViewController? I've initialized an NSObject subclass called SideBar and RightSideBar. They both draw from NSObject. The cells in the menu are called created by a TableViewController I created programatically. I followed a tutorial that did everything programmatically because I didn't know that Storyboard is better for building things.
Below is the code base.
EDITED: Sorry for being long winded. I don't know how to make this any shorter and as complete
===========
****Note the SideBar subclass is the left menu. The RightSideBar class has the same initializer setup and is the right menu. I want to be able to make them both appear on the same ViewController in the same instance of the same ViewController if possible.****
This is the left TableViewController:
import UIKit
//this protocol of the sidebar delegate manages the selection of the item in the row.
protocol SidebarTableViewControllerDelegate {
func sidebarControlDidSelectRow(indexPath: NSIndexPath)
}
class SidebarTableViewController: UITableViewController {
//setting up the delegate and array of menu items.
var delegate:SidebarTableViewControllerDelegate?
var tableData:Array <String> = []
var imageData:[UIImage] = []
// MARK: - Table view data source
//Setting up the number of sections in the menu
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
//Setting up the number of items in the menu
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
//Setting up the menu look for the main screen after login.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
//configure the cell...
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkTextColor()
let selectedView:UIView = UIView(frame: CGRect(x: 0, y: 0, width: cell!.frame.size.width, height: cell!.frame.size.height))
selectedView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.3)
cell!.selectedBackgroundView = selectedView
}
cell!.textLabel!.text = tableData[indexPath.row]
cell!.imageView!.image = imageData[indexPath.row]
return cell!
}
//Setting up the height for each cell of the table
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 45.0
}
//Setting up the selection of the item in the cell.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.sidebarControlDidSelectRow(indexPath)
}
override func viewDidLoad() {
}
override func didReceiveMemoryWarning() {
}
}
This is the right table view controller:
//setting up the RightSidebarControllerDelegate
protocol RightSidebarTableViewControllerDelegate {
func rightSidebarControlDidSelectRow(indexPath: NSIndexPath)
}
class RightSidebarTableViewController: UITableViewController {
//setting up the delegate and array of menu items.
var delegate:RightSidebarTableViewControllerDelegate?
var rightTableData:Array <String> = []
var rightImageData:[UIImage] = []
// MARK: - Table view data source
//Setting up the number of sections in the menu
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
//Setting up the number of items in the menu
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rightTableData.count
}
//Setting up the menu look for the main screen after login.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
//configure the cell...
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkTextColor()
let selectedView:UIView = UIView(frame: CGRect(x: 0, y: 0, width: cell!.frame.size.width, height: cell!.frame.size.height))
selectedView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.3)
cell!.selectedBackgroundView = selectedView
}
cell!.textLabel!.text = rightTableData[indexPath.row]
cell!.imageView!.image = rightImageData[indexPath.row]
return cell!
}
//Setting up the height for each cell of the table
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 45.0
}
//Setting up the selection of the item in the cell.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.rightSidebarControlDidSelectRow(indexPath)
}
override func viewDidLoad() {
}
override func didReceiveMemoryWarning() {
}
}
Here is where my problems may start with SideBar:NSObject. This is the left SideBar to be initialized:
import UIKit
#objc protocol SideBarDelegate {
func sideBarDidSelectButtonAtIndex (index: Int)
optional func sideBarWillClose()
optional func sideBarWillOpen()
optional func sideBarWillDeinitialize()
}
//this class sets up the actual sidebar.
class SideBar: NSObject, SidebarTableViewControllerDelegate {
//width of the bar, tableview setup, and views for the sidebar
let barWidth:CGFloat = 175.0
let sideBarTableViewTopInset:CGFloat = 25.0
let sideBarContainerView:UIView = UIView()
let sideBarTableViewController:SidebarTableViewController = SidebarTableViewController()
var originView:UIView!
//var for dynamic effect and controlling the sidebar
var animator:UIDynamicAnimator!
var delegate:SideBarDelegate?
var isSideBarOpen:Bool = false
//initializer for the "SideBar" class.
override init() {
super.init()
}
//initializer for the tableView of menu items.
init(sourceView: UIView, menuItems: Array<String>, menuImages: [UIImage]){
super.init()
//initializing the views and animation for the menu.
originView = sourceView
sideBarTableViewController.tableData = menuItems
sideBarTableViewController.imageData = menuImages
setupSideBar()
animator = UIDynamicAnimator(referenceView: originView)
}
//function for setting up the sidebar.
func setupSideBar () {
//setting up the frame/outline of the side bar.
sideBarContainerView.frame = CGRectMake(-barWidth, originView.frame.origin.y + 45, barWidth, originView.frame.size.height)
//setting up the color of the sidebar.
sideBarContainerView.backgroundColor = UIColor.clearColor()
//disables subviews from being confined to the sidebar.
sideBarContainerView.clipsToBounds = false
//placing the sidebar in the UIView
originView.addSubview(sideBarContainerView)
//adding blur to the menu.
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = sideBarContainerView.bounds
sideBarContainerView.addSubview(blurView)
//setting up controls for the sidebar
sideBarTableViewController.delegate = self
sideBarTableViewController.tableView.frame = sideBarContainerView.bounds
sideBarTableViewController.tableView.clipsToBounds = false
//disabling the scroll feature. Delete to keep the scroll feature.
sideBarTableViewController.tableView.scrollsToTop = false
//This will remove separators in the UITableCell. Delete to keep separators.
sideBarTableViewController.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
//This sets the background color of the sidebar and creates the inset.
sideBarTableViewController.tableView.backgroundColor = UIColor.clearColor()
sideBarTableViewController.tableView.contentInset = UIEdgeInsets(top: sideBarTableViewTopInset, left: 0, bottom: 0, right: 0)
//reloads the sidebar and adds the container view to the sideBarTableViewController.
sideBarTableViewController.tableView.reloadData()
sideBarContainerView.addSubview(sideBarTableViewController.tableView)
}
func showSideBar(shouldOpen: Bool){
animator.removeAllBehaviors()
isSideBarOpen = shouldOpen
//simple if and else statements to define the direction of animation and intensity of animation
let gravityX:CGFloat = (shouldOpen) ? 0.5 : -0.5
let magnitude:CGFloat = (shouldOpen) ? 20 : -20
let boundaryX:CGFloat = (shouldOpen) ? barWidth : -barWidth
//controls the behavior of the animation.
let gravityBehavior: UIGravityBehavior = UIGravityBehavior(items: [sideBarContainerView])
gravityBehavior.gravityDirection = CGVectorMake(gravityX, 0)
animator.addBehavior(gravityBehavior)
let collisionBehavior: UICollisionBehavior = UICollisionBehavior(items: [sideBarContainerView])
collisionBehavior.addBoundaryWithIdentifier("sideBarBoundary", fromPoint: CGPointMake(boundaryX, 20), toPoint: CGPointMake(boundaryX, originView.frame.size.height))
animator.addBehavior(collisionBehavior)
let pushBehavior:UIPushBehavior = UIPushBehavior(items: [sideBarContainerView], mode: UIPushBehaviorMode.Instantaneous)
pushBehavior.magnitude = magnitude
animator.addBehavior(pushBehavior)
let sideBarBehavior:UIDynamicItemBehavior = UIDynamicItemBehavior(items: [sideBarContainerView])
sideBarBehavior.elasticity = 0.3
animator.addBehavior(sideBarBehavior)
}
func sidebarControlDidSelectRow(indexPath: NSIndexPath) {
delegate?.sideBarDidSelectButtonAtIndex(indexPath.row)
}
}
This is the right SideBar:NSObject that will eventually initialize the right menu.
import UIKit
#objc protocol RightSideBarDelegate {
func rightSideBarDidSelectButtonAtIndex (index: Int)
optional func sideBarWillClose()
optional func sideBarWillOpen()
}
class RightSideBar: NSObject, RightSidebarTableViewControllerDelegate {
//width of the bar, tableview setup, and views for the sidebar
let barWidth:CGFloat = 175.0
let rightSideBarTableViewTopInset:CGFloat = 25.0
let rightSideBarContainerView:UIView = UIView()
let rightSideBarTableViewController:RightSidebarTableViewController = RightSidebarTableViewController()
var rightOriginView:UIView!
//var for dynamic effect and controlling the sidebar
var animator:UIDynamicAnimator!
var delegate:RightSideBarDelegate?
var isSideBarOpen:Bool = false
//initializer for the "SideBar" class.
override init() {
super.init()
}
//initializer for the tableView of menu items.
init(rightSourceView: UIView, rightMenuItems: Array<String>, rightMenuImages: [UIImage]){
super.init()
//initializing the views and animation for the menu.
rightOriginView = rightSourceView
rightSideBarTableViewController.rightTableData = rightMenuItems
rightSideBarTableViewController.rightImageData = rightMenuImages
setupSideBar()
animator = UIDynamicAnimator(referenceView: rightOriginView)
}
//function for setting up the sidebar.
func setupSideBar () {
//setting up the frame/outline of the side bar.
rightSideBarContainerView.frame = CGRectMake(rightOriginView.frame.size.width + barWidth , rightOriginView.frame.origin.y + 45, barWidth, rightOriginView.frame.size.height)
//setting up the color of the sidebar.
rightSideBarContainerView.backgroundColor = UIColor.clearColor()
//disables subviews from being confined to the sidebar.
rightSideBarContainerView.clipsToBounds = false
//placing the sidebar in the UIView
rightOriginView.addSubview(rightSideBarContainerView)
//adding blur to the menu.
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = rightSideBarContainerView.bounds
rightSideBarContainerView.addSubview(blurView)
//setting up controls for the sidebar
rightSideBarTableViewController.delegate = self
rightSideBarTableViewController.tableView.frame = rightSideBarContainerView.bounds
rightSideBarTableViewController.tableView.clipsToBounds = false
//disabling the scroll feature. Delete to keep the scroll feature.
rightSideBarTableViewController.tableView.scrollsToTop = false
//This will remove separators in the UITableCell. Delete to keep separators.
rightSideBarTableViewController.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
//This sets the background color of the sidebar and creates the inset.
rightSideBarTableViewController.tableView.backgroundColor = UIColor.clearColor()
rightSideBarTableViewController.tableView.contentInset = UIEdgeInsets(top: rightSideBarTableViewTopInset, left: 0, bottom: 0, right: 0)
//reloads the sidebar and adds the container view to the rightSideBarTableViewController.
rightSideBarTableViewController.tableView.reloadData()
rightSideBarContainerView.addSubview(rightSideBarTableViewController.tableView)
}
func showSideBar(shouldOpen: Bool){
animator.removeAllBehaviors()
isSideBarOpen = shouldOpen
//simple if and else statements to define the direction of animation and intensity of animation
let gravityX:CGFloat = (shouldOpen) ? -0.5 : 0.5
let magnitude:CGFloat = (shouldOpen) ? -20 : 20
let boundaryX:CGFloat = (shouldOpen) ? -barWidth : barWidth
//controls the behavior of the animation.
let gravityBehavior: UIGravityBehavior = UIGravityBehavior(items: [rightSideBarContainerView])
gravityBehavior.gravityDirection = CGVectorMake(gravityX, 0)
animator.addBehavior(gravityBehavior)
let collisionBehavior: UICollisionBehavior = UICollisionBehavior(items: [rightSideBarContainerView])
collisionBehavior.addBoundaryWithIdentifier("sideBarBoundary", fromPoint: CGPointMake(boundaryX, 20), toPoint: CGPointMake(boundaryX, rightOriginView.frame.size.height))
animator.addBehavior(collisionBehavior)
let pushBehavior:UIPushBehavior = UIPushBehavior(items: [rightSideBarContainerView], mode: UIPushBehaviorMode.Instantaneous)
pushBehavior.magnitude = magnitude
animator.addBehavior(pushBehavior)
let sideBarBehavior:UIDynamicItemBehavior = UIDynamicItemBehavior(items: [rightSideBarContainerView])
sideBarBehavior.elasticity = 0.3
animator.addBehavior(sideBarBehavior)
}
func rightSidebarControlDidSelectRow(indexPath: NSIndexPath) {
delegate?.rightSideBarDidSelectButtonAtIndex(indexPath.row)
}
}
Finally this is my current code for the DoubleMenuViewController. Something happens when I segue to the DoubleMenuViewController to break the menus. The menus won't even load. However, if I'm in a SingleMenuViewController that only calls SideBar:NSObject then the code will work so long as I'm only calling one menu. In this DoubleMenuViewController, I have the initialization section commented out for the RightSideBar class because I'm working on a solution. I know this code for this ViewController is garbled. I'm trying everything I can think of. See my remarks after the code to see what I've tried:
import UIKit
class DoubleMenuViewController: UIViewController, SideBarDelegate, RightSideBarDelegate {
var sideBar:SideBar?
var ondemandSideBar:SideBar {
get {
if sideBar == nil {
//setting up the menu items for the sidebar.
sideBar = SideBar(sourceView: self.view, menuItems: ["Home", "Share", "About", "Help"], menuImages: [homeImage!, shareImage!, aboutImage!, helpImage!])
sideBar!.delegate = self
SideBar.new()
}
return sideBar!
}
}
//initializes the "RightSideBar"
var rightSideBar:RightSideBar?
var ondemandRightSideBar:RightSideBar {
get {
if rightSideBar == nil {
rightSideBar = RightSideBar(rightSourceView: self.view, rightMenuItems: [//Other items], rightMenuImages: [//Other Items])
rightSideBar!.delegate = self
RightSideBar.new()
}
return rightSideBar!
}
}
var homeImage = UIImage(named: "Home")
var shareImage = UIImage(named: "Share")
var aboutImage = UIImage(named: "About")
var helpImage = UIImage(named: "Help")
#IBOutlet weak var currentMenuControl: UIBarButtonItem!
#IBAction func currentMenuDisplay(sender: AnyObject) {
if currentMenuControl.tag == 1 {
ondemandSideBar.showSideBar(true)
currentMenuControl.tag = 0
} else {
ondemandSideBar.showSideBar(false)
currentMenuControl.tag = 1
}
}
#IBOutlet weak var progressionMenuControl: UIBarButtonItem!
#IBAction func progressionMenuDisplay(sender: AnyObject) {
if progressionMenuControl.tag == 1 {
ondemandRightSideBar.showSideBar(true)
progressionMenuControl.tag = 0
} else {
ondemandRightSideBar.showSideBar(false)
progressionMenuControl.tag = 1
}
}
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 sideBarDidSelectButtonAtIndex(index: Int) {
switch index {
//segues
}
}
func rightSideBarDidSelectButtonAtIndex(index: Int) {
switch index {
//segues
}
}
}
Here's what I've tried:
I've tried altering the positioning of CGFloats since SubViews seem
to come from the left.
I've renamed all the RightSideBar variables and class names
everything to overcome a runtime confusion in instance variables and
class names. This includes renaming the initializers that you saw in
the NSObject SubClass and the target view controller.
I've tried using control flow in the viewDidLoad method with a
button tag. I took away the swipe features to show the menu and
added buttons because I thought system was struggling to deal with
the Swipes.
I've tried deinitializing in the SideBar subclass file of NSObject.
All that got me was an infinite loop that crashed the application
after login.
Then I tried ondemand initialization in the
targetViewController.....DoubleMenuViewController and
SingleMenuViewController. I returned to a working menu with buttons
in the SingleMenuViewController but it still won't show the left and
right menu in the DoubleMenuViewController.
Last I tried deinitializing the SideBar (left SideBar) and the RightSideBar in the DoubleMenuViewController. However, when I add println() functions to all my sections the debugger doesn't run the print function for me to get values of objects or even show typed states like "This". I added the print functions because I wasn't sure if I would know when deinitialization and reinitialization occurred.
It seems that my menu is initialized from the SideBar: NSObject file and the RightSideBar:NSObject file. What I mean is that my menu is being created before I hit the target view controller. This isn't a problem for me so long as I can get the compiler to initialize the SideBar and the RightSideBar in the same View Controller, but it won't do that.
I just need to be able to control both menus with swipes or button taps.
I think I have a problem with my initializers overriding each other.
However, I don't know how to fix that problem. I've read through the Swift manual and read articles on the internet. I've also searched StackOverflow.
You ask:
How do I Initialize two instances of NSObject in the same view controller?
Setting aside why you're dealing with NSObject at all (in Objective-C all classes have to subclass from NSObject ultimately, in Swift that's no longer the case), if you want to instantiate two objects, you simply have to have one property for each.
If these are lazily instantiated, as your code snippet suggests, then you have to identify where that lazily instantiated property is referenced (e.g. you might trigger it from a "swipe from edge" gesture) or what have you. Set a breakpoint in the code that references that lazily instantiated property and make sure you're getting there at all.
--
I had some observations on one of your code snippets. You say that you're instantiating your side bar like so:
var sideBar : SideBar?
var ondemandSideBar : SideBar {
get {
if sideBar == nil {
sideBar = SideBar(sourceView, menuItems, menuImage etc.)
sideBar!.delegate
SideBar.new()
}
}
}
I don't think that's what you're really doing because you're not setting the delegate, you're both instantiating the SideBar as well as calling new (which you shouldn't be doing from Swift), you're not returning a value, etc.
Also, that pattern of having a stored property that is instantiated by some computed property has a certain Objective-C je ne sais quoi. I'm inferring that you want a lazily instantiated property. If that's the case, I'd be inclined to use a single lazy stored property. And I'd then set that property lazily using a closure:
I'd expect something like this pattern
protocol SideBarDelegate : class { // specify class so we can use `weak` reference
func didChooseMenuItem(sender: SideBar, item: Int)
}
class SideBar {
weak var delegate: SideBarDelegate? // make sure this is weak to avoid strong reference cycle
}
class ViewController: UIViewController, SideBarDelegate {
lazy var sideBar: SideBar = {
let _sideBar = SideBar(sourceView, menuItems, menuImage, etc.)
_sideBar.delegate = self
return _sideBar
}()
func didChooseMenuItem(sender: SideBar, item: Int) {
// handle it here
}
// etc.
}
This way, the sideBar won't be instantiated until you reference sideBar somewhere in your code, but when you do, it will be instantiated with the code inside that closure.