I have issue with one segue between two ViewController's, when I try to log the segue.destinationViewController.scrollView it's NIL, in my case that is problem, because I need that scrollView to be available at this point. -prepareForSegue:sender.
The first code block is basic representation of my -prepareForSegue:sender: method. Second code block is the approach which i discovered to do what i want, but not in way which i want.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
ViewController *viewController = (ViewController *)[segue destinationViewController];
viewController.questions = selectedIndexArray;
NSLog(#"scrollView: %#", [viewController scrollView]); // this is logged as nil
}
-
However if I log the scrollView property from -setQuestions: method in the ViewController it's again NIL, until i do delayed execution:
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"scrollView: %#", _scrollView); // returns object address and description
});
I don't like the dispatch_after approach, because that delay of 100 ms is noticeable and I will prefer to skip it.
So how to have scrollView property available in the beginning , back there in prepareForSegue:sender: ?
I just move my code for this example:
NSLog(#"scrollView: %#", _scrollView);
to -viewDidLoad method in ViewController
Related
I am trying to customize an NSImageCell for NSTableView using NSArrayController and bindings to change the background of the cell which is selected. So, I created two NSImage images and retain them as normalImage and activeImage in the cell instance, which means I should release these two images when the cell calls its dealloc method. And I override
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
and
- (void) setObjectValue:(id) inObject
But I find that when I click any cell in the tableview, the cell's dealloc method is called.
So I put NSLog(#"%#", self); in the dealloc method and - (void)drawInteriorWithFrame:inView: and I find that these two instance are not same.
Can anyone tell me why dealloc is called every time I click any cell? Why are these two instances not the same? What does OS X do when I customize the cell in NSTableView?
BTW: I found that the -init is called only once. Why?
EDIT:
My cell code
#implementation SETableCell {
NSImage *_bgNormal;
NSImage *_bgActive;
NSString *_currentString;
}
- (id)init {
if (self = [super init]) {
NSLog(#"setup: %#", self);
_bgNormal = [[NSImage imageNamed:#"bg_normal"] retain];
_bgActive = [[NSImage imageNamed:#"bg_active"] retain];
}
return self;
}
- (void)dealloc {
// [_bgActive release]; _bgActive = nil;
// [_bgNormal release]; _bgNormal = nil;
// [_currentString release]; _currentString = nil;
NSLog(#"dealloc: %#", self);
[super dealloc];
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSLog(#"draw: %#", self);
NSPoint point = cellFrame.origin;
NSImage *bgImg = self.isHighlighted ? _bgActive : _bgNormal;
[bgImg drawAtPoint:p fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
NSPoint strPoint = cellFrame.origin;
strPoint.x += 30;
strPoint.y += 30;
[_currentString drawAtPoint:strPoint withAttributes:nil];
}
- (void) setObjectValue:(id) inObject {
if (inObject != nil && ![inObject isEqualTo:_currentString]) {
[self setCurrentInfo:inObject];
}
}
- (void)setCurrentInfo:(NSString *)info {
if (_currentString != info) {
[_currentString release];
_currentString = [info copy];
}
}
#end
As a normal recommendation, you should move to ARC as it takes cares of most of the memory management tasks that you do manually, like retain, releases. My answers will assume that you are using manual memory management:
Can anyone tell me why dealloc is called every time I click any cell ?
The only way for this to happen, is if you are releasing or auto-releasing your cell. If you are re-using cells, they shouldn't be deallocated.
Why these tow instance are not same ?
If you are re-using them, the cell that you clicked, and the cell that has been deallocated, they should be different. Pay close attention to both your questions, in one you assume that you are releasing the same cell when you click on it, on the second you are seeing that they are different.
What does Apple do when I custom the cell in NSTableView ?
Apple as a company? Or Apple as in the native frameworks you are using? I am assuming you are going for the second one: a custom cell is just a subclass of something that the NSTableView is expecting, it should behave the same as a normal one plus your custom implementation.
BTW: I found that the init is called only once, and why ?
Based on this, you are probably re-using cells, and only in the beginning they are actually being initialised.
It would be very useful to see some parts of your code:
Your Cell's code
Your NSTableView cell's creation code.
I have sub-classed uiscrollview and used initWithCoder to prepare my uiscrollview with all the subviews. I have used below code to set it up:
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
NSLog(#"%f", self.frame.size.height);
self.contentSize = CGSizeMake(5000, self.frame.size.height);
labelContainerView = [[UIView alloc] init];
labelContainerView.frame = CGRectMake(0, 0, self.contentSize.width, self.contentSize.height/2);
labelContainerView.backgroundColor = [UIColor yellowColor];
[self addSubview:labelContainerView];
}
But this keeps failing because the self.frame property always return 0 values. Its super important for me to use self.frame property because I want to use storyboard to do any height adjustments for the frame of the scrollview.
I have tried the same thing using xib files instead of using the storyboard and it works fine. But in my project I dont want to use xib files.
It'll be really great if anyone can explain me why I get 0 values in initWithCoder: method when I use storyboard? and if using storyboard how to achieve this?
PS: I notice layoutSubviews method return correct frame information but I cannot create my subviews here since it get called for each frame change (when I scroll)
Are you using Autolayout in your Storyboard?
If you are really not taking advantage of it, you can disable it and your -initWithCoder: method will return your frame again.
If you do need Autolayout, try the following:
-(void)awakeFromNib
{
[super awakeFromNib];
[self setNeedsLayout];
[self layoutIfNeeded];
// self.frame will now return something :)
}
I have been working on this for hours, have no idea what went wrong. I want a custom cursor for a button which is a subview of NSTextView, I add a tracking area and send the cursorUpdate message when mouse entered button.
The cursorUpdate method is indeed called every time the mouse entered the tracking area. But the cursor stays the IBeamCursor.
Any ideas?
Reference of the Apple Docs: managing cursor-update event
- (void)cursorUpdate:(NSEvent *)event {
[[NSCursor arrowCursor] set];
}
- (void)myAddTrackingArea {
[self myRemoveTrackingArea];
NSTrackingAreaOptions trackingOptions = NSTrackingCursorUpdate | NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
_trackingArea = [[NSTrackingArea alloc] initWithRect: [self bounds] options: trackingOptions owner: self userInfo: nil];
[self addTrackingArea: _trackingArea];
}
- (void)myRemoveTrackingArea {
if (_trackingArea)
{
[self removeTrackingArea: _trackingArea];
_trackingArea = nil;
}
}
I ran into the same problem.
The issue is, that NSTextView updates its cursor every time it receives a mouseMoved: event. The event is triggered by a self updating NSTrackingArea of the NSTextView, which always tracks the visible part of the NSTextView inside the NSScrollView. So there are maybe 2 solutions I can think of.
Override updateTrackingAreas remove the tracking area that is provided by Cocoa and make sure you always create a new one instead that excludes the button. (I would not do this!)
Override mouseMoved: and make sure it doesn't call super when the cursor is over the button.
- (void)mouseMoved:(NSEvent *)theEvent {
NSPoint windowPt = [theEvent locationInWindow];
NSPoint superViewPt = [[self superview]
convertPoint: windowPt fromView: nil];
if ([self hitTest: superViewPt] == self) {
[super mouseMoved:theEvent];
}
}
I had the same issue but using a simple NSView subclass that was a child of the window's contentView and did not reside within an NScrollView.
The documentation for the cursorUpdate flag of NSTrackingArea makes it sound like you only need to handle the mouse entering the tracking area rect. However, I had to manually check the mouse location as the cursorUpdate(event:) method is called both when the mouse enters the tracking area's rect and when it leaves the tracking rect. So if the cursorUpdate(event:) implementation only sets the cursor without checking whether it lies within the tracking area rect, it is set both when it enters and leaves the rect.
The documentation for cursorUpdate(event:) states:
Override this method to set the cursor image. The default
implementation uses cursor rectangles, if cursor rectangles are
currently valid. If they are not, it calls super to send the message
up the responder chain.
If the responder implements this method, but decides not to handle a
particular event, it should invoke the superclass implementation of
this method.
override func cursorUpdate(with event: NSEvent) {
// Convert mouse location to the view coordinates
let mouseLocation = convert(event.locationInWindow, from: nil)
// Check if the mouse location lies within the rect being tracked
if trackingRect.contains(mouseLocation) {
// Set the custom cursor
NSCursor.openHand.set()
} else {
// Reset the cursor
super.cursorUpdate(with: event)
}
}
I just ran across this through a Google search, so I thought I'd post my solution.
Subclass the NSTextView/NSTextField.
Follow the steps in the docs to create an NSTrackingArea. Should look something like the following. Put this code in the subclass's init method (also add the updateTrackingAreas method):
NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:(NSTrackingMouseMoved | NSTrackingActiveInKeyWindow) owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
self.trackingArea = trackingArea;
Now you need to add the mouseMoved: method to the subclass:
- (void)mouseMoved:(NSEvent *)theEvent {
NSPoint point = [self convertPoint:theEvent.locationInWindow fromView:nil];
if (NSPointInRect(point, self.popUpButton.frame)) {
[[NSCursor arrowCursor] set];
} else {
[[NSCursor IBeamCursor] set];
}
}
Note: the self.popUpButton is the button that is a subview of the NSTextView/NSTextField.
That's it! Not too hard it ends up--just had to used mouseMoved: instead of cursorUpdate:. Took me a few hours to figure this out, hopefully someone can use it.
Updated:
I have subclassed UIImageView to make some images movable through touch gestures. In the view controller I have an array holding the initial position of each imageview. My problem is that this array returns null whenever it is called on from the subclass. The idea is to check if the image is in its original position or if it has already been moved before, I have stripped the code to just NSLog what's going on but the problem remains and is driving me nuts.
ViewController.h
NSMutableArray *originalPositions;
#property (nonatomic, retain) NSMutableArray *originalPositions;
-(void)testLogging;
ViewController.m
#synthesize originalPositions;
- (void)viewDidLoad {
originalPositions = [[NSMutableArray alloc]init];
}
-(void)drawImages {
for (int i = 0; i < imagesArray.count; i++) {
CGRect frame = CGRectMake(65 * i, 10, 60, 60);
draggableView *dragImage = [[draggableView alloc] initWithFrame:frame];
NSString* imgName = [imagesArray objectAtIndex:i];
[dragImage setImage:[UIImage imageNamed:imgName]];
[dragImage setUserInteractionEnabled:YES];
[self.view addSubview:dragImage];
NSString *originalPositionAsString = NSStringFromCGPoint(dragImage.center);
[originalPositions addObject:originalPositionAsString];
}
}
-(void)testLogging {
NSLog(#"Logging array: %#", originalPositions);
}
-(IBAction)btnClicked {
[self testLogging];
}
When called from the IBAction (or any other way) in the class, the 'testLogging'-method NSLogs the array correctly but if I call the same method from the subclass, it NSLogs null:
Subclass.m
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
ViewController *viewController = [[ViewController alloc] init];
[viewController testLogging];
}
self.originalPositions = [[NSMutableArray alloc]init];
This already is a memory leak, when you call self.originalPositions = bla then 'bla' will be retained. Thus meaning that the retain count is upped. This means that your mutable array will have a retain count of 2 and that it will leak once your UIImageView is gone.
As for the rest, I wouldn't know what is wrong with your code. My first guess would be that you didn't call populatePositionsArray yet, you should call this whenever your view is created / shown so you are sure the array is populated when touchesBegan is called.
Alternatively you could include an 'if' statement in the touchesBegan that checks whether the array exists and otherwise call the populatePositionsArray to populate it before continueing.
I solved it by writing the array to a plist file after populating it. Not very elegant but working.
Thanks for the help though guys.
I want to implement this
1) when user start typing in a textfield a popOver flashes and shows the list of items in a table view in the popover as per the string entered in textfield.
2) Moreover this data should be refreshed with every new letter entered.
kind of predictive search.
Please help me with this and suggest possible ways to implement this.
UISearchDisplayController does most of the heavy lifting for you.
Place a UISearchBar (not a UITextField) in your view, and wire up a UISearchDisplayController to it.
// ProductViewController.h
#property IBOutlet UISearchBar *searchBar;
#property ProductSearchController *searchController;
// ProductViewController.m
- (void) viewDidLoad
{
[super viewDidLoad];
searchBar.placeholder = #"Search products";
searchBar.showsCancelButton = YES;
self.searchController = [[[ProductSearchController alloc]
initWithSearchBar:searchBar
contentsController:self] autorelease];
}
I usually subclass UISearchDisplayController and have it be it's own delegate, searchResultsDataSource and searchResultsDelegate. The latter two manage the result table in the normal manner.
// ProductSearchController.h
#interface ProductSearchController : UISearchDisplayController
<UISearchDisplayDelegate, UITableViewDelegate, UITableViewDataSource>
// ProductSearchController.m
- (id)initWithSearchBar:(UISearchBar *)searchBar
contentsController:(UIViewController *)viewController
{
self = [super initWithSearchBar:searchBar contentsController:viewController];
self.contents = [[NSMutableArray new] autorelease];
self.delegate = self;
self.searchResultsDataSource = self;
self.searchResultsDelegate = self;
return self;
}
Each keypress in the searchbar calls searchDisplayController:shouldReloadTableForSearchString:. A quick search can be implemented directly here.
- (BOOL) searchDisplayController:(UISearchDisplayController*)controller
shouldReloadTableForSearchString:(NSString*)searchString
{
// perform search and update self.contents (on main thread)
return YES;
}
If your search might take some time, do it in the background with NSOperationQueue. In my example, ProductSearchOperation will call showSearchResult: when and if it completes.
// ProductSearchController.h
#property INSOperationQueue *searchQueue;
// ProductSearchController.m
- (BOOL) searchDisplayController:(UISearchDisplayController*)controller
shouldReloadTableForSearchString:(NSString*)searchString
{
if (!searchQueue) {
self.searchQueue = [[NSOperationQueue new] autorelease];
searchQueue.maxConcurrentOperationCount = 1;
}
[searchQueue cancelAllOperations];
NSInvocationOperation *op = [[[ProductSearchOperation alloc]
initWithController:self
searchTerm:searchString] autorelease];
[searchQueue addOperation:op];
return NO;
}
- (void) showSearchResult:(NSMutableArray*)result
{
self.contents = result;
[self.searchResultsTableView
performSelectorOnMainThread:#selector(reloadData)
withObject:nil waitUntilDone:NO];
}
It sounds like you have a pretty good idea of an implementation already. My suggestion would be to present a UITableView in a popover with the search bar at the top, then simply drive the table view's data source using the search term and call reloadData on the table view every time the user types into the box.