I'm working on an app that has a menu that slides in from the left. The menu built programatically using a UITableView and an NSObject. I've been able to populate the slide out menu with items but I don't know how to create a segue with an Identifier without using Interface Builder.
Here's what I am tying to do in my tableView:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cellForRow = tableView.cellForRowAtIndexPath(indexPath)
let cellLabel = cellForRow?.textLabel?.text
delegate?.sideBarControlDidSelectRow(indexPath, cellName: cellLabel!)
if (cellLabel == "Item1") {
self.performSegueWithIdentifier("item1Settings", sender: nil)
} else {
self.performSegueWithIdentifier("item2Settings", sender: nil)
}
}
( gist: SideBarTableViewController)
When I click a cell in the menu I get the following error:
2015-06-11 18:35:28.720 SideBarApp[2862:1111870] * Terminating app
due to uncaught exception 'NSInvalidArgumentException', reason:
'Receiver () has no
segue with identifier 'item1Settings''
* First throw call stack: (0x1822882d8 0x193aac0e4 0x186fe4c00 0x10000b030 0x10000b108 0x186df1474 0x186eab790 0x186d4c240
0x186cbc6ec 0x1822402a4 0x18223d230 0x18223d610 0x1821692d4
0x18b97f6fc 0x186d2efac 0x1000147cc 0x19412aa08) libc++abi.dylib:
terminating with uncaught exception of type NSException
After searching stack I found a few solutions in Objective-C but nothing for Swift with this scenario.
How do I segue from a programmatically created UITableView?
This is where I assemble the UITableView programmatically:
SideBar.swift View Entire File
func setupSideBar(){
sideBarContainerView.frame = CGRectMake(-barWidth - 1, originView.frame.origin.y, barWidth, originView.frame.size.height)
sideBarContainerView.backgroundColor = UIColor.clearColor()
sideBarContainerView.clipsToBounds = false
originView.addSubview(sideBarContainerView)
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = sideBarContainerView.bounds
sideBarContainerView.addSubview(blurView)
sideBarTableViewController.delegate = self
sideBarTableViewController.tableView.frame = sideBarContainerView.bounds
sideBarTableViewController.tableView.clipsToBounds = false
sideBarTableViewController.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
sideBarTableViewController.tableView.backgroundColor = UIColor.clearColor()
sideBarTableViewController.tableView.scrollsToTop = false
sideBarTableViewController.tableView.contentInset = UIEdgeInsetsMake(sideBarTableViewTopInset, 0, 0, 0)
sideBarTableViewController.tableView.reloadData()
sideBarContainerView.addSubview(sideBarTableViewController.tableView)
}
Here is where I call it:
ViewController.swift (initial view)
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(named: "image2")
sideBar = SideBar(sourceView: self.view, menuItems: ["first item", "second item", "funny item", "another item"])
sideBar.delegate = self
}
What it looks like you're trying to do is open a settings page when you select a cell. To do this, you need to define a new UIViewController to hold the settings page code. Also create a UIStoryboardSegue to connect the current UIViewController and your new SettingsViewController.
Then, using override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) you can get the SettingsViewController just before it is opened, set whatever properties you need to on it, and let it finish launching with this new data at its disposal. Some data you might pass along could contain information telling the SettingsViewController which cell was selected, and what interface it should therefor offer to the user.
If that isn't working out for you, you can instantiate the SettingsViewController, set properties on it, and when you're ready, call currentController.presentViewController(settingsController, animated: blah, completion: blah)
Do you have an segue with the identifier item1Settings? To check select your segue and check in the identifier is set to item1Settings. It seems that swift is not finding this name setup in your storyboard.
I figured it out... I needed to perform the segue from my viewController not my SideBarTableViewController:
class ViewController: UIViewController, SideBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
sideBar = SideBar(sourceView: self.view, menuItems: ["Item1", "item2"])
sideBar.delegate = self
}
func sideBarDidSelectButtonAtIndex(index: Int) {
if (index == 0) {
self.performSegueWithIdentifier("item1", sender: self)
} else {
self.performSegueWithIdentifier("item2", sender: self)
}
}
Related
I am still new to swift and I would ask you for advice. Thank you in advance and sorry for my bad English.
My goal is:
User tap edit button in the table's row. UITextField appears instead cell. After entering value and pressing Return key UITextField disappears again and cell is recalculated.
editButton pressed -> hide priceCell & show UITextField & show keyboard & start editing/entering value (blinking cursor) -> stop editing/entering value execute by pressing Return key -> hide UITextField & shows priceCell & save entered value into array & reload edited row
I use this answer as starting blueprint.
I would like to also use .decimalPad keyboard to easier entering numeric value and limit user to use only numbers (and decimal point), but this exclude use Return key as stop editing, am I right?
I found this possible solution, but it seems to me complex for my problem...
my ViewController:
import UIKit
class PortfolioViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, PortfolioCellDelegate {
let getData = GetData()
...
override func viewDidLoad() {
super.viewDidLoad()
cellTableView.delegate = self
cellTableView.dataSource = self
cellTableView.register(UINib(nibName: "PortfolioCell", bundle: nil), forCellReuseIdentifier: "portfolioCell")
self.currencyControl.selectedSegmentIndex = MyVariables.currencyControlSelected
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let coinCell = tableView.dequeueReusableCell(withIdentifier: "portfolioCell", for: indexPath) as! PortfolioCell
...
coinCell.delegate = self
return coinCell
}
...
func portfolioButtonPressed(coinCell: PortfolioCell) {
let indexPath = self.cellTableView.indexPathForRow(at: coinCell.center)!
let selectedCell = cellTableView.cellForRow(at: indexPath) as! PortfolioCell
selectedCell.priceCell.isHidden = true
selectedCell.textCell.isHidden = false
selectedCell.textCell.delegate = self
func textFieldDidEndEditing(textField: UITextField) {
let owned: Double = Double(textField.text!)!
if owned >= 0 {
MyVariables.dataArray[indexPath.row].ownedCell = owned
} else {
MyVariables.dataArray[indexPath.row].ownedCell = 0.00
}
selectedCell.priceCell.isHidden = false
selectedCell.textCell.isHidden = true
self.cellTableView.reloadData()
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
selectedCell.textCell.resignFirstResponder()
return true
}
}
...
}
my custom cell:
import UIKit
protocol PortfolioCellDelegate {
func portfolioButtonPressed(coinCell: PortfolioCell)
}
class PortfolioCell: UITableViewCell {
var delegate: PortfolioCellDelegate?
...
#IBAction func editCellPressed(_ sender: UIButton) {
delegate?.portfolioButtonPressed(coinCell: self)
}
...
}
For now when button is pressed proper UITextField shows, but don't dismiss after Return key is pressed.
Or should I change it completely and use tap gestures?
To end editing in any kind of scrollView, simply use this
cellTableView.keyboardDismissMode = .onDrag
or
cellTableView.keyboardDismissMode = .interactive
It will hide keyboard when you interact with the tableView
For number keypad you can add toolbar as a textField's inputAccessoryView. On toolbar add cancel button to dismiss keyboard.
There is two way to go:
1.) Delegate
2.) IQKeyboardManager
1.)
Use UITextFieldDelegate
There is one particular callback named "textFieldShouldEndEditing"
In this method, return true.
2.)
User the IQKeyboardManager one liner library. This library manages all the TextFields and scrollviews automatically. You activate it with one line in AppDelegate so it's easy to use.
https://github.com/hackiftekhar/IQKeyboardManager
Working but not as sleek as want it to be and also I was not capable to make IQKeyboardManager works so I did use inputAccessoryView.
custom cell:
import UIKit
protocol PortfolioCellDelegate {
func portfolioButtonPressed(didSelect coinCell: PortfolioCell)
func portfolioButtonPressed(coinCell:PortfolioCell, editingChangedInTextCell newValue:String)
}
class PortfolioCell: UITableViewCell, UITextFieldDelegate {
var delegate: PortfolioCellDelegate?
...
#IBAction func editCellPressed(_ sender: UIButton) {
textCell.becomeFirstResponder()
delegate?.portfolioButtonPressed(didSelect: self)
}
#IBAction func textCellValueChanged(_ sender: UITextField) {
if (sender.text?.isEmpty)! {
delegate?.portfolioButtonPressed(coinCell: self, editingChangedInTextCell: "XXX")
} else {
let text = sender.text
delegate?.portfolioButtonPressed(coinCell: self, editingChangedInTextCell: text!)
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.textCell.delegate = self
let flexiableSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.doneButtonAction))
let toolBar:UIToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: 35))
toolBar.barTintColor = UIColor(red:0.15, green:0.69, blue:0.75, alpha:1.0)
toolBar.tintColor = UIColor(red:0.93, green:0.93, blue:0.93, alpha:1.0)
toolBar.setItems([flexiableSpace, doneButton], animated: false)
textCell.inputAccessoryView = toolBar
textCell.keyboardType = UIKeyboardType.decimalPad
}
#objc func doneButtonAction() {
textCell.endEditing(true)
}
...
}
ViewController:
import UIKit
class PortfolioViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, PortfolioCellDelegate {
let getData = GetData()
...
override func viewDidLoad() {
super.viewDidLoad()
cellTableView.delegate = self
cellTableView.dataSource = self
cellTableView.register(UINib(nibName: "PortfolioCell", bundle: nil), forCellReuseIdentifier: "portfolioCell")
self.currencyControl.selectedSegmentIndex = MyVariables.currencyControlSelected
getData.delegate = self
timeStampLabel.text = MyVariables.timeStamp
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.cellTableView.reloadData()
self.currencyControl.selectedSegmentIndex = MyVariables.currencyControlSelected
self.timeStampLabel.text = MyVariables.timeStamp
}
//MARK: - tableView
/***************************************************************/
...
func portfolioButtonPressed(coinCell: PortfolioCell, editingChangedInTextCell newValue: String) {
let indexPath = self.cellTableView.indexPathForRow(at: coinCell.center)!
let selectedCell = cellTableView.cellForRow(at: indexPath) as! PortfolioCell
selectedCell.priceCell.isHidden = false
selectedCell.textCell.isHidden = true
if newValue != "XXX" {
let owned: Double = Double(newValue)!
MyVariables.dataArray[indexPath.row].ownedCell = owned
}
selectedCell.priceCell.isHidden = false
selectedCell.textCell.isHidden = true
self.cellTableView.reloadRows(at: [indexPath], with: .automatic)
}
func portfolioButtonPressed(didSelect coinCell: PortfolioCell) {
let indexPath = self.cellTableView.indexPathForRow(at: coinCell.center)!
let selectedCell = cellTableView.cellForRow(at: indexPath) as! PortfolioCell
selectedCell.priceCell.isHidden = true
selectedCell.textCell.isHidden = false
}
...
}
It's easy: You should select that table view cell, then enable User Interaction Enabled in the attribute inspector.
I am sending data from one tableview list to another tableview used as a detail view for the selected cell. Simple -
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
let index = tableView.indexPathForSelectedRow
if (segue.identifier == "EventDetailSegue") {
let detailVC = segue.destinationViewController as! EventDetailTableViewController
var event : Event
if searchController.active && searchController.searchBar.text != "" {
event = filteredEvents[index!.row]
} else {
event = events[index!.row]
}
detailVC.eventImage = event.image
detailVC.eventCompany = event.company
detailVC.eventName = event.name
detailVC.eventLocation = event.location
detailVC.eventPrice = event.price
detailVC.eventPromoterImage = event.promoterImage
}
}
Since I now want to have a static button bar on the bottom of the detail tableview. I need to change the EventDetailTableViewController to be in a container view on a UIViewController. So I can then simply do what I want inside the UIViewController.
My question is how do I still send this data to the now embedded EventDetailTableViewController inside the container view? Since the segue will now need to be going to the UIViewController with the container view, but I also need to make sure this data is passed to that TableView inside the container.
i use color instead of data :
EventDetailTableViewController ->ViewController3
UIViewController -> ViewController2
try like this :
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let tableVc = ViewController3()
tableVc.color = UIColor.redColor()
let vc = ViewController2()
vc.tableViewController = tableVc
self.navigationController?.pushViewController(vc, animated: true)
}
}
class ViewController2: UIViewController {
var tableViewController:ViewController3!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.yellowColor()
tableViewController.willMoveToParentViewController(self)
addChildViewController(tableViewController)
tableViewController.view.frame = CGRect(x: 0, y: 100, width: 100, height: 100)
view.addSubview(tableViewController.view)
tableViewController.didMoveToParentViewController(self)
}
}
class ViewController3: UITableViewController {
var color :UIColor!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = color
}
}
hope it be helpful :-)
I have a Table View with basic label of Table View Cell. I labelled the cell as "January", "February", "March", etc. When user tap on "January", an image with file name "jan.jpeg" will be showed using the following Swift code:
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImage(named: "jan.jpeg")!
imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPointMake(0.0, 0.0), size:image.size)
}
My question is, is it possible when user tap on "February", "feb.jpeg" will be showed, whereas "mar.jpeg will be showed if user tap on "Mar"? How to implement this?
When a cell is selected, the tableView:didSelectRowAtIndexPath: delegate method is called. If you have a predefined list of cells, you can test which cell is being tapped and load the correct image. For example:
have a variable var imgName: String outside of all functions in the viewcontroller with your tableView.
Also put this function in the viewcontroller with your tableView:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
imgName = "jan.jpeg"
}
else if indexPath.row == 1 {
imgName = "feb.jpeg"
}
else if indexPath.row == 2 {
imgName = "mar.jpeg"
}
else {
// Handle else
}
self.performSegueWithIdentifier("showImageSegue", sender: self)
}
You will need to go to interface builder and name the segue between your viewcontrollers to "showImageSegue".
Additionally, implement the prepareForSegue: function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let destinationVC = segue.destinationViewController as ImageViewController // Replace ImageViewController with whatever the name of your destination viewcontroller is.
destinationVC.imageName = imgName
}
Finally, inside the ImageViewController class, add this:
var imageName: String
override func viewDidLoad() {
let image = UIImage(named: imageName)
imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPointMake(0,0, 0,0), size: image.size)
}
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.
I'm asking this question because the answer I received in this question: How Do I Initialize Two Instances of NSObject in the same ViewController - Swift
brought me in this direction. Whether I subclass as NSObject or UIViewController, I still receive the unrecognized selector when I change my code to what's below.
I'm still trying to be able to create a left and right SideBar. However, I cannot even get one SideBar to load now. I receive an error [UIViewController Center]: unrecognized selector sent to instance xxxxx.
This question is different from the other unrecognized selector instance questions I've seen because I'm not dealing with a button and have no outlets since everything is done programmatically. Hence, I can't specify a subclass that links to a UIViewController in the storyboard.
I feel like once I solve the selector issue the code will work. As the code is right now, the application compiles fine. The problem is the runtime error that I receive. I can provide information from the debugger if it's necessary.
Fwiw, it seems like the runtime is recognizing that I have a center, left, and right viewcontroller from the error message.
I haven't included the RightSideBar code because I think if the left SideBar runs the RightSideBar will run when I add the solution. I want to keep the code you have to read through as brief as possible.
Last thing to note, I'm reaching all of my print statements. I actually print through to the point where it says "I should be showing the sideBar".
Here is the code for the SideBar:
//optional delegate methods that select when the sidebar opens and closes.
#objc protocol SideBarDelegate : class {
func sideBarDidSelectButtonAtIndex (itemIndex: Int)
optional func sideBarWillClose()
optional func sideBarWillOpen()
}
//this class sets up the actual sidebar.
class SideBar: UIViewController, SidebarTableViewControllerDelegate {
//width of the bar, tableview setup, and views for the sidebar
let barWidth:CGFloat = 175.0
let sideBarTableViewTopInset:CGFloat = 25.0
let sideBarContainerView:UIViewController = UIViewController()
let sideBarTableViewController:SidebarTableViewController = SidebarTableViewController()
var originView:UIViewController?
//var for dynamic effect and controlling the sidebar
var animator:UIDynamicAnimator!
weak var delegate:SideBarDelegate?
var isSideBarOpen:Bool = false
//initializer for the "SideBar" class.
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(nibName NibNameOrNil:String!, bundle nibBundleOrNil:NSBundle!) {
super.init(nibName: nil, bundle: nil)
}
convenience init(){
self.init(nibName: nil, bundle: nil)
}
//initializer for the tableView of menu items.
init(sourceView: UIViewController, menuItems: Array<String>, menuImages: [UIImage]){
self.originView = sourceView
self.sideBarTableViewController.tableData = menuItems
self.sideBarTableViewController.imageData = menuImages
println("set initialization values")
super.init(nibName: nil, bundle: nil)
//initializing the views and animation for the menu.
setupSideBar()
animator = UIDynamicAnimator(referenceView: originView!.view)
println("finished initialization")
//swipe gesture recognition for opening the menu.
let showGestureRecognizer:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "handleSwipe:")
showGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right
originView!.view.addGestureRecognizer(showGestureRecognizer)
let hideGestureRecognizer:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "handleSwipe:")
hideGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Left
originView!.view.addGestureRecognizer(hideGestureRecognizer)
}
override func viewDidLoad() {
println("view loaded")
}
//this function handles the direction of swipes
func handleSwipe(recognizer: UISwipeGestureRecognizer){
if recognizer.direction == UISwipeGestureRecognizerDirection.Left {
showSideBar(false)
delegate?.sideBarWillClose?()
println("closed the sideBar")
} else {
println("opened the sideBar")
showSideBar(true)
delegate?.sideBarWillOpen?()
}
}
//function for setting up the sidebar.
func setupSideBar () {
println("setup sideBar")
//setting up the frame/outline of the side bar.
sideBarContainerView.view.frame = CGRectMake(-barWidth - 1, originView!.view.frame.origin.y, barWidth, originView!.view.frame.size.height)
//setting up the color of the sidebar.
sideBarContainerView.view.backgroundColor = UIColor.clearColor()
//disables subviews from being confined to the sidebar.
sideBarContainerView.view.clipsToBounds = false
//placing the sidebar in the UIView
originView!.view.addSubview(sideBarContainerView.view)
//adding blur to the menu.
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = sideBarContainerView.view.bounds
sideBarContainerView.view.addSubview(blurView)
//setting up controls for the sidebar
sideBarTableViewController.delegate = self
sideBarTableViewController.tableView.frame = sideBarContainerView.view.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.view.addSubview(sideBarTableViewController.tableView)
originView?.addChildViewController(sideBarContainerView)
sideBarContainerView.didMoveToParentViewController(originView)
}
func showSideBar(shouldOpen: Bool){
animator.removeAllBehaviors()
isSideBarOpen = shouldOpen
println("I should be showing the sideBar")
//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 - 1
//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!.view.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)
}
}
Here is the Home ViewController:
class Home: UIViewController, SideBarDelegate {
//*** Must put logout code into the logout button it should log the user out if they press it ***
var sideBar:SideBar = SideBar()
var homeImage = UIImage(named: "Shine Home")
var profileImage = UIImage(named: "Shine Profile")
var shareImage = UIImage(named: "Shine Share")
var aboutImage = UIImage(named: "Shine About")
var helpImage = UIImage(named: "Shine Help")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//setting up the menu items for the sidebar.
sideBar = SideBar(sourceView: self, menuItems: ["Home", "Profile", "Share", "About", "Help"], menuImages: [homeImage!, profileImage!, shareImage!, aboutImage!, helpImage!])
sideBar.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func sideBarDidSelectButtonAtIndex(itemIndex: Int) {
switch itemIndex {
case 0:
let vc = storyboard?.instantiateViewControllerWithIdentifier("Home") as! Home
self.navigationController?.pushViewController(vc, animated: true)
case 1:
performSegueWithIdentifier("profile", sender: self)
case 2:
performSegueWithIdentifier("share", sender: self)
case 3:
performSegueWithIdentifier("about", sender: self)
case 4:
performSegueWithIdentifier("help", sender: self)
default:
break
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
This situation of unrecognized selector stems from the runtime not knowing what specific object you want to activate.
The runtime sees sideBarContainerView object, which is acceptable for the compiler.
The problem is that your animation behavior is for views. Your UIViewController Object has not descended into UIViews. You need to add .view to all sideBarContainerView in your showSideBar function.
If you breakdown your function and just use isSideBarOpen as the bool for your if statements in the constants and place it in the initialization of your menu then you'll get the Selector error immediately.
That's just a second way to point to fact that the error is past your last print statement "I should be showing SideBar.
If you do that then your menu will load. So far, I have no solution for getting both menus to initialize as a left and right menu.