How can I animate any UIView inside UITableViewCell? - animation

let label = UILabel(frame: CGRectMake(20.0, 20.0, 15.0, 15.0))
label.backgroundColor = UIColor.flamingoColor()
UIView.animateWithDuration(3.0, animations: {
cell.addSubview(label)
label.backgroundColor = UIColor.yellowColor()
})
This code I put inside tableView:cellForRowAtIndexPath: -> result is a subview that has a yellow background, but I haven't seen any animation. The same is for method tableView:didEndDisplayingCell:
Which method should I use or what kind of animation should I put there to see any animations inside UITableViewCell. I know that I cannot animate existing labels, because these are with constraints.

Firstly, you should move cell.addSubview(label) to outside of the animation block and add the label to the contentView of the cell.
Back to your problem. You can't animate the background colour of a UILabel. You could use a UIView, of the same size, behind the UILabel for the same effect though.
To get the UIView to change colour once it's appeared I used a UITableViewCell subclass:
class AnimatedCell : UITableViewCell {
let animatedView = UIView()
/* Setup by adding animatedView to the contentView and setting the
animated view's initial frame and colour.
*/
func animate() {
UIView.animateWithDuration(3.0) {
animatedView.backgroundColor = UIColor.redColor()
}
}
Then in your UITableViewController subclass, when the view controller is presented the UITableView will be populated so you need to animate those cells with:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated: Bool)
for cell in tableView.visibleCells() as! [UITableViewCell] {
if let animatedCell = cell as? AnimatedCell {
animatedCell.animate()
}
}
}
Then, for cells that appear when you scroll:
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if let animatedCell = cell as? AnimatedCell {
a.animateLabel()
}
}
Also, you can animate with AutoLayout, you need to change the constant on the NSLayoutConstraint you want to animate. This gives a good example: iOS: How does one animate to new autolayout constraint (height)

Related

Additional view in NSCollectionViewItem pauses dragEvent in CollectionViewController

I am trying to implement drop delegates on a NSCollectionViewController and having issues using a custom NSCollectionViewItem with an additional View Layer I've added onto the CollectionView Item. FWIW, The additional view is used draw a dashed border to indicate a drop area.
The drag event works fine on this collectionItem, and all other collectionItems without this view when it is hidden, but as soon as the drag event occurs on top of this view, the drag event pauses.
The drag event resumes as soon as the mouse is dragged outside of the view, but nothing happens if I release the drag while the mouse is over the view.
I would love to know what is happening here and how to prevent the custom view from "stealing" the mouse event from the CollectionViewContoller.
Delegate Method on DropViewController
func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
print("1")
if proposedDropIndexPath.pointee.item <= self.destinationDirectoryArray.count {
if proposedDropOperation.pointee == NSCollectionView.DropOperation.on {
return .move
}
} else if proposedDropIndexPath.pointee.item == self.destinationDirectoryArray.count {
//There's some stuff here validating the URL removed for brevity. It works okay when the focus is outside the view, but happy to add back in if helpful
if proposedDropOperation.pointee == NSCollectionView.DropOperation.on {
return .move
}
}
return[]
}
Configuring Collection View
func configureCollectionView() {
let flowLayout = NSCollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = 8.0
flowLayout.minimumLineSpacing = 8.0
destinationCollectionView.delegate = self
destinationCollectionView.dataSource = self
destinationCollectionView.register(NSNib(nibNamed: "DestinationCollectionItem", bundle: nil), forItemWithIdentifier: directoryItemIdentifier)
destinationCollectionView.collectionViewLayout = flowLayout
destinationCollectionView.registerForDraggedTypes([.fileURL])
destinationCollectionView.setDraggingSourceOperationMask(NSDragOperation.move, forLocal: true)
}
Collection View Item Setup
class DestinationCollectionItem: NSCollectionViewItem {
#IBOutlet weak var backgroundLayer: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.highlightState = .none
view.wantsLayer = true
view.layer?.cornerRadius = 8.0
backgroundLayer.isHidden = true
}
}
Custom Border View - Applied custom class in Xib and linked to File's Owner
class BorderedView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let path : NSBezierPath = NSBezierPath(roundedRect: self.bounds, xRadius: 10.0, yRadius: 10.0)
path.addClip()
let dashHeight: CGFloat = 2
let dashLength: CGFloat = 7
let dashColor: NSColor = .lightGray
// setup the context
let currentContext = NSGraphicsContext.current!.cgContext
currentContext.setLineWidth(dashHeight)
currentContext.setLineDash(phase: 0, lengths: [dashLength])
currentContext.setStrokeColor(dashColor.cgColor)
// draw the dashed path
let cgPath : CGPath = CGPath(roundedRect: NSRectToCGRect(self.bounds), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)
currentContext.addPath(cgPath)
currentContext.strokePath()
}
}
Well - I solved this one pretty quick.
While I previously tried adding unregisterDraggedTypes() to the backgroundLayer, the issue turned out to also be occurring on the image layer. I applied it to both the Image and backgroundLayer and it works now.
Collection View Item Setup
class DestinationCollectionItem: NSCollectionViewItem {
#IBOutlet weak var backgroundLayer: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.highlightState = .none
view.wantsLayer = true
view.layer?.cornerRadius = 8.0
backgroundLayer.isHidden = true
backgroundLayer.unregisterDraggedTypes()
self.imageView?.unregisterDraggedTypes()
self.textField?.unregisterDraggedTypes()
}
}

Setting Background Color of NSTableCellView in Swift

After searching through SO and online, I'm struggling to figure out a concept that I thought would be relatively simple. Essentially, I have a table in an OS X Swift app, with several columns, and it is currently populating data. I am trying to discern how I can set the background color of each "row" (ideally with alternating colors, but I'll start with just one color). My MasterViewController file is like so;
import Cocoa
class MasterViewController: NSViewController {
var minions = [Minion]()
func setupSampleMinion() {
minions = Minion.fetchMinionData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
// MARK: - NSTableViewDataSource extension MasterViewController: NSTableViewDataSource {
func numberOfRowsInTableView(aTableView: NSTableView) -> Int {
return self.minions.count
}
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
// 1
var cellView: NSTableCellView = tableView.makeViewWithIdentifier(tableColumn!.identifier, owner: self) as! NSTableCellView
let minion = self.minions[row]
// 2
if tableColumn!.identifier == "MyColumn" {
// 3
cellView.imageView!.image = NSImage(named: "minion.name!")
cellView.textField!.stringValue = minion.name!
return cellView
}
return cellView
}
}
func tableView(tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
let myCustomView = MyRowView()
return myCustomView
}
class MyRowView: NSTableRowView {
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
self.backgroundColor = NSColor(red: 0.76, green: 0.82, blue: 0.92, alpha: 1)
NSRectFill(dirtyRect)
}
}
// MARK: - NSTableViewDelegate extension MasterViewController: NSTableViewDelegate {
}
While I THINK I have some of the coding right, this does not seem to set the background color if the row in any way. Any thoughts or overall guidance would be most appreciated. Thank you!
If you just want the rows to use the standard alternating colors for rows, there's a simple checkbox in the Attributes inspector for the table view in IB to enable that.
To use a non-standard background color, you want to set the row view's backgroundColor, but not inside of drawRect(). If you change properties of a view that affect how it draws inside of drawRect(), that will probably mark the view as needing display, which will provoke another call to drawRect(), etc.
It should work to just set it in the delegate's tableView(_:didAddRowView:forRow:) method. That's documented in the description of the backgroundColor property.
With regard to your attempt at overriding drawRect(): setting the row view's backgroundColor will presumably affect how the superclass draws. So, setting it after calling through to super is unlikely to help. It definitely won't affect the subsequent NSRectFill() call. That function relies on the fill color set for the current graphics context, which is implicit. You would change that by calling someColor.set().
Buy, anyway, there should be no need to override drawRect() given that you can set the backgroundColor. If you want to achieve some background drawing beyond what's possible by just setting a color, you should override drawBackgroundInRect() and not drawRect(), anyway.
Finally, your implementation of tableView(tableView:rowViewForRow:) should call the table view's makeViewWithIdentifier(_:owner:) method first, before creating a new view. And it should set the identifier on any new view it does create. That allows the table view to maintain a reuse queue of views, to avoid constantly destroying and recreating views.

Custom TableViewCell with Subview whose height changes

I'm using a TableView and have a custom TableViewCell that I've added a subview to.
The problem is that I need the subview's height to change sometimes and therefore, the table's contentView would have to be updated as well as the row's height.
The subview of the custom TableViewCell is represented by the yellow background.
These images show what's currently happening in my simulator.
On Load
After the event that causes the subview's height to increase
What's the best approach to take with something like this?
Should I use constraints? And if so, what kind of constraints should I use? Would I have to then reload the tableview too every time the subview's size changes?
Here is the code I'm currently using for my custom TableViewCell:
import UIKit
class CustomCell: UITableViewCell {
var newView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.newView = UIView(frame: self.frame)
self.newView.backgroundColor = .yellowColor()
self.addSubview(newView)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.newView.frame.size.width = self.frame.size.width // because self.frame.width is different than it was in the init method
}
func somethingHappenedThatMySubviewHasToIncreaseInHeight() {
self.newView.frame.size.height = self.frame.size.height + 40
}
}
The best approach is to use Auto Layout and self-sizing cells. Setup constraints in storyboard for your custom cell.
You will not need to reload the tableView. Each cell will automatically adjust its height, based on how much vertical space its subview takes.
For more information, see the detailed walkthrough by smileyborg in his answer to Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.

UITableView crashes if loaded from ContainerView

I've a big big problem related with a tableview (with a prototype cell made in IB) being loaded in a Container View. It loads perfectly, but when I try to tap or scroll over it just crashes with
respondsToSelector:]: message sent to deallocated instance 0x79a96e20
If I load the ViewController where the tableview is (without being embedded in a container view) it just works, which makes me think that has something to do with tableview being unallocated after parent viewcontroller finishes its loading process.
I'm pretty new with this and it's making me mad!!
Thanks for your help.
(THE VIEWCONTROLLER WHICH FILLS THE CONTAINERVIEW)
class cgGameViewerController: UIViewController {
#IBOutlet var cgGame:UIView; //Connected to IB ContainerView
override func viewDidLoad() {
var cgGameController=self.storyboard.instantiateViewControllerWithIdentifier("myIBviewController") as cgGameViewController;
self.cgGame.addSubview(cgGameController.view);
super.viewDidLoad()
}
}
(THE VIEWCONTROLLER WHICH IS LOADED IN A CONTAINERVIEW. In IB, UITableView delegate and datasource, is attached to the viewcontroller)
class cgGameViewController: UIViewController,UICollectionViewDataSource,UICollectionViewDelegate,UITableViewDelegate,UITableViewDataSource {
func tableView(tableView:UITableView!, numberOfRowsInSection section:Int) -> Int {
return 5;
}
func tableView(tableView:UITableView!, cellForRowAtIndexPath indexPath:NSIndexPath!) -> UITableViewCell! {
var myCell:UITableViewCell=tableView.dequeueReusableCellWithIdentifier("myCellInIB", forIndexPath: indexPath) as UITableViewCell;
return myCell;
}
}

Using Autolayout with expanding NSTextViews

My app consists of an NSScrollView whose document view contains a number of vertically stacked NSTextViews — each of which resizes in the vertical direction as text is added.
Currently, this is all managed in code. The NSTextViews resize automatically, but I observe their resizing with an NSViewFrameDidChangeNotification, recalc all their origins so that they don't overlap, and resize their superview (the scroll view's document view) so that they all fit and can be scrolled to.
This seems as though it would be the perfect candidate for autolayout! I set NSLayoutConstraints between the first text view and its container, the last text view and its container, and each text view between each other. Then, if any text view grows, it automatically "pushes down" the origins of the text views below it to satisfy contraints, ultimately growing the size of the document view, and everyone's happy!
Except, it seems there's no way to make an NSTextView automatically grow as text is added in a constraints-based layout? Using the exact same NSTextView that automatically expanded as text was entered before, if I don't specify a constraint for its height, it defautls to 0 and isn't shown. If I do specify a constraint, even an inequality such as >=20, it stays stuck at that size and doesn't grow as text is added.
I suspect this has to do with NSTextView's implementation of -intrinsicContentSize, which by default returns (NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric).
So my questions: if I subclasses NSTextView to return a more meaningful intrinsicContentSize based on the layout of my text, would my autolayout then work as expected?
Any pointers on implementing intrinsicContentSize for a vertically resizing NSTextView?
I am working on a very similar setup — a vertical stack of views containing text views that expand to fit their text contents and use autolayout.
So far I have had to subclass NSTextView, which is does not feel clean, but works superbly in practice:
- (NSSize) intrinsicContentSize {
NSTextContainer* textContainer = [self textContainer];
NSLayoutManager* layoutManager = [self layoutManager];
[layoutManager ensureLayoutForTextContainer: textContainer];
return [layoutManager usedRectForTextContainer: textContainer].size;
}
- (void) didChangeText {
[super didChangeText];
[self invalidateIntrinsicContentSize];
}
The initial size of the text view when added with addSubview is, curiously, not the intrinsic size; I have not yet figured out how to issue the first invalidation (hooking viewDidMoveToSuperview does not help), but I'm sure I will figure it out eventually.
I had a similar problem with an NSTextField, and it turned out that it was due to the view wanting to hug its text content tightly along the vertical orientation. So if you set the content hugging priority to something lower than the priorities of your other constraints, it may work. E.g.:
[textView setContentHuggingPriority:NSLayoutPriorityFittingSizeCompression-1.0 forOrientation:NSLayoutConstraintOrientationVertical];
And in Swift, this would be:
setContentHuggingPriority(NSLayoutConstraint.Priority.fittingSizeCompression, for:NSLayoutConstraint.Orientation.vertical)
Here is how to make an expanding NSTextView using Auto Layout, in Swift 3
I used Anchors for Auto Layout
Use textDidChange from NSTextDelegate. NSTextViewDelegate conforms to NSTextDelegate
The idea is that textView has edges constraints, which means whenever its intrinsicContentSize changes, it will expand its parent, which is scrollView
import Cocoa
import Anchors
class TextView: NSTextView {
override var intrinsicContentSize: NSSize {
guard let manager = textContainer?.layoutManager else {
return .zero
}
manager.ensureLayout(for: textContainer!)
return manager.usedRect(for: textContainer!).size
}
}
class ViewController: NSViewController, NSTextViewDelegate {
#IBOutlet var textView: NSTextView!
#IBOutlet weak var scrollView: NSScrollView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
activate(
scrollView.anchor.top.constant(100),
scrollView.anchor.paddingHorizontally(30)
)
activate(
textView.anchor.edges
)
}
// MARK: - NSTextDelegate
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else { return }
print(textView.intrinsicContentSize)
textView.invalidateIntrinsicContentSize()
}
}
Class ready for copying and pasting. Swift 4.2, macOS 10.14
class HuggingTextView: NSTextView, NSTextViewDelegate {
//MARK: - Initialization
override init(frame: NSRect) {
super.init(frame: frame)
delegate = self
}
override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) {
super.init(frame: frameRect, textContainer: container)
delegate = self
}
required init?(coder: NSCoder) {
super.init(coder: coder)
delegate = self
}
//MARK: - Overriden
override var intrinsicContentSize: NSSize {
guard let container = textContainer, let manager = container.layoutManager else {
return super.intrinsicContentSize
}
manager.ensureLayout(for: container)
return manager.usedRect(for: container).size
}
//MARK: - NSTextViewDelegate
func textDidChange(_ notification: Notification) {
invalidateIntrinsicContentSize()
}
}

Resources