UICollectionView with autosizing cells incorrectly positions supplementary view - autolayout

I am using a UICollectionView with a Flow Layout and trying to get the collectionView to size the cells appropriately according to AutoLayout constraints.
While the cells work as intended, I am running in to issues with the layout of any supplementary views that I add to the CollectionView.
Specifically, the supplementaryView will be in the wrong position (i.e., the y origin is incorrect) on initial layout, before 'correcting' itself after I scroll.
For reference, here is how I am configuring my cell sizing:
1. Set the collectionViewLayout's estimated item size
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = CGSizeMake(375, 50.0)
layout.minimumInteritemSpacing = 0.0
layout.minimumLineSpacing = 0.0
let view = UICollectionView(frame: CGRectZero, collectionViewLayout: layout)
view.backgroundColor = UIColor.whiteColor()
view.alwaysBounceVertical = true
return view
}()
2. Use subclasses of AutoLayoutCollectionViewCell
class AutoLayoutCollectionViewCell: UICollectionViewCell {
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutIfNeeded()
layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return layoutAttributes
}
}
Note that at this point, everything works as intended.
The next step is where we fail.
3. Provide a reference size for a header
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSizeMake(CGRectGetWidth(collectionView.frame), 30.0)
}
My question is: Why does this happen? How can I get this to correct? How am I supposed to handle supplementary views within a collectionView that self-sizes its cells??

I had the same problem, what solved it for me was to subclass UICollectionViewFlowLayout and override the following function:
override func invalidationContext(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forPreferredLayoutAttributes: preferredAttributes, withOriginalAttributes: originalAttributes)
context.invalidateSupplementaryElements(ofKind: UICollectionElementKindSectionHeader,
at: [originalAttributes.indexPath])
return context
}

Related

Different cells CollectionView rxswift

Good evening.
Tell me please
I need to make such an implementation
Have: CollectionView
Rxswift
The maximum number of cells is 6
If there are less than 6 elements in a certain array, then the first cells are filled with one type (by the number of elements in the array), and the rest with other cells.
collectionView.register(UINib(nibName: "PhotoCollectionCell", bundle: nil), forCellWithReuseIdentifier: "PhotoCell")
collectionView.register(UINib(nibName: "EmptyCollectionCell", bundle: nil), forCellWithReuseIdentifier: "EmptyCell")
Please check collectionView.rx.items in UICollectionView+Rx.swift from RxCocoa
Here is the example from code itselft
let items = Observable.just([
1,
2,
3
])
items
.bind(to: collectionView.rx.items) { (collectionView, row, element) in
let indexPath = IndexPath(row: row, section: 0)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! NumberCell
cell.value?.text = "\(element) # \(row)"
return cell
}
.disposed(by: disposeBag)
You can dequeue whatever cell you want based on the index of the row or type of the data, just like the way we did in DataSource
You need to import RxDataSources in case you want different cell type.
// TableView Cell Nib Register
tableView.register(R.nib.meChatCell)
tableView.register(R.nib.otherChatCell)
vm.output.chats
.bind(to: tableView.rx.items){(tv, row, item) -> UITableViewCell in
if item.userId == 0 {
let cellIdentifier = R.reuseIdentifier.meChatCell.identifier
let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: IndexPath.init(row: row, section: 0)) as! MeChatCell
cell.vm = item
return cell
} else {
let cellIdentifier = R.reuseIdentifier.otherChatCell.identifier
let cell = tv.dequeueReusableCell(withIdentifier: cellIdentifier, for: IndexPath.init(row: row, section: 0)) as! OtherChatCell
cell.vm = item
return cell
}
}.disposed(by: disposeBag)
}

Autolayout constraints in TextField of an outlineView

last night a have an autolayout issue. I was googling and try to find something similar in SO. Even the apple doc doesn't point me to the right direction. Maybe my search terms are completely wrong.
Maybe you guys can bring some light into my darkness.
I added a NSOutlineView in storyboard and added some constraints to the NSTableCellView. As you can see, i added a trailing space to Superview of 50:
My example code adds some foo's and bar's into the outlineView by identifyer:
func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
if tableColumn?.identifier == "NAME_COLUMN" {
cell = outlineView.makeViewWithIdentifier("NAME_COLUMN", owner: self) as NSTableCellView
cell.textField!.stringValue = "foo"
cell.textField!.editable = true
cell.textField!.delegate = self
} else
if tableColumn?.identifier == "VALUE_COLUMN" {
cell = outlineView.makeViewWithIdentifier("VALUE_COLUMN", owner: self) as NSTableCellView
cell.textField!.stringValue = "bar"
cell.textField!.editable = true
cell.textField!.delegate = self
}
return cell
}
But the trailing space will not show up in my running application!
I even try to set the cell display:
cell.needsDisplay = true
cell.needsLayout = true
cell.needsUpdateConstraints = true
or - according to someone on the internet - add "requiresConstraintBasedLayout":
class func requiresConstraintBasedLayout() -> Bool {
return true
}
but all without luck. The trailing space do not appears and the bar's on the right side border looks awful.
How do i use a TableViewCell inside a OutlineView with a trailing space?
Thanks a lot for any kind of hint.
ps

Swift get value from UnsafeMutablePointer<Void> using UnsafePointer<String>

I am trying to pass contextInfo of typeUnsafeMutablePointer<Void> to UISaveVideoAtPathToSavedPhotosAlbum and use it in the callback function. For some reason I am unable to access contextInfo as a string using UnsafePointer<String>(x).memory when I am in the callback function.
I am pretty sure it is something simple I am missing but have spent way to many hours trying to figure this out.
Below is some code that I have tried.
The following code works.
var testStr:String = "hello"
takesAMutableVoidPointer(&testStr)
func takesAMutableVoidPointer(x: UnsafeMutablePointer<Void>){
var pStr:String = UnsafePointer<String>(x).memory
println("x = \(x)")
println("pStr = \(pStr)")
}
However the following code does not work.
var testStr:String = "hello"
if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(filePath){ //the filePath is compatible
println("Compatible")
//UISaveVideoAtPathToSavedPhotosAlbum(filePath, self, nil, nil)
UISaveVideoAtPathToSavedPhotosAlbum(filePath, self, "video:didFinishSavingWithError:contextInfo:", &testStr)
}
else{
println("Not Compatible")
}
func video(video: NSString, didFinishSavingWithError error:NSError, contextInfo:UnsafeMutablePointer<Void>){
var pStr:String = UnsafePointer<String>(contextInfo).memory
println("contextInfo = \(contextInfo)")
println("pStr = \(pStr)")
}
Once I get to the following line:
var pStr:String = UnsafePointer<String>(contextInfo).memory
I keep getting the following error:
Thread 1: EXC_BAD_ACCESS(code=1, address=0x0)
Any help with this would be greatly appreciated.
Thanks.
Update
Rintaro commented that testStr needs to be top level but the following code works.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var testStr:String = "hello"
takesAMutableVoidPointer(&testStr)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func takesAMutableVoidPointer(x: UnsafeMutablePointer<Void>){
var answer = UnsafePointer<String>(x).memory
println("x = \(x)")
println("answer = \(answer)")
}
}
I am trying not to use global variables unless I have to. I may have to but since I am able to execute the above code, it seems as though I do not need to use a global variable.
As discussed in OP comments, testStr has already been freed.
Is there any way to force the retaining of a variable that has been created in a function? Then release it later?
It's not impossible, but I don't know this is the best way to do that.
Anyway, try this with Playground or OS X "Command Line Tool" template:
import Foundation
func foo() {
var str:NSString = "Hello World"
let ptr = UnsafePointer<Void>(Unmanaged<NSString>.passRetained(str).toOpaque())
bar(ptr)
}
func bar(v:UnsafePointer<Void>) {
let at = dispatch_time(
DISPATCH_TIME_NOW,
Int64(2.0 * Double(NSEC_PER_SEC))
)
dispatch_after(at, dispatch_get_main_queue()) {
baz(v)
}
}
func baz(v:UnsafePointer<Void>) {
println("notified")
let str = Unmanaged<NSString>.fromOpaque(COpaquePointer(v)).takeRetainedValue()
println("info: \(str)")
}
foo()
println("started")
dispatch_main()
Unmanaged<NSString>.passRetained(str) increments the retain count.
Unmanaged<NSString>.fromOpaque(...).takeRetainedValue() decrements it, and extract the object.
I think, using pure Swift String is impossible. because String is struct and is allocated in stack memory. Maybe the buffer of it is allocated in heap, but we cannot access it directly.

View-based NSOutlineView without NIB?

NSOutlineView is a subclass of NSTableView. And currently, NSTableView supports two implementations.
Cell-based.
View-based.
To make OSX 10.8 Finder style side bar (with automatic gray Icon styling), need to use view-based table view with source-list highlight style.
With NIBs, this is typical job. Nothing hard. (see SidebarDemo) But I want to avoid any NIBs or Interface Builder. I want make the side bar purely programmatically.
In this case, I have big problem. AFAIK, there's no way to supply prototype view for specific cell. When I open .xib file, I see <tableColumn> is containing <prototypeCellViews>. And this specifies what view will be used for the column. I can't find how to set this programmatically using public API.
As a workaround, I tried to make cell manually using -[NSTableView makeViewWithIdentifier:owner:] and -[NSTableView viewAtColumn:row:makeIfNecessary:], but none of them returns view instance. I created a NSTableCellView, but it doesn't have image-view and text-field instances. And I also tried to set them, but the fields are marked as assign so the instances deallocated immediately. I tried to keep it by forcing retaining them, but it doesn't work. NSTableView doesn't manage them, so I am sure that table view don't like my implementation.
I believe there's a property to set this prototype-view for a column. But I can't find them. Where can I find the property and make system-default NSOutlineView with source-list style programmatically?
If you follow the example in SidebarDemo, they use a subclass of NSTableCellView for the detail rows. In order to emulate the InterfaceBuilder mojo, you can hook everything together in the constructor. The rest is the same as the demo (see outlineView:viewForTableColumn:item:).
#interface SCTableCellView : NSTableCellView
#end
#implementation SCTableCellView
- (id)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
[self setAutoresizingMask:NSViewWidthSizable];
NSImageView* iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 6, 16, 16)];
NSTextField* tf = [[NSTextField alloc] initWithFrame:NSMakeRect(21, 6, 200, 14)];
NSButton* btn = [[NSButton alloc] initWithFrame:NSMakeRect(0, 3, 16, 16)];
[iv setImageScaling:NSImageScaleProportionallyUpOrDown];
[iv setImageAlignment:NSImageAlignCenter];
[tf setBordered:NO];
[tf setDrawsBackground:NO];
[[btn cell] setControlSize:NSSmallControlSize];
[[btn cell] setBezelStyle:NSInlineBezelStyle];
[[btn cell] setButtonType:NSMomentaryPushInButton];
[[btn cell] setFont:[NSFont boldSystemFontOfSize:10]];
[[btn cell] setAlignment:NSCenterTextAlignment];
[self setImageView:iv];
[self setTextField:tf];
[self addSubview:iv];
[self addSubview:tf];
[self addSubview:btn];
return self;
}
- (NSButton*)button {
return [[self subviews] objectAtIndex:2];
}
- (void)viewWillDraw {
[super viewWillDraw];
NSButton* btn = [self button];
...
Here's #jeberle's code re-written in Swift 4 (five years later!):
class ProgrammaticTableCellView: NSTableCellView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.autoresizingMask = .width
let iv: NSImageView = NSImageView(frame: NSMakeRect(0, 6, 16, 16))
let tf: NSTextField = NSTextField(frame: NSMakeRect(21, 6, 200, 14))
let btn: NSButton = NSButton(frame: NSMakeRect(0, 3, 16, 16))
iv.imageScaling = .scaleProportionallyUpOrDown
iv.imageAlignment = .alignCenter
tf.isBordered = false
tf.drawsBackground = false
btn.cell?.controlSize = .small
// btn.bezelStyle = .inline // Deprecated?
btn.cell?.isBezeled = true // Closest property I can find.
// btn.cell?.setButtonType(.momentaryPushIn) // Deprecated?
btn.setButtonType(.momentaryPushIn)
btn.cell?.font = NSFont.boldSystemFont(ofSize: 10)
btn.cell?.alignment = .center
self.imageView = iv
self.textField = tf
self.addSubview(iv)
self.addSubview(tf)
self.addSubview(btn)
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var button: NSButton {
get {
return self.subviews[2] as! NSButton
}
}
}
Edit: I found a link (that will inevitably rot away – it was last revised in 2011) to Apple's SidebarDemo that #jeberle based his code on.
In addition to #jeberle 's answer, I need to note something more.
The key to keep the text-field and image-view is adding them as subviews of the NSTableCellView.
Set NSTableView.rowSizeStyle to a proper value (non-Custom which is default value) to make the table-view layout them automatically. Otherwise, you have to layout them completely yourself.
Do not touch frame and autoresizing stuffs if you want to use predefined NSTableViewRowSizeStyle value. Otherwise, the layout might be broken.
You can adjust row-height by providing private func outlineView(outlineView: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat delegate method. Setting NSTableView.rowHeight is not a good idea because it needs NSTableView.rowSizeStyle set to Custom which will turn off cell text/image layout management provided by default.
You can reuse row/cell views by settings NSView.identifier property. (example)

Redraw NSTableView with new data from file when NSViewController re-loaded?

I have a Mac OS X Document based app that has multiple NSViewControllers that I switch between and each view displays data from plist files in NSTableViews based on the user selections in the previous NSViewController's NSTableView. The problem I have is that I can't figure out what function can be called, every time a NSViewController gets loaded, to read the correct data from a file to display in the NSTableView. For UIViewControllers I used the function family of viewDidLoad, viewWillAppear, but I haven't been able to find the corresponding functions for NSViewController.
Currently I am using awakeFromNib, which works fine, but only the first time the NSViewController gets loaded. I've tried loadView, but that collapses the NSView. I assume that I need to do more setup to use loadView.
I'm using the View Swapping code from Hillegass's book Cocoa Programming for MAC OS X which switches ViewControllers with the following code:
- (void)displayViewController:(ManagingViewController *)vc
curBox: (NSBox *)windowBox
{
// End editing
NSWindow *w = [windowBox window];
BOOL ended = [w makeFirstResponder:w];
if (!ended) {
NSBeep();
return;
}
NSView *v = [vc view];
NSSize currentSize = [[windowBox contentView] frame].size;
NSSize newSize = [v frame].size;
float deltaWidth = newSize.width - currentSize.width;
float deltaHeight = newSize.height - currentSize.height;
NSRect windowFrame = [w frame];
windowFrame.size.height += deltaHeight;
windowFrame.origin.y -= deltaHeight;
windowFrame.size.width += deltaWidth;
[windowBox setContentView:nil];
[w setFrame:windowFrame
display:YES
animate:YES];
[windowBox setContentView:v];
// Put the view controller in the responder chain
[v setNextResponder:vc];
[vc setNextResponder:windowBox];
}
and puts the NSView Controller in the responder chain.
Is there some function I can call to setup the view every time I swap NSViewControllers? Can I check that a NSViewController has become the firstResponder?
This post provided the answer.
I added the following code:
- (void)viewWillLoad {
}
- (void)viewDidLoad {
}
- (void)loadView {
[self viewWillLoad];
[super loadView];
[self viewDidLoad];
}
and at the beginning of displayViewController I added
[vc loadView]

Resources