Forwarding drag & drop event to parent view - objective-c

I have an application where I have one custom view which is derived from NSView.
Within this view, there are several custom subviews, which are also derived from NSView.
I want to implement a drag and drop behavior which allows URLs to be dropped onto the views. Everything is already working for the main view.
So, actually I would have to implement dragging behavior handlers on the child-views and the parent-view class.
The thing is, that I don't want to copy the complete handling code to all the child-views to make them also accept drag events. So I thought that it would be the best way to just let them forward all drag events to the parent view.
Is this possible somehow?? Not sure if I can somehow set this up with the responder-chain maybe?
Any tips are highly appreciated!!
Thanks in advance.

I faced a similar issue where I wanted anything dropped in a view, or any of it's subviews to be processed by the view, but the calls never got there.
After some research I found this answer to be the most helpful: https://stackoverflow.com/a/7389711/327471
Essentially if a view is not registered to receive any drag events it should pass that information up to it's parent view automatically. So in my case I ended up with something like this:
NSArray *subviews = [self.view subviews];
for (NSView *aSubview in subviews) {
[aSubview unregisterDraggedTypes];
}
Of course you can be more precise than that, and make sure to only check subclasses of a certain type or whatever parameters you want. But ultimately the key was unregistering the problem subview from it's dragged types.
I hope this helps.

If the subviews are used for display only and don't require any user interaction, you can override -hitTest: in the parent view like so:
- (NSView *)hitTest:(NSPoint)aPoint
{
NSView* hitView = [super hitTest:aPoint];
if(hitView)
return self;
return nil;
}
This makes the parent view receive all mouse events.

Still works XCode 10.1. Swift 4.2. under 10.14.4 Beta (18E184e).
// MARK: - ViewController lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.view.subviews.forEach { $0.unregisterDraggedTypes() }
}

There's probably a better way, but you could put your dragging protocol implementation in a category, rather than in the view directly, and include that category in each of the views.

Related

tvOS - Focus particular item in a Table View

In tvOS development - By the default the focus of the table view is the 1st item. But, how to focus particular item by default in a Table View.
For Example, By default, I want to focus the nth row of the table view.
Thanks for your help in advance.
To control the focus engine in general you have to override the methods provided by UIFocusEnvironment and/or the related UITableViewDelegate and UICollectionViewDelegate methods. This behaves much differently than controlling something like the selected row/item, in that it's a global system and not specific to a particular view.
In your particular case, it may be sufficient to implement the indexPathForPreferredFocusedViewInTableView(_:) method that is available on UITableViewDelegate (note that there is an equivalent method for UICollectionViewDelegate).
One problem I ran into while attempting to do something similar was how the default chain of preferred focus views works. Eventually I noticed this part of the documentation for UIFocusEnvironment (which all views and view controllers conform to):
By default, UIView returns itself and UIViewController returns its
root view. Returning self in a focusable view indicates that view
should be focused. Returning self in an unfocusable view causes the
focus engine to pick a default preferred focused view, by finding the
closest focusable subview to the top-leading corner of the screen.
Returning nil indicates that there is no preferred focused view.
In my case this meant that the focus engine was picking a default preferred focus view and the focus related delegate methods for my UICollectionView were not being called as I expected. If you find this is the case you may need to implement preferredFocusedView (defined in the UIFocusEnvironment protocol) on your view controller subclass and return your instance of UITableView so that the delegate methods related to focus get invoked reliably.
With TVML, it's the attribute autoHighlight="true"
(https://developer.apple.com/library/tvos/documentation/LanguagesUtilities/Conceptual/ATV_Template_Guide/TVJSAttributes.html)
Doesn't work with all templates, though, there still seem to be some bugs in the system.
First thing first,
During the view Load, set:
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [myTableView]
}
To focus to a particular IndexPath,
func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? {
if focusAtIndexPathRow != -1 {
return IndexPath(item: focusAtIndexPathRow, section: 0)
} else {
return IndexPath(item: 0, section: 0)
}
}

IOS Accessing one view from another view

Basically I have a view controller having two subviews. I want these views to be connected. A touch event should trigger an event from another view and vice versa. I have thought about two solutions.
1-) Accessing views through their view controllers
2-) Each view has a pointer to another view
I am a newbie on IOS and as far I read from other problems it is mentioned that accessing view controller from a view is not suggested. So, what do you guys suggest me to do?
Edit:
I didn't make much progress on coding but my first view is:
#interface PaintView : UIView
-(id)initWithFrame:(CGRect)frame andController:(ViewController*)ctrl;
and i will control the touch event and access my viewcontroller:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//[self.controller somethingThatAccessToOtherView]
}
and second view will be very similar to that one.
Why dont you use the delegate pattern!
For the two views communicate with each other ..
Its essentially similar to " Each view has a pointer to another view" but in a much more flexible manner
here is a so thread on it and
If you want the simplest, dirtiest, and non recommended way of proceeding. Tag your two views with some sort of unique tag (use say, -20 and -21, or something. So in the view controller where you create the views you do the following.
[v1 setTag:-20];
[v2 setTag:-21];
Then you can do from in say, v2, the following.
[self.superview viewWithTag:-20];
to get a reference to v1. However, this assumes the same superview, and is not a nice way of doing things unless you are sure that the view heirachy will not change (I'm talking, it's a widget you made that no one else is going to touch and you've documented it well anyway).
A better way would be to use a delegate pattern. The ViewController is the delegate of each of the subviews. On touch, the subviews call some method like
[delegate iwastouched:self];
and the delegate has a method like
-(void) iwastouched:(UIView *) someview {
if(someview == v1){
//view 1 was touched, do something to view two
}
if(someview == v2){
//view 2 was touched, do something to view one
}
}
Another bad way of doing it would be to use notifications. Hell, there are about as many ways to do this as you could like. Some are just not as nice.
Instead of doing this, in the subclass of UIViewController, you should have two members. These member will represent each of the subview.
Now, inside the subclass of UIViewController, add the methods of touch which are of your interest. Inside this methods, identify the view on which any touch event is generated.
Depending on it, pass the event to other view.
You should not have UIViewController as a iVar of your UIView subclass.

Get UIViewController to reload different UIView programmatically

I am overriding the loadView method within a UIViewController as follows:
-(void)loadView
{
NSLog(#"HPSFormController loadView starting");
HPSFormView* viewForThisController = [ [ HPSFormView alloc ] initWithFrame:_frame ] ;
self.view = viewForThisController;
}
When a certain button is pressed within the view then the same UIViewController gets control again and at this point I wish to completely change the view that the controller is showing. As follows:
-(void)buttonTapped
{
ABCFormView* newview = [ [ ABCFormView alloc ] initWithFrame:_frame ] ;
self.view = newview;
}
However, the buttonTapped method does not load the second view. A completely blank view is shown instead.
How do I get the view controller to reload a completely different view when the button is pressed?
However, the buttonTapped method does not load the second view. A completely blank view is shown instead.
Is it not possible that the problem is in the way you create ABCFormView? I mean, it seems that the original view is replaced by an empty view, so check how the latter is created...
EDIT AFTER YOUR COMMENT:
if you say that the view is "created within a viewDidLoad method within the view controller", then you should instantiate your view controller:
#property (...) ABCFormViewController* newviewController;
....
-(void)buttonTapped
{
self.newviewController = [ [ ABCFormViewController alloc ] init] ;
self.view = newviewController.view;
}
keep in mind that newviewController must be around as long as you are using its controlled view, otherwise you will get a crash. That is the reason why I store its reference in a property.
Obviously you can't get your new view visible by simply setting self.view = newView; because the newView has never been added as a subview to any other views yet - i.e. not in the window.
If you need to switch to a different view, you should probably add APSFormView as a subview to your viewcontroller.view, and when you need to switch, remove APSFormView from superview then add ABCFormView as a new subview to viewcontroller.view.
If your loadView implementation needn't do much else, it may be better to use the storyboard to set it up initially. It is easy to miss, but you can specify in the storyboard that the view should be of a custom type (in the "identity inspector" with the view selected). Further, it may be worth evaluating why a completely different class of view is necessary for the same view controller instance; to me this may be a red flag regarding the application design. You may be better served by a flow between two view controllers, or else write some state-changing logic in this custom UIView-extending class. The decision for me would be made based on the model being represented by the views, along with which behaviors each is designed to facilitate.
If the models are different (i.e. your first view shows a list of accounts, second shows one account detail), or if the behaviors are significantly different (i.e. the first is viewing an account and the second is creating a new one), then I would use two distinct view controllers.
If the models and behaviors are similar, and the style should change, then I would likely write state-changing code in the custom view class to rearrange things, etc.
If you are coming from a different platform, it can seem silly at first, but we really do throw around view controllers without much hesitation. They are elegantly handled by the framework, and are designed to manage "a screenful of content" and be easily swapped for another screenful.
It is hard to tell without knowing what is inside ABCFormViewController. I had some timing issues once on a view controller which I just needed to generate the view because I wanted to capture its content to create a pdf file (its a view that never gets displayed onscreen). In that case I needed to insert a code like this:
[newviewcontroller.view setneedsrefresh];
Before I do
otherVC.view = newviewcontroller.view;
Otherwise I get a blank page.
I believe I get this because by the time everything is sorted out ARC deallocates newviewcontroller so the view is nil. In your case this may not be the problem. Is there a reason why you need a 2nd and 3rd view controller to put into your view because a much simpler way of doing this is to just transfer control to the other view controllers via modal, pop view or a navigationController. Another more usual way is to create multiple views in your XIB and then just load it into a blank view instead of creating view controllers for each of them.

NSTableView Drag and Drop not working

I'm trying to set up very basic drag and drop for my NSTableView. The table view has a single column (with a custom cell). The column is bound to an NSArrayController, and the array controller's content array is bound to an NSArray on my controller object. The data displays fine in the table. I connected the dataSource and delegate outlets of the table view to my controller object, and then implemented these methods:
- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
{
NSLog(#"dragging");
return YES;
}
- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)op
{
return NSDragOperationEvery;
}
- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
return YES;
}
I also registered the drag types in -awakeFromNib:
#define MyDragType #"MyDragType"
- (void)awakeFromNib
{
[super awakeFromNib];
[_myTable registerForDraggedTypes:[NSArray arrayWithObjects:MyDragType, nil]];
}
The problem is that the -tableView:writeRowsWithIndexes:toPasteboard: method is never called. I've looked at a bunch of examples and I can't figure out anything I'm doing wrong. Could the problem be that I'm using a custom cell? Is there something I'm supposed to override in the cell subclass to enable this functionality?
EDIT: Confirmed. Switching the custom cell for a regular NSTextFieldCell made dragging work. Now, how do I make drag and drop work with my custom cell?
I was banging my head against the wall in search of a more elegant solution to the same problem, and then I came across this:
http://www.wooji-juice.com/blog/cocoa-10-bindings.html
For instance, if you want to support drag-and-drop from a table, you need to set up a data source for it — even if you’re using bindings to supply the actual data, you can set a data source on it, and Cocoa needs one to handle the tableView:writeRowsWithIndexes:toPasteboard and related messages.
Yep. If you've got everything bound to your array controller, what you can do is to have the array controller implement the necessary drag/drop functions and then set the table view's data source to the array controller with setDataSource:.
I fixed the issue. There seems to be an issue with using bindings with custom NSCells in a table view. Switching to the traditional NSTableViewDataSource methods rather than bindings and an array controller solved it.
What works for me is to call initTextCell rather than init or initImageCell within the initializer of my custom cell (in my case, init). It doesn't seem to matter whether the superclass is NSCell or NSActionCell. Also, I have binding, and dragging still works.
That should be sufficient to allow the drag to start. Are you sure you've connected the delegate methods?
I ran into this problem, your custom cell needs to extend NSActionCell not NSCell if you want drag and drop to work properly. There is probably something you could implement in NSCell that would make it all work too, but I didn't dig any further after switching to NSActionCell. At least, that fixed the issue for me.
I ran into the problem. I have a NSCell subclass, and I did implement the tableView:writeRowsWithIndexes:toPasteboard and the dataSource was set for the NSTableView. Dragging would not work.
If I set the cell type in my init for the custom subclass
self.type = NSTextCellType;
Then I get dragging. If I don't, it defaults to NSNullCellType and dragging doesn't work. I'm guessing the people who got it working by using another subclass NSTextFieldCell works because the cell type is different.
I also observe the similar issue, NStableView drag & drop is not working. I have 4 column in my tableview and two of them are custom cells. Dragging for non-custom cell is working fine even though its working on cell separator as well however its not working with custom cells.
My custom cell was subclassed from NSButtonCell which was causing the
issue. So as suggested, I changed my parent class from NSButtonCell to
NSActionCell. Now, dragging is working perfectly.
It works with both NSCell as well as NSActionCell however I required action on my cell so used NSActionCell.

How to use NSCollectionView and Outlets properly?

I'm desperately trying to connect controls of NSViews which will reside in a NSCollectionView using outlets. The collection view is fed using an NSArrayController.
I created the NSView in a separate NIB file and in the implementation of NSCollectionViewItem I overwrote copyWithZone to load it:
-(id)copyWithZone:(NSZone *)zone
{
id result = [super copyWithZone:zone];
[NSBundle loadNibNamed:#"InputView" owner:result];
return result;
}
I've used this approach according to this instructions.
Unfortunately this is what happening:
The NSView looks like this:
The NSCollectionView resides in a NSScrollView and the scrollbar is set to enable automatically.
But as you can see there's no scrollbar.
I don't really understand what I need to do so the NSCollectionView knows the dimensions of its NSViews.
It has worked before when I didn't have a seperate NIB-file, but then I couldn't make outlet connections from the view to the item :-(
How many item are in the array controller? Your output looks correct for what you've described, assuming there are at least 14 things in the controller (1 view per item). The sizing is just off. It's not clear which problem you're trying to solve.
Perhaps you were looking for a grid, and so need to call setMaximumNumberOfColumns:? Or perhaps your views aren't being resized as you expect (check -maxItemSize and -minItemSize)?