Can I use a customized checkmark with UITableView's allowsMultipleSelectionDuringEditing set to YES? - objective-c

A picture's worth a thousand words...
For a bit more background, I have a UITableView leveraging iOS 5's allowsMultipleSelectionDuringEditing set to YES. This results in the empty and filled edit controls being shown on the left of the cell any time the cell is in edit mode. This behavior is exactly what I want. I just want to change the appearance of these check marks.
I know it would be possible to write custom selection logic and basically roll my own version (like this and this), but that's what I want to avoid. The system is already in place, and I want to re-use as much of it as possible.

This is the closest I've come. It's simple and it works, while reusing almost all of the pre-baked system. It's also a giant hack however, and relies on exploiting the undocumented view hierarchy of UITableViewCell after a little runtime introspection.
In a nutshell, this simply hides the view normally responsible for showing the checkmark, allowing me to add my own view that can be shown in its place. I can then manipulate this stand-in view when the cell's selection or editing state changes...
To prevent the standard checkmark from appearing, all that's needed is a custom -layoutSubviews implementation. It's called, per the documentation, after both -willTransitionToState: and -setEditing:animated:, ensuring the state is always valid when either isSelected or isEditing changes.
- (void)layoutSubviews
{
[super layoutSubviews];
// Find the offending view, and quietly bury it...
for (UIView* subview in [self subviews])
{
// As determined by NSLogging every subview's class, and guessing which was the one I wanted
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellEditControl"])
{
[subview setHidden:YES];
}
}
if ([self isEditing])
{
// Show the custom view however you want.
// The value of [self isSelected] will be useful...
}
else
{
// Hide the custom view.
}
}
I would still welcome a solution that's a bit more... kosher.

Related

iOS7 Keyboard Behavior with SearchBar/UITableView: Offset on secondary view appearances

Problem
I am having a rather big issue with the iOS7 keyboard appearance. I have a Searchbar on a UIViewController with TableView Delegation/Data Source setup (I am using the self.searchDisplayController delegates as well). I segue from this scene to a prototype tableview to show the results.
Here is the issue:
On first load I can see the keyboard being displayed when I tap into the text field of the UISearchBar. I can type and perform a search with the results being shown in the next scene.
I've added NSNotifications to view the keyboard properties in local methods keyboardWillShow and keyboardWasShown. I can see on the first scene appearance (after the view is completely loaded):
I segue to the result tableview at this point and when I navigate back and touch the text field, my keyboard shows up either fully or partially off-screen:
When I look at the keyboardWillShow notification at this point I can see that my keyboard values are incorrect:
I've researched and tried many possibilities including:
Added the following to my main view controller:
-(BOOL)canResignFirstResponder
{
return YES;
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
Configured the following in my view did load
self.searchDisplayController.searchBar.spellCheckingType = UITextSpellCheckingTypeNo;
self.searchDisplayController.searchBar.autocapitalizationType= UITextAutocapitalizationTypeNone;
self.searchDisplayController.searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
self.searchDisplayController.searchBar.keyboardType = UIKeyboardTypeDefault;
Put in standard stubs for:
-(void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
I've noticed that if I choose a Partial Curl as my segue mode, the keyboard remains accessible when I roll back to the main view controller (but then it was never fully off screen in that case). However if I move from the results tableview to a detail scene and then navigate back to the main view controller, the keyboard appears off-screen again.
Question
Is there a method I can use to intercept the misplaced keyboard so that it displays in the default location?
NB: Along these lines, I have created a NSDictionary property to hold the initial userInfo values with the correct keyboard placement. I am not sure how to reassign these values to get the keyboard to return to it's original placement.
BTW - This seems a bit of a hack to get the keyboard fixed due to a bug in IB, is there some other way that I can try to remedy the situation?
Thanks in advance for any feedback!
Solution
This was such an obscure issue that I'm sharing the solution to save the next person some effort. Like most programming issues, it turns out this one was self-inflicted. In my original iteration of this project I had turned off rotational support as I am learning auto-layout and I wanted to ease into the transition from Springs and Struts. Somehow between the start of the project and the code release I ended up with this bit of code in the Main Scenes' View Controller.
//BAD
- (NSUInteger) supportedInterfaceOrientations
{
return !UIInterfaceOrientationPortraitUpsideDown;
}
instead of returning a valid enumeration like...
//OK
- (NSUInteger) supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}

How can I disable the touch events on two UIImageViews after i drag them into each other?

I have two draggable UIImageViews within a UIView. When i drag one UIImageView over the other, i would like to disable touch events to those two UIImageViews and create another UIImageView which does respond to my touch events. Ive tried using setUserInteractionEnabled: but it isnt really doing anything at all. Forgive me if this is a no brainer but i am new to programming, here is what i have so far. Please give me some feedback on my code and give me some constructive criticism because i feel as though i am setting this up all wrong.
-(void)swapImageViews
{
if ((self.imgView1.center.x == self.imgView2.center.x) &&
(self.imgView1.center.y == self.imgView2.center.y)) {
[self addSubview:self.imgView3];
self.imgView3.center = CGPointMake(self.imgView1.center.x, self.imgView1.center.y);
[self.imgView1 removeFromSuperView];
[self.imgView2 removeFromSuperView];
[self.imgView1 setUserInteractionEnabled:NO];
[self.imgView2 setUserInteractionEnabled:NO];
}
}
So once again, the goal is to swap out two image views with a fresh one that i can also drag around.
The problem i am running into is that my touch events are still moving the first two image views around behind the third one.
You say that the third image appeared, but the the other two are still in the view and they still receive touch events. There are two two scenarios for this:
You've already added the third image view somewhere else, and your method is never called, or the condition is never met.
self.imgView1 and self.imgView2 are not set to the actual objects in your view stack. They're probably just nil. you can debug that by NSLog(#"%# %#", self.imgView1, self.imgView2); in that method, before the condition, or in viewWillAppear or viewDidLoad.
The reasons that support my opinion:
In that method, you call removeFromSuperview to the two image views you want to disable, yet they are still visible and receiving touch events. That means that this method is not executed. Which means they're nil or some other objects, that are different than the visible ones.
The condition in the start of the method is a bit strict. It's hard to be easily met. Yet the method is executed as the third image view is added to the view. This can happen when the object is nil, so the values returned by methods are the default values.
Check this link "the 10th item" and this one too.
Well this is not the best way to do that, but with the code that you provide to us, we have not a lot of options, you can do something like that, to remove the gestures:
-(void)swapImageViews{
if ((self.imgView1.center.x == self.imgView2.center.x) &&
(self.imgView1.center.y == self.imgView2.center.y)) {
[self addSubview:self.imgView3];
self.imgView3.center = CGPointMake(self.imgView1.center.x, self.imgView1.center.y);
[self.imgView1 removeFromSuperView];
[self.imgView2 removeFromSuperView];
NSArray* gestures1 = [self.imgView1.gestureRecognizers copy];
for(UIGestureRecognizer *gesture in gestures1){
[self.imgView1 removeGestureRecognizer:gesture];
}
NSArray* gestures2 = [self.imgView2.gestureRecognizers copy];
for(UIGestureRecognizer *gesture in gestures2){
[self.imgView2 removeGestureRecognizer:gesture];
}
}
}

How do I know if a view is visible or not?

Say I have two view controllers: xVC and yVC. I have used the shake API and and have used the methods -(void)motionBegan,-(void)motionEnded: and -(void)motionCancelled in xVC. What happens is when the device is shaken, it fires a simple animation. Now the thing is that this animation is fired even when the I have yVC open that is, when yVS.view has been added as the subview. What I am looking for is some if condition which I can use in -(void)motionEnded: like this:
if(yVC == nil)
{
//trigger animation
}
By that I mean that the shake shouldn't work when yVC is visible. How do I do that? Please help.
The general advice I have seen and used is to ask a view if it has a non-nil window property:
if( ! yVC.view.window) {
// trigger animation
}
But note that this doesn't always equate with being visible; though in most apps it's about as good as you can performantly get (the basic case where it's not accurate is when a different view completely obscures it, but this may still satisfy your needs)
Add this to both of your view controllers:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
visible = YES;
}
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
visible = NO;
}
Now, just check the variable isVisible of both the view controllers and trigger your animation likewise.
The previous answers all work to some degree, but fail to take modally presented view controllers into account. If view controller A presents view controller B, of the previous answers will tell you that A is still visible. If you, like me, want to know whether or not the view is actually visible (and not just a part of the view hierarchy), I would suggest also checking the presentedViewController property:
if (self.isViewLoaded && [self.view window] && !self.presentedViewController) {
// User is looking at this view and nothing else
}
This works since presentedViewController will be non-nil whenever the current view controller OR any of its ancestors are currently presenting another view controller.

Proper way to determine if NSView is drawn

Is there a proper way to determine if a NSView is actually drawn in the current view hierarchy or not, considering cases like:
The view is completely offscreen (not mandatory)
The view is not on top of the view hierarchy
The -isHidden and -isHiddenOrHasHiddenAncestor are unfortunately not set when e.g. a view disappears because a tab view switches to another tab.
The reason for this is that I have an attached child window and I would like to be able to hide it as well when the view that it is attached to is not drawn.
I have found a trick to tell if it is visible, but it requires subclassing. It works by toggling an ivar on 2 events.
- (void)discardCursorRects {
isDrawn_ = NO;
[super discardCursorRects];
}
- (void)resetCursorRects {
isDrawn_ = YES;
[super resetCursorRects];
}
Whether (or when) it's drawn is supposed to be "none of your business" and really have nothing to do with whether or not it's on-screen. Use NSView's -viewDidMoveToSuperview or -viewDidMoveToWindow to manage this.

NSTableView selection & highlights

I have a NSTableView as a very central part of my Application and want it to integrate more with the rest of it. It has only one column (it's a list) and I draw all Cells (normal NSTextFieldCells) myself.
The first problem is the highlighting. I draw the highlight myself and want to get rid of the blue background. I now fill the whole cell with the original background color to hide the blue background, but this looks bad when dragging the cell around. I tried overriding highlight:withFrame:inView: and highlightColorWithFrame:inView: of NSCell but nothing happened. How can I disable automatic highlighting?
I also want all rows/cells to be deselected when I click somewhere outside my NSTableView. Since the background / highlight of the selected cell turns gray there must be an event for this, but I can't find it. I let my cells expand on a double click and may need to undo this. So getting rid of the gray highlight is not enough.
EDIT: I add a subview to the NSTableView when a cell gets double clicked and then resignFirstResponder of the NSTableView gets called. I tried this:
- (BOOL)resignFirstResponder
{
if (![[self subviews] containsObject:[[self window] firstResponder]])
{
[self deselectAll:self];
...
}
return YES;
}
Besides that it's not working I would need to implement this method for all objects in the view hierarchy. Is there an other solution to find out when the first responder leaves a certain view hierarchy?
I wanted to achieve a similar solution (with an NSOutlineView but this has no difference): when clicking inside the outline view BUT not in a row with a cell (for instance at the empty bottom of a source list), I wanted the currently selected row to be deselected. I ended up with this little piece of code that might be of some help.
In a NSOutlineView subclass, I've put:
#implementation ClickOutlineView
- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint pointInWindow = [theEvent locationInWindow];
NSPoint pointInOutlineView = [self convertPoint:pointInWindow toView:nil];
int rowIndex = [self rowAtPoint:pointInOutlineView];
if ([theEvent clickCount] == 1 && rowIndex == -1) {
[self deselectAll:nil];
}
else {
[super mouseDown:theEvent];
}
}
#end
No doubt it's too late to help the question's poster, but this may help others who come across this on Google (as I did).
You can prevent row selection by selecting 'None' in the 'Highlight' drop-down menu, in the attributes inspector in Xcode 4.
There is also an answer on SO here for setting this programmatically.
Overloading -highlight:withFrame:inView: should be correct. How did you do the overload? Does an NSLog() statement indicate that your code is running? You should also look at NSTableView's -highlightSelectionInClipRect:, which may be more convenient for this.
In order to do something (such as unselect the current selection) when the user clicks outside the table view, overload -resignFirstResponder on NSTableView.