Show a preloaded search results? - objective-c

I have a non-tableview view with a searchbar in it, and while it works perfectly, the search display controller hides the table view and overlays a dark dimmed view when an empty string is in the searchbar. I want it to show a preloaded data when the empty string is in the searchbar instead of hiding the table view and overlaying the dark dimmed view underneath the searchbar. Just like how the Google search bar in Safari for iOS works.
I found a similar question asked on stackoverflow before:
UISearchDisplayController - how to preload searchResultTableView, I couldn't really get it to work.
I have no problem getting the preloaded data and setting the current data to it, but I'm not sure how to prevent the displaycontroller from removing the searchResultsTableView.
Thanks in advance.

I finally found a way to do this.
I found out that the searchDisplayController simply removes the searchResultsTableView from the superview, so I just added the table view back into the superview whenever the display controller tried to hide the table view:
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
// add the tableview back in
[self.view addSubview:self.searchDisplayController.searchResultsTableView];
}
and then I also have to show the tableview the first time the searchbar is clicked, so I did:
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
// after the data has been preloaded
self.searchResults = self.allItems;
[self.searchDisplayController.searchResultsTableView reloadData];
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
[self.view addSubview:self.searchDisplayController.searchResultsTableView];
}
For me, 'allItems' is where I stored all the searchable items and 'searchResults' is where the filtered items (after the search) is stored. And of course, you would have to preload the items (e.g. search history) before reloading the data.
I don't know if this is a nice way or not to do it in terms of the performance and what not, but it worked perfectly for me, and I hope this could be useful for other people as well. Please comment if there is a better way to do this.

After hours and hours I finally figured out a solution that works in iOS 7
Just implement the following two methods in your UISearchDisplayDelegate
-(void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView {
// We need to prevent the resultsTable from hiding if the search is still active
if (self.searchDisplayController.active == YES) {
tableView.hidden = NO;
}
}
When the search starts, the searchResultsTableView is being hidden automatically, so we need to unhide it again
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
controller.searchResultsTableView.hidden = NO;
// Then we need to remove the semi transparent overlay which is here
for (UIView *v in [[[controller.searchResultsTableView superview] superview] subviews]) {
if (v.frame.origin.y == 64) {
[v setHidden:YES];
}
}
}

I found a much better solution to this issue, and it seems to work perfectly on iOS 6 and 7. While it is still a hack, its a much cleaner and future proof hack than the above. The other solutions do not work consistently and prevent some UISearchDisplayDelegate methods from ever firing! Further I had complex insetting issues which I could not resolve with the above methods. The main issue with the other solutions is that they seriously confuse the internals of the UISearchDisplayController. My solution is based on the observation that UISearchDisplayContoller is a UISearchbarDelegate and that the automatic undimming & showing of results table can be triggered by simulating a keypress in the search field! So:
- (void) searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
if ([controller respondsToSelector: #selector(searchBar:textDidChange:)])
[(id<UISearchBarDelegate>)controller searchBar: controller.searchBar textDidChange: #" "];
}
This code is future proof against crashing by checking it responds to the UISearchbarDelegate method, and sends space #" " to trick the UISearchDisplayController into thinking user has typed a letter.
Now if the user types something and then erases it, the table will dim again. The other solutions try to work around this by doing something in the searchDisplayController:didHideSearchResultsTableView: method. But this doesn't make sense to me, as surely when you cancel the search it will need to truly hide your results table and you may need to run code in this case. My solution for this part is to subclass (note you could probably use a Method Swizzled Category to make it work everywhere if needed in your project):
// privately declare protocol to suppress compiler warning
#interface UISearchDisplayController (Super) <UISearchBarDelegate>
#end
// subclass to change behavior
#interface GMSearchDisplayController : UISearchDisplayController
#end
#implementation GMSearchDisplayController
- (void) searchBar: (UISearchBar *) searchBar textDidChange: (NSString *) searchString
{
if (searchString.length == 0)
searchString = #" ";
if ([super respondsToSelector: #selector(searchBar:textDidChange:)])
[super searchBar: searchBar textDidChange: searchString];
}
#end
This code works by intercepting the textDidChange delegate method and changing nil or empty strings in to space string #" " preventing the normal hiding/dimming that occurs on an empty search bar. If you are using this second bit of code, then you could modify the first bit to pass a nil instead of #" " as this second bit will do the needed conversion to #" " for you.
In my own project, I needed to handle the case that user does type a space, so instead of #" " above I used a defined token:
// arbitrary token used internally
#define SEARCH_PRELOAD_CONDITIONAL #"_#preresults#_"
And then handle it internally by converting it back to nil string:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
if ([searchString isEqualToString: SEARCH_PRELOAD_CONDITIONAL])
searchString = nil;
}
Enjoy! :)

This works in iOS 8:
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
self.searchDisplayController.searchResultsTableView.hidden = NO;
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
self.searchDisplayController.searchResultsTableView.hidden = NO;
[self.searchDisplayController.searchResultsTableView.superview.superview bringSubviewToFront:self.searchDisplayController.searchResultsTableView.superview];
CGRect frame = self.searchDisplayController.searchResultsTableView.frame;
self.searchDisplayController.searchResultsTableView.frame = CGRectMake(frame.origin.x, 64, frame.size.width, frame.size.height);
}

When you start searching this method gets called. Add the searchResultsTableView and unhide it. It would then display your already preloaded data. I must have your data preloaded in order for this to work.
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);
self.searchDisplayController.searchResultsTableView.frame = testFrame;
[self.notesSearchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];
// [self.view addSubview:self.searchDisplayController.searchResultsTableView];
controller.searchResultsTableView.hidden = NO;
}
-(void) searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);
self.searchDisplayController.searchResultsTableView.frame = testFrame;
[self.notesSearchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];
// [self.view addSubview:self.searchDisplayController.searchResultsTableView];
controller.searchResultsTableView.hidden = NO;
}
-(void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
controller.searchResultsTableView.hidden = YES;
}

iOS 9 working code.
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
// Bring the search table view to the view's front
self.searchDisplayController.searchResultsTableView.hidden = NO;
[self.searchDisplayController.searchResultsTableView.superview bringSubviewToFront:self.searchDisplayController.searchResultsTableView];
}
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView {
// We need to prevent the resultsTable from hiding if the search is still active
if (self.searchDisplayController.active == YES) {
tableView.hidden = NO;
}
}

Swift 2.0+ version
func searchDisplayControllerDidBeginSearch(controller: UISearchDisplayController) {
controller.searchResultsTableView.hidden = false
controller.searchResultsTableView.superview!.bringSubviewToFront(controller.searchResultsTableView)
}
func searchDisplayController(controller: UISearchDisplayController, didHideSearchResultsTableView tableView: UITableView) {
if ((searchDisplayController?.active) != nil) {
tableView.hidden = false
}
}

Related

NSCollectionView does not scroll items past initial visible rect

I'm running into an issue with an existing app while trying to fix a few outstanding macOS 10.13 bugs. I have a small NSCollectionView that looks similar to the small monthly calendar in the Calendar app. It displays days and scrolls vertically on click. However, on macOS 10.13 the collection view only displays a few rows and does not scroll. I've verified that the data source is being called correctly and it does try to load additional items - but doesn't scroll to them.
I've created a small sample application that also demonstrates the issue. This is a basic macOS app that adds an NSCollectionView via the Main storyboard and has a generic NSCollectionViewItem class loaded from a nib. The entirety of the code in the main view controller is:
- (void)viewDidLoad {
[super viewDidLoad];
NSNib *nib = [[NSNib alloc] initWithNibNamed:#"FooCollectionViewItem" bundle:nil];
[self.collectionView registerNib:nib forItemWithIdentifier:#"foo"];
}
- (void)viewDidAppear {
[super viewDidAppear];
[self.collectionView reloadData];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
}
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 2000;
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
FooCollectionViewItem *item = [collectionView makeItemWithIdentifier:#"foo" forIndexPath:indexPath];
item.fooField.stringValue = [NSString stringWithFormat:#"%ld", indexPath.item];
return item;
}
The resulting app looks like:
Unfortunately, that's the entirely of the collection view. Scrolling doesn't scroll through any additional items. I've uploaded the sample app to https://www.dropbox.com/sh/wp2y7g0suemzcs1/AABVKfTZq54J7riy6BR7Mhxha?dl=0 in case that's useful. Any ideas why this is breaking on 10.13 for me?
I had a similar issue with NSCollectionView scroll running High Sierra. Here's how I solved it:
SWIFT
if #available(OSX 10.13, *) {
if let contentSize = self.collectionView.collectionViewLayout?.collectionViewContentSize {
self.collectionView.setFrameSize(contentSize)
}
}
OBJ-C
if (#available(macOS 10.13, *)) {
[self.collectionView setFrameSize: self.collectionView.collectionViewLayout.collectionViewContentSize];
}
I had a similar problem on macOS 10.14. I had a call to set the selection in -viewDidLoad:
[self.collectionView selectItemsAtIndexPaths:[NSSet setWithCollectionViewIndexPath: indexPath] scrollPosition:NSCollectionViewScrollPositionNone];
which caused the broken layout. I was able to fix the problem by moving this call to -viewWillAppear.
My solution is that if I can show the collectionView ordinarily, I embed the viewController in a window. If I want a popover, I use the following for my ViewController:
override func viewDidAppear() {
self.view.setFrameSize(NSSize(width: self.view.frame.size.width, height: self.view.frame.size.height + 1))
}
You have to change one (or both) values, it does not matter which one.
On my macOS Mojave 10.14.4, using Xcode 10.2, the NSCollectionView would not scroll until I added a custom NSWindowController to my app. I borrowed the following viewDidLoad from the Ray Wenderlich tutorial found here https://www.raywenderlich.com/783-nscollectionview-tutorial. I don't know why it works, but it does.
override func windowDidLoad() {
super.windowDidLoad()
if let window = window, let screen = NSScreen.main {
let screenRect = screen.visibleFrame
window.setFrame(NSRect(x: screenRect.origin.x, y: screenRect.origin.y, width: screenRect.width/2.0, height: screenRect.height), display: true)
}
}
If you created your NSCollectionView programmatically, make sure it's the documentView of an NSScrollView, otherwise, it won't scroll.
let collectionView = NSCollectionView(frame: .zero)
let scrollView = NSScrollView()
scrollView.documentView = collectionView
section.orthogonalScrollingBehavior = .continuous
In your NSCollectionViewLayout, this can only be applied to the section and not the group.

tableView numberOfRowsInSection calls tableView viewForHeaderInSection on iOS4

I got a big app containing a lot of dependencies. For this case I implemented a class called RootTableViewController to handle all the stuff that has to be done everytime a table view controller is required.
Now I discovered an endless loop and I dont know how to fix it. I got the following code in RootTableViewController:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSString *sectionTitle = [self tableView:tableView titleForHeaderInSection:section];
int numbersOfRowInSection = [self.tableView numberOfRowsInSection:section];
if (numbersOfRowInSection > 0)
{
// ...
}
else
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 28.0f)];
return view;
}
}
This works perfect on iOS 5 and iOS 6, but on iOS4 it causes an endless loop, because [tableView numberOfRowsInSection] is calling [tableView viewForHeaderInSection]. How can I fix this using the table view api? Its no solution for me to work with the [ count] of internal data arrays because I got a lot of table view controllers extending this RootTableViewController with different data sources.
This is simply not good style. You are supposed to subclass or rahter implement the related delegate method but you shoudl not call UITableView.numberofRowsInSection:
However, you have certainly implemented tableView:numberOfRowsInSection. Move all of its functionality to the new method myNumberOfRowsInSection: In there do the same. It is mainly a copy of your current numberOfRowsInSection.
Then here in your code sniplet call [self myNumberOfRowsInSection:...];
And within tableView:numberOfRowsInSection:section just do:
return [self myNumberOfRowsInSection:section];
Apply the same pattern to all delegate methods that you may want to call yourself. Move all its business logic into your own method and then only call your own method from the delegate method and from your own code.
If you want to get the number of rows in a section of the data source without accessing the internal data array, you could query the dataSource delegate for it, something like
int numbersOfRowInSection = [self.tableView.dataSource tableView:self.tableView numberOfRowsInSection:section];
(not compiler checked)

How to move around searchResultsTableView

I suppose I want to move them to place that I like best. I can set the frame, but where should I set that?
Also how do I ensure that the table still show up irrelevant of whether search result is empty or not?
This is what I have tried:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
controller.searchResultsTableView.frame=self.placeForTableView.frame;
controller.searchResultsTableView.hidden=false;
controller.searchResultsTableView.alpha =1;
[self debugMode:nil];
return true;
}
Result:
When text is searchResultsTableView is still empty
When there is one character, the frame is not moved yet
When there are more than 2 characters, things work as I wish.
Then I added things like this:
- (void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView
{
[self putTableViewWhereItBelongs:tableView];
}
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
[self putTableViewWhereItBelongs:tableView];
}
-(void)putTableViewWhereItBelongs :(UITableView *) tableView
{
[self.view addSubview:tableView];
tableView.frame=self.placeForTableView.frame;
tableView.hidden=false;
tableView.alpha =1;
}
This works EXACTLY as I wish with one small catch. At the very beginning it doesn't work. At the very beginning the tableView is neither shown or hidden.
Another Update:
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
//UISearchDisplayController * controller = [self displayControllerForThisSearchBar:searchBar];
if (IsEmpty(searchBar.text)) {
searchBar.text=#" ";
searchBar.text=#"";
}
//[self putTableViewWhereItBelongs:controller.searchResultsTableView];
}
Problem:
Awkward
A short glitch. I don't want this to be animated. I don't want my user to even know I have this problem. Well it does look a little animated for less than 1 second
Otherwise the final result is exactly as I please.
I wonder if anyone can improve this.

iPad custom/dynamic layout

I am a newbie to iOS development. I have gone through a couple of tutorials and know the basics, but currently I am stuck on how to proceed further. I am planning to create an app for basic home automation (i.e. switching lights, measuring temperature etc.). The backend is all set, so this is just about the frontend. This is what I am planning to do:
The main view of the app should display a floor plan or the layout of the house
On this floor plan you should be able to add lights/sensors/etc. - lets say objects to keep it generic
These objects should be draggable so that you can arrange them on the floor plan according to where they really are (physically) - ideally this drag mode is toggable similar to rearranging icons on the home screen
Each object should have a popover view (i.e. to set the dimmer intensity, switch lights etc.)
I know there is a lot of work to do, but I don't really know how to set this up. Current alternatives:
Create a custom UIView subclass that contains all the logic an do the drawing in custom code, i.e. the dragging, the popover positioning etc. - but I have the feeling that I wouldn't really be leveraging the iOS framework capabilities
Display the floor plan as an UIImageView and one UIButton for each object. This has the advantage that I can use StoryBoard to do the layouting and wiring (i.e. create segues for popovers etc.) - but I simply can't figure out how to do this with a variable number of buttons (since I don't know in advance how many buttons there will be). Is there some way to create these buttons in code?
Use a custom UITableView. I have seen a couple of examples where they seem to use table views even if the layout has nothing to do with tables (like in my example) but I haven't found any tutorials that explain this concept in more detail
Or am I totally on the wrong track? Any input is appreciated.
Thanks
D.
UPDATE:
After some more research and thought on this I think the way to go with iOS 6 is to use an UICollectionView with a custom layout. Once I have come up with a complete solution I will post it here. For older iOS versions I think it would be promising to go with Option Nr. 2 - i.e. creating each UIButton (for the automation objects e.g. lights) in code and having a custom UIView subclass to do the layouting of these buttons.
Ok I think UICollectionView is ideal for this usage scenario and I am just lucky to have started with iOS programming just as it was introduced to the framework. The following example is a UICollectionView that displays its elements according to their inherent coordinates. This example could also be applied to positioning objects on a map. I couldn't find any examples elsewhere so I'll post the main steps here (since I am a beginner please correct any mistakes).
To start off I created a simple project with one view and storyboard in XCode. I removed the standard view and inserted a Collection View Controller instead and configured my UICollectionViewController subclass as the class that should be used (in the properties of the controller in storyboard).
For the demo just set the background of the default UICollectionViewCell to a color and set the Identifier to "AutomationCell" for this example (if you change it be sure to adjust the code below).
First I create a simple object with some properties that represents an object that should be displayed on the floor plan:
#interface AULYAutomationObject : NSObject
#property NSString *title;
#property CGPoint position;
#end
Then I need my own delegate as subclass to the standard UICollectionViewDelegate since my custom UICollectionViewLayout will not have direct access to the dataSource objects. Therefore I provide a method that will give me the position of the object:
#protocol AULYAutomationObjectLayoutDelegate <UICollectionViewDelegate>
- (CGPoint)getPositionForItemAtIndexPath:(NSIndexPath *)indexPath;
#end
Make sure to implement this protocol in your controller like this:
#interface AULYViewController : UICollectionViewController <AULYAutomationObjectLayoutDelegate>
Then I implemented the standard datasource and delegate methods along with my custom one in the view controller subclass:
#interface AULYViewController ()
#property NSArray *objects;
#property (strong, nonatomic) IBOutlet UICollectionView *collectionView;
#end
#implementation AULYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Set up the data source
NSMutableArray *automationObjects = [[NSMutableArray alloc] initWithCapacity:10];
// add some objects here...
self.objects = [automationObjects copy];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapGesture:)];
[self.collectionView addGestureRecognizer:longPressRecognizer];
}
#pragma mark - UICollectionViewController
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.objects.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
AULYAutomationObjectViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"AutomationCell" forIndexPath:indexPath];
// If you have a custom UICollectionViewCell with a label as outlet
// you could for example then do this:
// AULYAutomationObject *automationObject = self.objects[indexPath.row];
// cell.label.text = automationObject.title;
return cell;
}
#pragma mark - AULYAutomationObjectLayoutDelegate
- (CGPoint)getPositionForItemAtIndexPath:(NSIndexPath *)indexPath
{
AULYAutomationObject *automationObject = self.objects[indexPath.item];
return automationObject.position;
}
In a real project you would probably do some conversion from the object model position to the position on screen (e.g. GPS data to pixels) but here this is left out for simplicity.
After having done that we still need to set up our layout. This has the following properties:
#interface AULYAutomationObjectLayout : UICollectionViewLayout
#property (nonatomic, strong) NSIndexPath *draggedObject;
#property (nonatomic) CGPoint dragPosition;
#end
And the following implementation:
#implementation AULYAutomationObjectLayout
- (void)setDraggedObject:(NSIndexPath *)draggedObject
{
_draggedObject = draggedObject;
[self invalidateLayout];
}
- (void)setDragPosition:(CGPoint)dragPosition
{
_dragPosition = dragPosition;
[self invalidateLayout];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
id viewDelegate = self.collectionView.delegate;
if ([viewDelegate respondsToSelector:#selector(getPositionForItemAtIndexPath:)])
{
CGPoint itemPosition = [viewDelegate getPositionForItemAtIndexPath:indexPath];
layoutAttributes.center = itemPosition;
layoutAttributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
}
if ([self.draggedObject isEqual:indexPath])
{
layoutAttributes.center = self.dragPosition;
layoutAttributes.transform3D = CATransform3DMakeScale(1.5, 1.5, 1.0);
layoutAttributes.zIndex = 1;
}
return layoutAttributes;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *allAttributes = [[NSMutableArray alloc] initWithCapacity:4];
for (NSInteger i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[allAttributes addObject:layoutAttributes];
}
return allAttributes;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (CGSize)collectionViewContentSize
{
return [self.collectionView frame].size;
}
#end
To set the custom layout in the storyboard just go to the properties of the controller view and select custom as the layout type - then select your custom class.
Now to enable drag and drop support with the long press gesture simply add the following to your controller:
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
{
AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout;
if (sender.state == UIGestureRecognizerStateBegan)
{
CGPoint initialPinchPoint = [sender locationInView:self.collectionView];
NSIndexPath* tappedCellPath = [self.collectionView indexPathForItemAtPoint:initialPinchPoint];
[self.collectionView performBatchUpdates:^{
automationLayout.draggedObject = tappedCellPath;
automationLayout.dragPosition = initialPinchPoint;
} completion:nil];
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
automationLayout.dragPosition = [sender locationInView:self.collectionView];
}
else if (sender.state == UIGestureRecognizerStateEnded)
{
AULYAutomationObject *automationObject = self.objects[automationLayout.draggedObject.item];
automationObject.position = [sender locationInView:self.collectionView];
[self.collectionView performBatchUpdates:^{
automationLayout.draggedObject = nil;
automationLayout.dragPosition = CGPointMake(0.0, 0.0);
} completion:nil];
}
}
One important note:(this cost me at least an hour): When using the transform3D you should make sure to import QuartzCore into your linked frameworks (in the project properties below the orientation settings). Otherwise you will get a Mach-O Linker Error saying that _CATransform3DMakeScale can not be found.

Notification when content changes in NSOutlineView subclass

I'd like to subclass NSOutlineView to show a label in the middle of itself when there is no content yet. Much like the inspectors in XCode:
Obviously I can't use the delegate methods, because I'm implementing this as a subclass and I have to be able to set the delegate to something else when using this class.
I didn't find any notifications that I could observe, except changes in the bounds property, but that's not very reliable.
I ended up overriding several methods of NSOutlineView to inject my code. It's not a very elegant solution, but it works. If anyone has a better solution, let me know and I might accept your answer instead.
- (void)updateEmptyLabelVisibility {
int r = [[self dataSource] outlineView:self numberOfChildrenOfItem:nil];
BOOL hasRows = r > 0;
_emptyLabel.hidden = hasRows;
}
- (void)reloadItem:(id)item {
[super reloadItem:item];
[self updateEmptyLabelVisibility];
}
- (void)reloadData {
[super reloadData];
[self updateEmptyLabelVisibility];
}
- (void)awakeFromNib {
[super awakeFromNib];
[self updateEmptyLabelVisibility];
}
- (void)endUpdates {
[super endUpdates];
[self updateEmptyLabelVisibility];
}