UITableViewCell custom properties (in Swift) - objective-c

I'd like to be able to programatically attribute custom properties to a UITableViewCell (or any other object for that matter)
I'm using a delegate method in Swift w/ Realm.io DB (but I'm guessing it should be something similar in objective-c). Is using commented out line below == correct way of doing this?
override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as Cell
let object = array[UInt(indexPath!.row)] as Language
cell.textLabel.text = object.title
cell.position = object.position // does not produce any warnings
return cell
}
Attributing title works just fine (because it is a label), but how do I attribute other (non-declared) properties to a cell so I can recover them later, when traversing the tableView?
for var row = 0; row < tableView.numberOfRowsInSection(0); row++ {
var cellPath = NSIndexPath(forRow: row, inSection: 0)
var cell:Cell = tableView.cellForRowAtIndexPath(cellPath) as Cell
println(cell) // This prints out my cells, which contains a .text property, but no custom properties
}
Println produces the following result, but position property is not in it:
<_TtC8Verbum_24Cell: 0x7f9fb58bd3d0; baseClass = UITableViewCell; frame = (0 0; 320 44); text = 'Italiano'; autoresize = W; layer = <CALayer: 0x7f9fb585bb70>>
<_TtC8Verbum_24Cell: 0x7f9fb58bb670; baseClass = UITableViewCell; frame = (0 44; 320 44); text = 'Francais'; autoresize = W; layer = <CALayer: 0x7f9fb58a44f0>>
UPDATE: I'm using a custom class for table cells:
class Cell: UITableViewCell {
var position:Int?
init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier)
}
}

print uses the CustomStringConvertible protocol's description method to print out your object. Since you haven't provided a custom description, you're just seeing the regular UITableViewCell's description. You could simply print(cell.position) or:
var description: String { return "Cell with position \(position)" }

Related

Change insets for each section in a UICollectionView

I have a simple UICollectionView written in Swift. My sectionInsets used to be 0, 35, 0, 35 which gave the entire collectionView margins left end right. My minimumLineSpacing is set to default 10, which means all of my cells have a margin of 10 between each other. All good.
Then I divided the UICollectionView into two sections. The first section serves to hold a cell that acts as a prompt to create a new cell (which will end up in section 2). The second section holds all other cells (the actual content).
My problem is that ever since I added the second section, the spacing between the one static cell in section 1 and the first cell in section 2 isn't 10 anymore but 35+10. I'm now trying to get this particular spacing back to 10.
My thought was to add an if condition to identify whether it's section 1 or 2 and set the sectionInsets accordingly. I'm stuck however as to where and how exactly I should do this. I don't seem to be able to call sectionInsets or FlowLayout from my View Controller at e.g. cellForItemAtIndexPath. Should I do it in my CustomCollectionViewFlowLayout subclass? Here is the code that I have in there.
- (void)awakeFromNib
{
self.itemSize = CGSizeMake(305.0, 407.0);
self.minimumInteritemSpacing = 20.0;
self.minimumLineSpacing = 10.0;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.sectionInset = UIEdgeInsetsMake(0, 35, 0, 35);
}
And this is the code for my sections in my view controller:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let firstCell = collectionView.dequeueReusableCellWithReuseIdentifier("createCell", forIndexPath: indexPath) as! CreateCollectionViewCell
firstCell.imageView.image = UIImage(named: "puppy3")
return firstCell
} else {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("mainCell", forIndexPath: indexPath) as! MainCollectionViewCell
cell.imageView?.image = self.imageArray[indexPath.row]
return cell
}
}
UPDATE:
I figured it out. It works for me by adding two variables for sectionInsets of type UIEdgeInsets in my view controller and then adding this function:
//Set custom insets per section
func collectionView(collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
if section == 0 {
return sectionInsets1
} else {
return sectionInsets2
}
}

NSTableView with NSCheckBox cells - how to intercept row selection?

I have a NSTableView with cells that contain NSCheckBox. I'd like to change the behaviour of it so that any row in the table can be clicked and the checkbox in that row toggles on/off accordingly.
I figured it can be done with
func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
}
This method provides the index of the clicked row and I could use this to toggle the checkboxes from there on programmatically. But the problem is that the NSCheckBox intercepts mouse clicks on its row. Is there any way to disable this so that the rows can be clicked fully? Setting the enabled state of the NSCheckBox to false would allow this but it also greys out the checkbox and its title.
EDIT:
To clarify what I need: The (view-based) table cell view contains a checkbox. If a user clicks the checkbox, it toggles but when the row is clicked anywhere where no checkbox is, nothing happens. I want the row to be clicked and the checkbox toggles accordingly. So essentially I want the checkbox to be non-clickable and the table row to notify the checkbox inside it when the row is clicked.
In a cell-based table view, you can implement -selectionShouldChangeInTableView:. I assume this will also work for a view-based table view.
- (BOOL)selectionShouldChangeInTableView:(NSTableView *)tableView
{
NSInteger clickedColumn = tableView.clickedColumn;
NSInteger attributeEnabledColumn = [tableView columnWithIdentifier:#"attributeEnabled"];
if (clickedColumn == attributeEnabledColumn) {
NSInteger clickedRow = tableView.clickedRow;
if (clickedRow >= 0) {
NSArrayController *attributesArrayController = self.attributesArrayController;
NSMutableDictionary *attributeRow = [attributesArrayController.arrangedObjects objectAtIndex:clickedRow];
BOOL attributeEnabled = [attributeRow[kAttributeSelectionAttributeEnabledKey] boolValue];
attributeRow[kAttributeSelectionAttributeEnabledKey] = #(!attributeEnabled);
}
return NO;
}
return YES;
}
Apple provide you with the opportunity to intercept a number of NSEvent types via the following NSEvent class method:
Any time an event whose type property matches the mask you passed in to the first argument of the above method, the block (second argument) executes. This block gives you the opportunity to do a number of things: you can carry out additional processing then let the event carry on as usual, you can modify the event, or you cancel the event altogether. Crucially anything you put in this block happens before any other event processing.
In the snippet below any time a checkbox is clicked, the incoming event is doctored to make the event behave as it the click took place outside of the checkbox, but inside the checkbox's NSTableCellView superview.
func applicationDidFinishLaunching(aNotification: NSNotification) {
NSEvent.addLocalMonitorForEventsMatchingMask(.LeftMouseDownMask,
handler: { (theEvent) -> NSEvent! in
var retval: NSEvent? = theEvent
let winPoint = theEvent.locationInWindow
// Did the mouse-down event take place on a row in the table?
if let row = self.tableView.rowContainingWindowPoint(winPoint) {
// Get the view on which the mouse down event took place
let view = self.tableView.viewAtColumn(0,
row: row,
makeIfNecessary: true) as! NSTableCellView
// In my demo-app the checkbox is the NSTableCellView's last subview
var cb = view.subviews.last! as! NSButton
let cbBoundsInWindowCoords = cb.convertRect(cb.bounds, toView: nil)
// Did the click occur on the checkbox part of the view?
if CGRectContainsPoint(cbBoundsInWindowCoords, theEvent.locationInWindow) {
// Create a modified event, where the <location> property has been
// altered so that it looks like the click took place in the
// NSTableCellView itself.
let newLocation = view.convertPoint(view.bounds.origin, toView: nil)
retval = theEvent.cloneEventButUseAdjustedWindowLocation(newLocation)
}
}
return retval
})
}
func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
if let view = self.tableView.viewAtColumn(0,
row: row,
makeIfNecessary: true) as? NSTableCellView {
var checkbox = view.subviews.last! as! NSButton
checkbox.state = (checkbox.state == NSOnState) ? NSOffState : NSOnState
}
return true
}
////////////////////////////////////////////////////////////////
extension NSTableView {
// Get the row number (if any) that coincides with a specific
// point - where the point is in window coordinates.
func rowContainingWindowPoint(windowPoint: CGPoint) -> Int? {
var rowNum: Int?
var tableRectInWindowCoords = convertRect(bounds, toView: nil)
if CGRectContainsPoint(tableRectInWindowCoords, windowPoint) {
let tabPt = convertPoint(windowPoint, fromView: nil)
let indexOfClickedRow = rowAtPoint(tabPt)
if indexOfClickedRow > -1 && indexOfClickedRow < numberOfRows {
rowNum = indexOfClickedRow
}
}
return rowNum
}
}
extension NSEvent {
// Create an event based on another event. The created event is identical to the
// original except for its <location> property.
func cloneEventButUseAdjustedWindowLocation(windowLocation: CGPoint) -> NSEvent {
return NSEvent.mouseEventWithType(type,
location: windowLocation,
modifierFlags: modifierFlags,
timestamp: timestamp,
windowNumber: windowNumber,
context: context,
eventNumber: eventNumber,
clickCount: clickCount,
pressure: pressure)!
}
}
Your approach is fine so far, you can click on the table row and this toggles the checkbox state.
As you said, the checkbox can be clicked on its own which doesn't trigger the table row selection. You need to subclass the NSTableCellView and assign this subclass to the cell's class property. Within that custom class file you can react on the checkbox toggle and change the underlying datasource of your table.
import Cocoa
class MyTableCellView: NSTableCellView {
#IBOutlet weak var checkbox: NSButton! // the checkbox
#IBAction func checkboxToggle(sender: AnyObject) {
// modify the datasource here
}
}
EDIT:
Here is a code snippet where a checkbox is toggled when the user clicks anywhere on the table cell:
func tableView(tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
if let cell = tableView.viewAtColumn(0, row: row, makeIfNecessary: false) as? MyTableCellView {
cell.checkbox.state = cell.checkbox.state == NSOnState ? NSOffState : NSOnState
}
return false
}
Note that it still needs a subclass for the table cell where you place an #IBOutlet to your checkbox to make it accessible in code.

Button Location based on random number - xCode 6 (swift)

I'm trying to make a button move to 5 different locations depending on a random number (1-6), I also want to display they random number in a label.
I have written the following code but the button doesn't seem to move to the location I specified in the IF statement: -
import UIKit
class game: UIViewController {
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.
}
//Decalare display label
#IBOutlet var d1: UILabel!
//One Button declaration
#IBOutlet var oneBTN: UIButton!
#IBAction func rollBTNPress(sender: AnyObject) {
//Generate random number
var r = arc4random_uniform(5)
//Display dice1 number in d1 Label
d1.text = "\(dice1)"
if (r == 1) {
oneBTN.center = CGPointMake(10, 70);
} else if (r == 2) {
oneBTN.center = CGPointMake(30, 70);
} else if (r == 3) {
oneBTN.center = CGPointMake(50, 70);
} else if (r == 4) {
oneBTN.center = CGPointMake(70, 70);
} else if (r == 5) {
oneBTN.center = CGPointMake(90, 70);
} else {
oneBTN.center = CGPointMake(0, 70);
}
}
}
The code runs and compiles without any issues. However the button position seems to be random and it's actually ignoring the coordinates specified in the IF statement.
What's even stranger is that if I comment out the d1.text = "\(dice1)" the button begins to move in the correct positions depending on the random number.
I also tried to change the CGPointMake and use CGPoint instead but I get exactly the same behaviour.
Thank you to vacawama for answering this question in the comments, indeed disabling Autolayout solved the issue.
More detailed answer here: -
Swift NSTimer and IBOutlet Issue
#IBAction func moveButton(button: UIButton) {
// Find the button's width and height
let buttonWidth = button.frame.width
let buttonHeight = button.frame.height
// Find the width and height of the enclosing view
let viewWidth = button.superview!.bounds.width
let viewHeight = button.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
button.center.x = xoffset + buttonWidth / 2
button.center.y = yoffset + buttonHeight / 2
}

Resize cells when bounds UICollectionView change

I'm using a horizontally, paging UICollectionView to display a variable number of collection view cells. The size of each collection view cell needs to be equal to that of the collection view and whenever the size of the collection view changes, the size of the collection view cells need to update accordingly. The latter is causing issues. The size of the collection view cells is not updated when the size of the collection view changes.
Invalidating the layout doesn't seem to do the trick. Subclassing UICollectionViewFlowLayout and overriding shouldInvalidateLayoutForBoundsChange: doesn't work either.
For your information, I'm using an instance of UICollectionViewFlowLayout as the collection view's layout object.
I think solution below is much cleaner. You only need to override one of UICollectionViewLayout's method like:
- (void)invalidateLayoutWithContext:(UICollectionViewFlowLayoutInvalidationContext *)context
{
context.invalidateFlowLayoutAttributes = YES;
context.invalidateFlowLayoutDelegateMetrics = YES;
[super invalidateLayoutWithContext:context];
}
and
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
if(!CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size))
{
return YES;
}
return NO;
}
as well.
I have similar behavior in my app: UICollectionView with cells that should have the same width as collection view at all time. Just returning true from shouldInvalidateLayoutForBoundsChange: didn't work for me either, but I managed to make it work in this way:
class AdaptableFlowLayout: UICollectionViewFlowLayout {
var previousWidth: CGFloat?
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
let newWidth = newBounds.width
let shouldIvalidate = newWidth != self.previousWidth
if shouldIvalidate {
collectionView?.collectionViewLayout.invalidateLayout()
}
self.previousWidth = newWidth
return false
}
}
In documentation it is stated that when shouldInvalidateLayoutForBoundsChange returns true then invalidateLayoutWithContext: will be called. I don't know why invalidateLayout works and invalidateLayoutWithContext: doesn't.
Swift 4 Xcode 9 implementation for height changes:
final class AdaptableHeightFlowLayout: UICollectionViewFlowLayout {
var previousHeight: CGFloat?
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
let newHeight = newBounds.height
let shouldIvalidate = newHeight != self.previousHeight
if shouldIvalidate {
collectionView?.collectionViewLayout.invalidateLayout()
}
self.previousHeight = newHeight
return false
}
}

Custom NSTableCellView labels not changing text color when selected

I have a custom NSTableCellView with 3 textfields, 1 that came along and 2 others that i created myself. Here's the problem:
The textfields' text color stays the same even when i click on the row. I've tried to implement a code i found out by googling but it doesn't work. My Custom NSTableCellView code is:
- (void)drawRect:(NSRect)dirtyRect{
NSColor *color = [NSColor colorWithCalibratedRed:(26/255.0) green:(26/255.0) blue:(26/255.0) alpha:1.0];
[self.textField setTextColor:color];
color = [NSColor colorWithCalibratedRed:(102/255.0) green:(102/255.0) blue:(102/255.0) alpha:1.0];
[_lbl1 setTextColor:color];
[_lbl2 setTextColor:color];
}
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
NSColor *color = (backgroundStyle == NSBackgroundStyleDark) ? [NSColor windowBackgroundColor] : [NSColor controlShadowColor];
self.textField.textColor = color;
self.lbl1.textColor = color;
self.lbl2.textColor = color;
[super setBackgroundStyle:backgroundStyle];
}
What can i do to make the labels' text color white when the user clicks on them?
Actually, overriding setBackgroundStyle on NSTableViewCell has worked perfectly for me, at least on OS X 10.8. It is updated on selection events and on window activation/deactivation.
Here's my custom cell impl — as trivial as it can get:
#implementation RuntimeInstanceCellView
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
[super setBackgroundStyle:backgroundStyle];
self.detailTextField.textColor = (backgroundStyle == NSBackgroundStyleLight ? [NSColor darkGrayColor] : [NSColor colorWithCalibratedWhite:0.85 alpha:1.0]);
// self.detailTextField.textColor = (backgroundStyle == NSBackgroundStyleLight ? [NSColor blackColor] : [NSColor whiteColor]);
}
#end
Expanding on the accepted answer, in Swift 2.0 the process is slightly different. Override the backgroundStyle property of your NSTableCellView subclass to add a didSet property observer:
class CustomTableCellView: NSTableCellView {
#IBOutlet weak var detailTextField: NSTextField!
override var backgroundStyle: NSBackgroundStyle {
didSet {
if self.backgroundStyle == .Light {
self.detailTextField.textColor = NSColor.controlTextColor()
} else if self.backgroundStyle == .Dark {
self.detailTextField.textColor = NSColor.alternateSelectedControlTextColor()
}
}
}
}
And for Swift 3 & 4 (isn’t this fun?):
override var backgroundStyle: NSView.BackgroundStyle {
didSet {
if self.backgroundStyle == .light {
self.detailTextField.textColor = NSColor.controlTextColor
} else if self.backgroundStyle == .dark {
self.detailTextField.textColor = NSColor.alternateSelectedControlTextColor
}
}
}
In your tableViewSelectionDidChange get the cell using
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath]; //replace UITableViewCell with your customCell class name if it other
//now as u got the instance of your cell u can modify the labels in it, like
cell.lable1.textColor = [UIColor whiteColor];
This will work for you.
You may get problem when you select other cell again after this, at that time previous cell may have still white colored labels. If this causes problems to you just have a NSIndexPath instance in your header class which represents previous selected indexPath, using this you can set back to default colors after selecting a new cell.