Loading data asynchronously in UICollectoinView inside UICollectoinviewCell - uicollectionviewcell

Hi I am using UICollectionView inside a UICollectionViewCell in swift tvOS so that it scrolls both horizontally and vertically.
Below is my Outer Collection view code.
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return variableCount
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// Each section contains a single `CollectionViewContainerCell`.
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// Dequeue a cell from the collection view.
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(CollectionViewContainerCell.reuseIdentifier, forIndexPath: indexPath) as? CollectionViewContainerCell
{
cell.configureWithData("some Parameters to build inner collectionView")
print("Returning Cell")
return cell
}
return CollectionViewContainerCell()
}
Method configureWithData is in CollectionViewContainerCell and in that I am calling an API asynchronously and reloading the inner collection view in dispatch_async.
Scrolling and displaying works fine. The problem comes with data when I scroll down outer collection view(4th cell which is not loaded initially) where in I get same data in 1st cell. I can see that when I say dequeueReusableCellWithReuseIdentifier it re uses the memory allocated to the cell initially which is not visible now. but I am configuring the cell which is not reflecting here.
Any help is appreciated.

You probably want to put some extra debug information to track down the data come back cycle for the remote API you are calling, in the case there is a delay in the inner collectionview datasource delegate methods and you have no provide additional clean up calls in the prepareForReuse method [https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionReusableView_class/#//apple_ref/occ/instm/UICollectionReusableView/prepareForReuse], the info loaded for the 1st cell will remain visible even if the inner collectionview has been reused for the 4th cell, before the new data managed to fill in via the async call.

Related

Kotlin - Trying to populate spinner from Fragment using findViewById(), but context: this is throwing an error

Obligatory preface: I'm quite new to Kotlin and Android Studio. As the title states, I'm trying to populate a spinner in Android Studio from within a fragment. First, I had an issue with findViewById(R.id.spinner) but I believe I've resolved it by prefixing it with root..
Currently, the only error being thrown is the context: this line. Ultimately, I'd like to use this spinner to allow the user to filter by different NY boroughs (hence, boroughs_array. Here's my current code within the FilterFragment -- my attempt to populate the spinner begins below return root.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
filtersViewModel =
ViewModelProviders.of(this).get(FiltersViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_filters, container, false)
return root
val spinner: Spinner = root.findViewById(R.id.spinner)
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter.createFromResource(
this,
R.array.boroughs_array,
android.R.layout.simple_spinner_item
).also { adapter ->
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Apply the adapter to the spinner
spinner.adapter = adapter
}
My current assumption is that this is not the right context, as I'm within a fragment. On the off-chance that this is correct, I'm not really sure how to handle this. If you can shine any sort of light on the issue, I will be eternally grateful.
You need to use use context!! instead of this. this refers to the current Fragment, which is not a Context. The current Fragment has a reference to the Context though, accessed via this.getContext(), or context for short.
The reason you need context!! is because getContext() is nullable (it may return null). It's safe in this case to 'force unwrap' (!!) because context will never be null inside onCreateView().
Another issue I've spotted, is you're returning from the onCreateView() function before you've set up the spinner.
Try this instead:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
filtersViewModel = ViewModelProviders.of(this).get(FiltersViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_filters, container, false)
val spinner: Spinner = root.findViewById(R.id.spinner)
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter.createFromResource(
context!!,
R.array.boroughs_array,
android.R.layout.simple_spinner_item
).also { adapter ->
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Apply the adapter to the spinner
spinner.adapter = adapter
return root
}
Also, just for further clarification - you've probably seen some examples where this is passed in for context. This is often done inside of an Activity, since Activity does extend Context.
You can access context like this:
context?.let { context ->
ArrayAdapter.createFromResource(
context,
R.array.boroughs_array,
android.R.layout.simple_spinner_item
).also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
}
}

Change width of a lookup column

I've created a lookup with two columns, first one containing and integer which works just fine but the second one has a long name and this is where the problem arises. Users should horizontally scroll in order to check the entire string and even in that case, the column's width is not big enough to display the whole data.
I've found this :
Adjusting column width on form control lookup
But i don't understand exactly where and what to add.
I am not sure but maybe I have to add the fact that this lookup is used on a menu item which points to an SSRS report, in the parameters section.
Update 1:
I got it working with a lookup form called like this :
Args args;
FormRun formRun;
;
args = new Args();
args.name(formstr(LookupOMOperatingUnit));
args.caller(_control);
formRun = classfactory.formRunClass(args);
formRun.init();
_control.performFormLookup(formRun);
and in the init method of this form i added:
public void init()
{
super();
element.selectMode(OMOperatingUnit_OMOperatingUnitNumber);
}
meaning the field i really need.
I am not sure i understand the mechanism completely but it seems it knows how to return this exact field to the DialogField from where it really started.
In order to make it look like a lookup, i have kept the style of the Design as Auto but changed the WindowType to Popup, HideToolBar to Yes and Frame to Border.
Probably the best route is do a custom lookup and change the extended data type of the key field to reflect that. In this way the change is reflected in all places. See form FiscalCalendarYearLookup and EDT FiscalYearName as an example of that.
If you only need to change a single place, the easy option is to override performFormLookup on the calling form. You should also override the DisplayLength property of the extended data type of the long field.
public void performFormLookup(FormRun _form, FormStringControl _formControl)
{
FormGridControl grid = _form.control(_form.controlId('grid'));
grid.autoSizeColumns(false);
super(_form,_formControl);
}
This will not help you unless you have a form, which may not be the case in this report scenario.
Starting in AX 2009 the kernel by default auto-updates the control sizes based on actual record content. This was a cause of much frustration as the sizes was small when there was no records and these sizes were saved! Also the performance of the auto-update was initially bad in some situations. As an afterthought the grid control autoSizeColumns method was provided but it was unfortunately never exposed as a property.
you can extends the sysTableLookup class and override the buildFromGridDesign method to set the grid control width.
protected void buildFormGridDesign(FormBuildGridControl _formBuildGridControl)
{
if (gridWidth > 0)
{
_formBuildGridControl.allowEdit(true);
_formBuildGridControl.showRowLabels(false);
_formBuildGridControl.widthMode(2);
_formBuildGridControl.width(gridWidth);
}
else
{
super(_formBuildGridControl);
}
}

Objc Blocks and memory management

I am setting up a block which gets called on a custom UIButton press. I am assigning the block to the UIButton instance in viewDidLoad().
- (void) viewDidLoad{
[super viewDidLoad];
_customBTN.block = ^{
self.test = #"Something";
}
}
Should I keep the block on the stack since the block can only get called on a button press and that means viewDidLoad() would be on the stack, and this can be considered performant/best practice ... or am I doing something wrong?
Blocks don't stay on stacks (memory stack) and rather are copied (Objc objects referred inside block get a retain call (+1 retainCount) and scalar variables get copied) to heap when instantiated. It means when the line:
_customBTN.block = ^{
self.test = #"Something";
};
is executed, stack frame created for viewDidLoad function got popped from the stack, self got a +1 retainCount and block was assigned to the block property of _customBTN, later on when _customBTN calls the block (on say button tapped event) the block is executed and it uses self and does what it does.
P.S. Its safe to use weak references to self when referring inside a block. weak wouldn't increase retainCount of self (which can lead to a retain cycles in worse cases).
You can do it like this if there is no other choice. Also, do not use self in block. Create a week reference like this:
__weak typeof(self) weakSelf = self;
And use it in blocks:
_customBTN.block = ^{
weakSelf.test = #"Something";
}

Adding event to a WatchKit item of a table

I have created a table in which the items contains some groups and some labels. I would like that when the user tap on the item, I receive an action. How can I do it? Is it possible to add an invisible button covering all the area of an item of the table?
You will need to connect the table row to another Interface Controller by control dragging from the row, and selecting push or modal.
Give the Storyboard Segue an identifier (string).
Then, to pass data to the Interface Controller, override contextForSegueWithIdentifier:inTable:rowIndex:
Swift:
override func contextForSegueWithIdentifier(segueIdentifier: String, inTable table: WKInterfaceTable, rowIndex: Int) -> AnyObject? {
if segueIdentifier == "identifier" {
return "banana"
}
return nil
}
Objective-C:
- (nullable id)contextForSegueWithIdentifier:(nonnull NSString *)segueIdentifier inTable:(nonnull WKInterfaceTable *)table rowIndex:(NSInteger)rowIndex {
if ([segueIdentifier isEqualToString:#"identifier"]) {
return #"banana";
}
return nil;
}
Button can contain groups (you have to choose this option in the editor) so the problem is solved! I don't understand why this answer is rated negatively. It works!

How to trigger an action from a NSTableCellView in view based NSTableView when using bindings

I'm facing a problem with a view-based NSTableView running on 10.8 (target is 10.7, but I think this is not relevant).
I'm using an NSTableView, and I get content values for my custom NSTableCellView through bindings. I use the obejctValue of the NSTableCellView to get my data.
I added a button to my cell, and I'd like it to trigger some action when clicked. So far I have only been able to trigger an action within the custom NSTableCellView's subclass.
I can get the row that was clicked like this, using the chain:
NSButton *myButton = (NSButton*)sender;
NSTableView *myView = (NSTableView*)myButton.superview.superview.superview;
NSInteger rowClicked = [myView rowForView:myButton.superview];
From there I don't know how to reach my App Delegate or controller where the action is defined.
As I am using cocoa bindings, I do not have a delegate on the NSTableView that I could use to trigger my action.
Do you have any idea how I could talked back to controller ?
Many thanks in advance!
Although you are using bindings you can still set your controller as the delegate for your tableview in the interface builder.
I see that you already are able to access the table view from inside your cell. The next task must be simple, just set the table view delegate as the target for your button's action.
Thanks for your question, I also will be triggering an action from a button on a NSTableView. Your question helped to put me on the correct path.
First to address the your solution to finding which row number my NSTableView is on. I was able to find it without knowing the button, in my custom NSTableView I installed the following as a first attempt:
- (NSInteger)myRowNumber
{
return [(NSTableView*)self.superview.superview rowForView:self];
}
this works fine, however it is less than robust. It only works if you already know specifically how deep you are in the view hierarchy. A more robust and universal solution is:
- (NSInteger)myRowNumber
{
NSTableView* tableView = nil;
NSView* mySuperview = self;
do
{
NSView* nextSuper = mySuperview.superview;
if (nextSuper == nil)
{
NSException *exception =
[NSException exceptionWithName:#"NSTableView not found."
reason:[NSString stringWithFormat:#"%# search went too deep.",
NSStringFromSelector(_cmd)] userInfo:nil];
#throw exception;
}
if ([nextSuper isKindOfClass:[NSTableView class]])
tableView = (NSTableView*)nextSuper;
else
mySuperview = mySuperview.superview;
} while (tableView == nil);
return [tableView rowForView:self];
}
this not only works at the NSTableView level, but works with anything installed at any level above it, no matter how complex the view hierarchy.
As to the unanswered part of your question, I established an IBOutlet in my class and using interface builder tied if to my files owner (in my case my document class). Once I had a reference to the class I was sending my message to, and the row number, I call the function. In my case the call required that I pass the row number it originates from.
[self.myDoc doSomethingToRow:self.myRowNumber];
I tested this and it works at various levels of the view hierarchy above NSTableView. And it functions without having to have the row selected first (which appears to be assumed in Apples documentation).
Regards, George Lawrence Storm, Maltby, Washington, USA
Use rowForView: and the responder chain
To respond to a control's action embedded within an NSTableCellView, the control should issue the action to the First Responder. Alternatively, File Owner is possible but this is more tightly coupled.
Use rowForView: within the action method to determine which row's control issued the action:
- (IBAction)revealInFinder:(id)sender {
NSInteger row = [self.tableView rowForView:sender];
...
}
The action is implemented within any of the responder chain classes. Most likely, this will be your subclassed NSWindowController instance. The responder could also be the application delegate; assuming the delegate has a means to talk to the NSTableView.
See Apple's example TableViewPlayground: Using View-Based NSTableView and NSOutlineView to see this in action.
Suhas answer helped me.
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "EDIT_CELL_VIEW"), owner: self) as? SymbolManagerCell {
if let editButton = cell.subviews[0] as? NSButton {
editButton.target = cell // this is required to trigger action
}
return cell
}
return nil
}

Resources