UISlider not updating current value in Swift - ios

I have a slider inside a table view cell and I'm trying to set the text of a label to the value of the slider. However, the text isn't setting to the changed slider values. What am I doing wrong?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
if indexPath.section == 0 {
let slider = UISlider(frame: CGRectMake(0, 0, 320, 44))
slider.userInteractionEnabled = true
slider.minimumValue = 1
slider.maximumValue = 100
slider.value = 50
slider.continuous = true
cell.addSubview(slider)
println(cell.bounds)
let label = UILabel(frame: CGRectMake(260, -20, 100, 20))
var theValue = Int(slider.value)
label.text = "\(theValue)" + "mi."
cell.addSubview(label)
}

You can do it as you have using some tricks, first of all you need to set the target for the UISlider as some as suggested, the other point is get the selected row, which you can achieve saving in the tag property the indexPath.row like in the following code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
let slider = UISlider(frame: CGRectMake(0, 0, 100, 44))
slider.userInteractionEnabled = true
slider.minimumValue = 1
slider.maximumValue = 100
slider.value = 50
slider.continuous = true
// set the target to respond to changes.
slider.addTarget(self, action: "sliderValueChanged:", forControlEvents: UIControlEvents.ValueChanged)
// save the indexPath.row
slider.tag = indexPath.row
cell.addSubview(slider)
let label = UILabel(frame: CGRectMake(105, 0, 100, 20))
var theValue = Int(slider.value)
label.text = "\(theValue)" + "mi."
cell.addSubview(label)
return cell
}
The another problem is set the UILabel text , which you can achieve by getting the exactly view inside the cell, like in the following code:
func sliderValueChanged(sender: AnyObject) {
var slider = sender as! UISlider
let cell = self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: slider.tag, inSection: 0)) as UITableViewCell!
// If you know the exactly index
// var label = cell.subviews[4] as! UILabel
// label.text = "\(slider.value)"
// assuming you have only one `UILabel` inside your cell
for view in cell.subviews {
if let label = view as? UILabel {
label.text = "\(slider.value)"
}
}
}
In the above code I only set the exactly number inside the UITableViewCell for purpose of show the way, but the strongly recommended way is iterate through all the UIView and find your UILabel like in the method proposed in this question Find UILabel in UIView in Swift because the number can change.
As some people said before, is more easy set a Custom Cell all through Interface Builder, but it's up to you.
I hope this help you.

Generally you should add an action to your slider :
slider.addTarget(self, action: "sliderValueDidChange:", forControlEvents: .ValueChanged)
func sliderValueDidChange(sender:UISlider!)
{
println("value--\(sender.value)")
}
as described here : http://www.apptuitions.com/programmatically-creating-uiview-uislider-uiswitch-using-swift/
** As you need to change label and use slider's value in any cell, separately, You have to use custom TableViewCell class and define your slider, label and action in it, easily.
** MY RECOMMENDATION: Use storyBoard and then easily design your view and use customized TableViewCell class.
a sample table view cell class for you ( similar to one in my own project ) :
class CustomCell: UITableViewCell {
#IBOutlet weak var slider: UISlider!
#IBOutlet weak var label: UILabel!
#IBAction func sliderChanged(sender: AnyObject) {
// set label text to slider.value here
}
}

You are missing action on slider drag.
slider.addTarget(self, action: "sliderValueDidChange", forControlEvents: .ValueChanged)
func sliderValueDidChange(sender:UISlider!){
println("value--\(sender.value)")
label.text = String(sender.value)
}
You can use slider.superview or store index in tag of the slider in order to get the reference to cell to which slider belongs and access label.
Better solution will be to create custom class for the cell.

Add this in your cellForRowAtIndexPath function:
slider.addTarget(self, action: "sliderValueChanged:", forControlEvents: .ValueChanged)
label.tag = 1
You can use this to update the label:
func sliderValueChanged(sender: AnyObject)
{
var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
if let indexPath = self.tableView.indexPathForRowAtPoint(position) {
//get your cell here
let cell = tableView.cellForRowAtIndexPath(indexPath)
//update label
for view in cell.subviews {
if let label = view as? UILabel {
if label.tag == 1 {
label.text! = "\((sender as! UISlider).value)"
}
}
}
}
}
OR
You can do what others have mentioned and create a Custom UITableViewCell, add a slider and a label, and create IBOutlets for them.

Related

How to detect long press on a UICollectionViewCell and and perform an action a the cell

I have a list of images displaying in a UICollectionViewCell. Now I want a way to display an overlay on the image when user long press on the cell. I have been able to place a long press gesture on the cell but unfortunately how to perform the overlay on the cell is where I'm struggling to achieve.
In my cellForItemAt I have this
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reUseMyCellID, for: indexPath) as! MyCollectionCell
cell.gestureRecognizers?.removeAll()
cell.tag = indexPath.row
let directFullPreviewer = UILongPressGestureRecognizer(target: MyCollectionCell(), action: #selector(MyCollectionCell().directFullPreviewLongPressAction))
cell.addGestureRecognizer(directFullPreviewer)
I have this function for the action on LongPressGestureRecognizer in my MyCollectionCell
class MyCollectionCell: UICollectionViewCell {
weak var textLabel: UILabel!
let movieImage: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.clipsToBounds = true
image.contentMode = .scaleAspectFill
image.layer.cornerRadius = 10
// image.image = UIImage(named: "105")
return image
}()
let movieOverlay: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .black.withAlphaComponent(0.7)
view.alpha = 0
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(movieImage)
movieImage.addSubview(btnRate)
movieImage.addSubview(movieOverlay)
NSLayoutConstraint.activate([
movieImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
movieImage.topAnchor.constraint(equalTo: contentView.topAnchor),
movieImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
movieImage.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
movieOverlay.leadingAnchor.constraint(equalTo: movieImage.leadingAnchor),
movieOverlay.topAnchor.constraint(equalTo: movieImage.topAnchor),
movieOverlay.trailingAnchor.constraint(equalTo: movieImage.trailingAnchor),
movieOverlay.bottomAnchor.constraint(equalTo: movieImage.bottomAnchor)
])
}
override func prepareForReuse() {
super.prepareForReuse()
movieImage.image = nil
}
func configure(with urlString: String){
movieImage.sd_setImage(with: URL(string: urlString), placeholderImage: UIImage(named: "ImagePlaceholder"))
}
#objc func directFullPreviewLongPressAction(g: UILongPressGestureRecognizer)
{
if g.state == UIGestureRecognizer.State.began
{
movieOverlay.alpha = 1
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Your current code has many issues when creating the long press gesture.
You are setting the target to a new cell instance that is immediately thrown away. Set the target as cell, not MyCollectionCell().
You are also using the wrong syntax for the selector. Don't attempt to create a new instance of a cell. Just pass the name of the method #selector(MyCollectionCell.directFullPreviewLongPressAction).
Having said all of that, there is no reason this code should be in the cellForItemAt method. You should be creating the long press gesture inside the cell class.
Remove these three lines from cellForItemAt:
cell.gestureRecognizers?.removeAll()
let directFullPreviewer = UILongPressGestureRecognizer(target: MyCollectionCell(), action: #selector(MyCollectionCell().directFullPreviewLongPressAction))
cell.addGestureRecognizer(directFullPreviewer)
Then add the following lines inside the init of MyCollectionCell:
let directFullPreviewer = UILongPressGestureRecognizer(target: self, action: #selector(directFullPreviewLongPressAction))
addGestureRecognizer(directFullPreviewer)
Now the cell is fully responsible for setting up and handling the long press gesture.
Unrelated to your question, you should know that the line:
cell.tag = indexPath.row
should be:
cell.tag = indexPath.item
row is used for UITableView. item is used for UICollectionView.
But besides that, you really should avoid such code. If your collection view allows cells to be inserted, deleted, and/or reordered, then a cell's tag will no longer represent the item you set.

Patterns for Showing a Toast Popup with Custom UITableViewCell

Currently, I have a CustomTableViewCell that is used in four or 5 different places. The custom cell has a lazy loaded UILongPressGestureRecognizer property that gets added as a gesture recognizer in cellForRowAtIndexPath in the parent VC.
self.tableView.addGestureRecognizer(cell.longPress)
When a user initiates the long press, I want a toast notification to popup displaying some contextual information, and then to disappear after a few seconds. I've included this in my code for the CustomTableViewCell, but all of these decisions are starting to "smell." Is there a smarter, more logical way to be implementing these decisions?
This table view cell has the following code:
weak var parentTableView: UITableView?
lazy var longPress: UILongPressGestureRecognizer = {
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressSelector))
longPress.minimumPressDuration = 0.5
longPress.delegate = self
return longPress
}()
func longPressSelector(_ longPress: UILongPressGestureRecognizer!) {
if let tableView = self.parentTableView {
let point = longPress.location(in: tableView)
let indexPath = tableView.indexPathForRow(at: point)
if ((indexPath! as NSIndexPath).section == 0 && longPress.state == .began) {
// do some work
// Show informational popup
let toast = createToastNotification(withTitle: addedSong.name)
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) -> Void in
UIView.animate(withDuration: 1.0) { () -> Void in
toast.alpha = 0.0
toast = nil
}
}
}
}
}
func createToastNotification(withTitle title: String) -> UIView {
if let tableView = self.parentTableView {
let windowFrame = tableView.superview?.bounds
let height:CGFloat = 145, width: CGFloat = 145
let x = (windowFrame?.width)! / 2 - width / 2
let y = (windowFrame?.height)! / 2 - height / 2
let toast = EnsembleToastView.create()
toast.frame = CGRect(x: x, y: y, width: width, height: height)
toast.songLabel.text = title
toast.layer.cornerRadius = 5
tableView.superview?.addSubview(toast)
return toast
}
return UIView()
}
I think it makes more sense for the TableView to know how to display a toast so I would create a protocol in your tableViewCell, so I would take the following steps.
Make the TableViewController responsible for:
creating toast (only once)
configuring toast
showing toast
responding to long press gesture
configuring your table view cell
Allow YourTableViewCell to only delegate
So let's do responding to long press gesture first
protocol TableViewCellLongPressDelegate {
func tableViewCellHadLongPress(_ cell: YourTableViewCell)
}
Then extend your TableViewController to conform to your new protocol
extension YourTableViewController : TableViewCellLongPressDelegate {
func tableViewCellHadLongPress(_ cell: YourTableViewCell){
//configure toast based on which cell long pressed
configureToastNotification(with title: cell.title){
//show toast
}
}
Now, configuring your table view cell within your TableViewController configure your cell and assign the TableViewController as the longPressDelegate
let cell = YourTableViewCell.dequeue(from: self.tableView)!
//configure cell
cell.tableViewCellLongPressDelegate = self
This approach is nice because you can move the createToastNotification() method to your TableViewController and be responsible for creating toast (only once)
var toastNotification : UIView?
viewDidLoad(){
//yatta yatta
toastNotification = createToastNotification()
}
Then you can change createToastNotification to
func createToastNotification() -> UIView? {
let windowFrame = self.bounds
let height:CGFloat = 145, width: CGFloat = 145
let x = (windowFrame?.width)! / 2 - width / 2
let y = (windowFrame?.height)! / 2 - height / 2
let toast = EnsembleToastView.create()
toast.frame = CGRect(x: x, y: y, width: width, height: height)
toast.layer.cornerRadius = 5
self.addSubview(toast)
return toast
}
Lastly for YourTableViewController, configuring toast, let's create a configureToastNotification(with title: String) like:
func configureToastNotification(with title: String){
if let toast = self.toastNotification {
toast.songLabel.text = title
}
}
For the end, we remove a lot of the responsibility from YourTableViewCell and allow it to only delegate :
protocol TableViewCellLongPressDelegate : class {
func tableViewCellHadLongPress(_ cell: YourTableViewCell)
}
class YourTableViewCell : UITableViewCell {
//initializers
weak var longPressDelegate: TableViewCellLongPressDelegate?
lazy var longPress: UILongPressGestureRecognizer = {
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressHappened))
longPress.minimumPressDuration = 0.5
longPress.delegate = self
return longPress
}()
func longPressHappened() {
self.longPressDelegate?.tableViewCellHadLongPress(self)
}
}

How to toggle a Button Inside a table View

I have a custom cell in my table view which contain a button and name of audio file. when i am tapping in button the relevant audio is playing and when tapping again it audio stops. My problem is when i am tapping the same button it is ok but when i am tapping in any button different then current cell then my process is not working. Can any one show me the right direction to do this . My code is below . Thanks in advance..
var isAudioPlaying = false
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return designCellAtIndexPath(indexPath)
}
private func designCellAtIndexPath(indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = NSBundle.mainBundle().loadNibNamed("ExtendedDesign", owner: self, options: nil)[4] as! UITableViewCell
cell.frame = CGRectMake(0.0, 0.0, UIScreen.mainScreen().bounds.size.width, cell.bounds.height)
cell.contentView.frame = cell.bounds
cell.selectionStyle = UITableViewCellSelectionStyle.None
btnPlayPause = cell.viewWithTag(1) as! subclassedUIButton
btnPlayPause.addTarget(self, action: "btnPlayPauseTapped:", forControlEvents: .TouchUpInside)
btnPlayPause.urlString = arrAttractionList[indexPath.row].audio.url
let lblAttractionName = cell.viewWithTag(2) as! UILabel
lblAttractionName.text = arrAttractionList![indexPath.row].name
return cell
}
My Button Methode is:
func btnPlayPauseTapped(sender: subclassedUIButton) {
if !isAudioPlaying {
isAudioPlaying = true
sender.setImage(UIImage(named: "Button_Pause"), forState: .Normal)
AudioHandler.sharedManager().playAudio(sender.urlString)
} else {
isAudioPlaying = false
sender.setImage(UIImage(named: "Button_Play_Menu.png"), forState: .Normal)
AudioHandler.sharedManager().audioPlayer?.pause()
}
}
// Custome class for UIButton
class subclassedUIButton: UIButton {
var indexPath: Int?
var urlString: String?
}
You have to manage other flag for your current cell's audio you have to play. Set current cell index and manage that.
let cell: UITableViewCell = NSBundle.mainBundle().loadNibNamed("ExtendedDesign", owner: self, options: nil)[4] as! UITableViewCell
This is not the correct way to add a cell to a table view. You should call registerNib(_:forCellReuseIdentifier:) (usually in the viewDidLoad() method) and then dequeueReusableCellWithIdentifier(_:forIndexPath:) in your cellForRowAtIndexPath(_:) delegate method.
The registerNib(_:forCellReuseIdentifier:) isn't needed if you design your cell with a template in the storyboard.
But you really need to use table views the way they were designed to be used. What you have now might seem to work, but it's going to have problems and make your life miserable if you don't use table views the way they were intended to be used.
Also, you don't want to be referencing your subviews with viewWithTag(_:). You want to wire up proper IBOutlets in Interface Builder. Or at least creating properties in your UITableView subclass to access the subviews.
Finally I did it here is my code to do this. First Declare Two variables in class:
var currentCellIndex :int = -1
var previousCellIndex : int = -1
func btnPlayPauseTapped(sender: subclassedUIButton) {
currentCellIndex = sender.indexPath!
if currentCellIndex == previousCellIndex {
if !isAudioPlaying {
isAudioPlaying = true
AudioHandler.sharedManager().stopAudioPlayer()
sender.setImage(UIImage(named: "Button_Play_Menu.png"), forState: .Normal)
} else {
isAudioPlaying = false
AudioHandler.sharedManager().playAudio(sender.urlString)
sender.setImage(UIImage(named: "Button_Pause"), forState: .Normal)
}
} else {
let indexPath = NSIndexPath(forRow: previousCellIndex, inSection: 0)
tblAttractinList.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
isAudioPlaying = false
sender.setImage(UIImage(named: "Button_Pause"), forState: .Normal)
AudioHandler.sharedManager().playAudio(sender.urlString)
previousCellIndex = sender.indexPath!
}
}
Thank You all of you for your help! :)

UIImageView and ScrollView weird behaviour

I'm trying to create a imageView Carousel with a PageControl in the titleView. Everything seem to work now, beside the alignment of the images. What am i doing wrong here? Look at the bottom image where it does not seem to fit it all?
Cell
class ImageViewCell: UITableViewCell, UIScrollViewDelegate {
#IBOutlet var imageScroll:UIScrollView?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
//scrollView
imageScroll!.backgroundColor = UIColor.blackColor()
imageScroll!.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
imageScroll!.showsHorizontalScrollIndicator = false
imageScroll!.showsVerticalScrollIndicator = false
imageScroll!.pagingEnabled = true
imageScroll!.contentMode = UIViewContentMode.Center
imageScroll!.bounces = false
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
CellForRowAtIndexPath in viewController
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ImageCell", forIndexPath: indexPath) as! ImageViewCell
cell.imageScroll!.contentSize = CGSizeMake(self.view.frame.width * CGFloat(pageImages.count), 200)
cell.imageScroll!.delegate = self
for (index, element) in pageImages.enumerate() {
let newImage = UIImageView(frame: CGRectMake(CGFloat(index) * self.view.frame.width + 4, 0, self.view.frame.width, 200))
newImage.image = element
cell.imageScroll!.addSubview(newImage)
}
return cell
}
You have to set the clipsToBounds property of the UIImageView to true, this property determines whether subviews are confined to the bounds of the view. You need to set too the contentMode to specify how a view adjusts its content when its size changes to any you want, something like this:
newImage.contentMode = .ScaleAspectFill
newImage.clipsToBounds = true
I hope this help you.

How Do I Initialize Two Instances of NSObject in the same ViewController - Swift

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.

Resources