NSView Instantiation and Get Its Values with Identifier - objective-c

I have a subclass of NSView named clickView as follows.
// clickView.h
#interface clickView : NSView {
BOOL onOff;
}
- (BOOL)getOnOff;
// clickView.m
- (BOOL)getOnOff {
return onOff;
}
- (void)mouseDown:(NSEvent *)event {
if (onOff) {
onOff = NO;
} else {
onOff = YES;
}
[self setNeedsDisplay:YES];
NSLog(#"%#",self.identifier);
}
It utilizes its drawRect method (, which is not shown here,) to fill the rectangle with a color if the user clicks on it. And it uses a boolean value (onOff) to see if it's been clicked on. Now, switching to AppleDelegate, I instantiate this NSView subclass as follows.
// AppDelegate.m
- (IBAction)create4Clicked:(id)sender {
NSInteger rowCount = 10;
NSInteger colCount = 10;
NSInteger k = 1;
for (NSInteger i2 = 0; i2 < rowCount; i2 ++) {
for (NSInteger i3 = 0; i3 < colCount; i3 ++) {
NSView *view = [[clickView alloc] initWithFrame:NSMakeRect(50+i2*10,50+i3*10,10,10)];
[view setIdentifier:[NSString stringWithFormat:#"%li",k]];
[view1 addSubview:view]; // view1 is NSView that's been created with Interface Builder
k++;
}
}
}
So I now have 100 squares displayed on view1 (NSView). If I click on any of the squares, I do get its identifier. (See 'mouseDown.') Now, what I need to figure out is how to tell which square has its 'onOff' set to YES (or NO).
Thank you for your help.

A. First off all:
Probably you are new to Cocoa and Objective-C.
a. Why don't you use buttons?
b. onOff is obviously a property. Why don't you use declared properties?
B. To your Q:
You can retrieve the views with a specific state by asking the superview for its subviews and then filter them with a predicate:
NSPredicate *clickedPredicate = [NSPredicate predicateWithFormat:#"onOff == %d", YES];
NSArray* clickViews = [view1 subviews]; // Returns all subviews
clickViews = [clickViews filteredArrayUsingPredicate:clickedPredicate]; // On-views
But you should think about storing the state outside the views. What is your app for?

Related

Draw only resized area of NSView

I'm trying to draw an NSView efficiently, even when resizing. According to the docs for -drawRect:, I can use -getRectsBeingDrawn:count: to figure out what exactly needs to be redrawn and only redraw that. Furthermore, if I want to have live-resizing preserve content, I should return YES from -preservesContentDuringLiveResize and override -setFrameSize: and use -getRectsExposedDuringLiveResize:count: to determine what to mark as needing display. But while live-resizing the window, inside -drawRect: the -getRectsBeingDrawn:count: method still returns the entire bounds of the NSView rather than just the newly exposed part of the view. I confirm this using the NSView subclass below (auto-sized to always fill the window) and by dragging the window's bottom edge downward.
- (void) drawRect:(NSRect)dirtyRect {
const NSRect* rects;
NSInteger count;
[self getRectsBeingDrawn:&rects count:&count];
for (int i = 0; i < count; i++) {
NSRect r = rects[i];
[[[NSColor greenColor] colorWithAlphaComponent:0.5] drawSwatchInRect:r];
}
}
- (BOOL) preservesContentDuringLiveResize {
return YES;
}
- (BOOL) isOpaque {
return YES;
}
- (void) setFrameSize:(NSSize)newSize {
[super setFrameSize:newSize];
if ([self inLiveResize]) {
NSRect rects[4];
NSInteger count;
[self getRectsExposedDuringLiveResize:rects count:&count];
for (int i = 0; i < count; i++) {
NSRect r = rects[i];
[self setNeedsDisplayInRect:r];
}
}
else {
[self setNeedsDisplay:YES];
}
}

finding locations of subview in superview

Hello i have superview in which i have many UIImageViews.I have added UIPanGesture to the SuperView which is 'View.m' in my case. I need to get the reference or in others words object of that UIImageView(subview) when user drags over it in superview.Also if this can be done with 'hittest' then let me know how to use it in gestureHandler and as hittest returns UIView , how i can convert UiView to UIImageView. Here is my code
//
// View.m
// PuzzleGame
//
// Created by Noman Khan on 8/29/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "View.h"
#define noOfRows 5
#define noOfCols 4
#implementation View
#synthesize imageArray;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)initImageView {
int x = 1;
int y = 1;
// int width = 190;
// int height = 198;
int width = sizeOfRows;
int height = sizeOfCols;
UIImageView *imgView=[[UIImageView alloc]initWithFrame:CGRectMake(x,y,width-10,height+5)];
imgView.image=[UIImage imageNamed:#"iceage_01.png"];
[self addSubview:imgView];
x=x+(width-9);
y=y+(sizeOfCols+9);
UIImageView *imgView1=[[UIImageView alloc]initWithFrame:CGRectMake(x,y,width-10,height+5)];
imgView1.image=[UIImage imageNamed:#"iceage_02.png"];
[self addSubview:imgView1];
- (void)drawRect:(CGRect)rect
{
imageArray=[[NSArray alloc]initWithObjects:[UIImage imageNamed:#"iceage_01.png"],[UIImage imageNamed:#"iceage_02.png"],[UIImage imageNamed:#"iceage_03.png"],[UIImage imageNamed:#"iceage_04.png"],[UIImage imageNamed:#"iceage_05.png"],[UIImage imageNamed:#"iceage_06.png"],[UIImage imageNamed:#"iceage_07.png"],[UIImage imageNamed:#"iceage_08.png"],[UIImage imageNamed:#"iceage_09.png"],[UIImage imageNamed:#"iceage_10.png"],[UIImage imageNamed:#"iceage_11.png"],[UIImage imageNamed:#"iceage_12.png"],[UIImage imageNamed:#"iceage_13.png"],[UIImage imageNamed:#"iceage_14.png"],[UIImage imageNamed:#"iceage_15.png"],[UIImage imageNamed:#"iceage_16.png"],[UIImage imageNamed:#"iceage_17.png"],[UIImage imageNamed:#"iceage_18.png"],[UIImage imageNamed:#"iceage_19.png"],[UIImage imageNamed:#"iceage_20.png"], nil];
CGContextRef content=UIGraphicsGetCurrentContext();
CGContextSetLineWidth(content, 2);
CGContextSetStrokeColorWithColor(content, [UIColor whiteColor].CGColor);
int totalWidth=self.frame.size.width;
int totalHeight=self.frame.size.height;
sizeOfRows=totalHeight/noOfRows;
sizeOfCols=totalWidth/noOfCols;
//making rows
int x = 0,y = 0;
for (int i=0; i<noOfRows+1; i++) {
CGContextMoveToPoint(content, x, y);
CGContextAddLineToPoint(content, 1000, y);
CGContextStrokePath(content);
//y=y+200;
y=y+sizeOfRows;
}
//making colums
x=0,y=0;
for (int i=0; i<noOfCols+1; i++) {
CGContextMoveToPoint(content, x, y);
CGContextAddLineToPoint(content, x, 1000);
CGContextStrokePath(content);
//x=x+192;
x=x+sizeOfCols;
}
[self initImageView];
UIPanGestureRecognizer *panGesture=[[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(panHandler:)];
[self addGestureRecognizer:panGesture];
// CGContextMoveToPoint(content, 20, 20);
// CGĂ„ContextAddLineToPoint(content, 50, 50);
//
// CGContextStrokePath(content);
}
- (void)panHandler:(UIGestureRecognizer *)sender{
CGPoint point=[sender locationInView:self];
if (sender.state == UIGestureRecognizerStateBegan) {
NSLog(#"BEgin");
UIView *v=[[UIView alloc]initWithFrame:CGRectMake(75, 75, 100, 100)];
v = [self hitTest:point withEvent:nil];
// UIImageView *imv=(UIImageView*)v;
[self addSubview:v];
}
if (sender.state == UIGestureRecognizerStateChanged) {
NSLog(#"moved");
}
if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"end");
}
// NSLog(#"In Pan Handler");
}
#end
Yes you can use hittest.
Hittest works in recursive fashion, below are some important points about hittest.
1. It calls pointInside:withEvent: of self
2. If it ,hitTest:withEvent:, returns nil. your view hierarchy is end. you dont have further views.
3. If it returns YES, the message is passed to subviews, it starts from the top-level subview, and continues to other views until a subview returns a non-nil object or all subviews receive the message.
4. If no nil object is returned nil, the self is returned
UIImageView is the subclass of UIView so you can cast your hittest returned view to UIImageView:
UIImageView *imageView = (UIImageView *)[[UIView alloc]initWithFrame:CGRectMake(75, 75, 100, 100)];
You can use Tag property of view for much better handling.

NSScrollView infinite / endless scroll | subview reuse

I'm searching for a way to implement something like reusable cells for UI/NSTableView but for NSScrollView. Basically I want the same like the WWDC 2011 video "Session 104 - Advanced Scroll View Techniques" but for Mac.
I have several problems realizing this. The first: NSScrollView doesn't have -layoutSubviews. I tried to use -adjustScroll instead but fail in setting a different contentOffset:
- (NSRect)adjustScroll:(NSRect)proposedVisibleRect {
if (proposedVisibleRect.origin.x > 600) {
// non of them work properly
// proposedVisibleRect.origin.x = 0;
// [self setBoundsOrigin:NSZeroPoint];
// [self setFrameOrigin:NSZeroPoint];
// [[parentScrollView contentView] scrollPoint:NSZeroPoint];
// [[parentScrollView contentView] setBoundsOrigin:NSZeroPoint];
}
return proposedVisibleRect;
}
The next thing I tried was to set a really huge content view with a width of millions of pixel (which actually works in comparison to iOS!) but now the question is, how to install a reuse-pool?
Is it better to move the subviews while scrolling to a new position or to remove all subviews and insert them again? and how and where should I do that?
As best I can tell, -adjustScroll: is not where you want to tap into the scrolling events because it doesn't get called universally. I think -reflectScrolledClipView: is probably a better hookup point.
I cooked up the following example that should hit the high points of one way to do a view-reusing scroll view. For simplicity, I set the dimensions of the scrollView's documentView to "huge", as you suggest, rather than trying to "fake up" the scrolling behavior to look infinite. Obviously drawing the constituent tile views for real is up to you. (In this example I created a dummy view that just fills itself with red with a blue outline to convince myself that everything was working.) It came out like this:
// For the header file
#interface SOReuseScrollView : NSScrollView
#end
// For the implementation file
#interface SOReuseScrollView () // Private
- (void)p_updateTiles;
#property (nonatomic, readonly, retain) NSMutableArray* p_reusableViews;
#end
// Just a small diagnosting view to convince myself that this works.
#interface SODiagnosticView : NSView
#end
#implementation SOReuseScrollView
#synthesize p_reusableViews = mReusableViews;
- (void)dealloc
{
[mReusableViews release];
[super dealloc];
}
- (NSMutableArray*)p_reusableViews
{
if (nil == mReusableViews)
{
mReusableViews = [[NSMutableArray alloc] init];
}
return mReusableViews;
}
- (void)reflectScrolledClipView:(NSClipView *)cView
{
[super reflectScrolledClipView: cView];
[self p_updateTiles];
}
- (void)p_updateTiles
{
// The size of a tile...
static const NSSize gGranuleSize = {250.0, 250.0};
NSMutableArray* reusableViews = self.p_reusableViews;
NSRect documentVisibleRect = self.documentVisibleRect;
// Determine the needed tiles for coverage
const CGFloat xMin = floor(NSMinX(documentVisibleRect) / gGranuleSize.width) * gGranuleSize.width;
const CGFloat xMax = xMin + (ceil((NSMaxX(documentVisibleRect) - xMin) / gGranuleSize.width) * gGranuleSize.width);
const CGFloat yMin = floor(NSMinY(documentVisibleRect) / gGranuleSize.height) * gGranuleSize.height;
const CGFloat yMax = ceil((NSMaxY(documentVisibleRect) - yMin) / gGranuleSize.height) * gGranuleSize.height;
// Figure out the tile frames we would need to get full coverage
NSMutableSet* neededTileFrames = [NSMutableSet set];
for (CGFloat x = xMin; x < xMax; x += gGranuleSize.width)
{
for (CGFloat y = yMin; y < yMax; y += gGranuleSize.height)
{
NSRect rect = NSMakeRect(x, y, gGranuleSize.width, gGranuleSize.height);
[neededTileFrames addObject: [NSValue valueWithRect: rect]];
}
}
// See if we already have subviews that cover these needed frames.
for (NSView* subview in [[[self.documentView subviews] copy] autorelease])
{
NSValue* frameRectVal = [NSValue valueWithRect: subview.frame];
// If we don't need this one any more...
if (![neededTileFrames containsObject: frameRectVal])
{
// Then recycle it...
[reusableViews addObject: subview];
[subview removeFromSuperview];
}
else
{
// Take this frame rect off the To-do list.
[neededTileFrames removeObject: frameRectVal];
}
}
// Add needed tiles from the to-do list
for (NSValue* neededFrame in neededTileFrames)
{
NSView* view = [[[reusableViews lastObject] retain] autorelease];
[reusableViews removeLastObject];
if (nil == view)
{
// Create one if we didnt find a reusable one.
view = [[[SODiagnosticView alloc] initWithFrame: NSZeroRect] autorelease];
NSLog(#"Created a view.");
}
else
{
NSLog(#"Reused a view.");
}
// Place it and install it.
view.frame = [neededFrame rectValue];
[view setNeedsDisplay: YES];
[self.documentView addSubview: view];
}
}
#end
#implementation SODiagnosticView
- (void)drawRect:(NSRect)dirtyRect
{
// Draw a red tile with a blue border.
[[NSColor blueColor] set];
NSRectFill(self.bounds);
[[NSColor redColor] setFill];
NSRectFill(NSInsetRect(self.bounds, 2,2));
}
#end
This worked pretty well as best I could tell. Again, drawing something meaningful in the reused views is where the real work is here.
Hope that helps.

How do I add views to an UIScrollView and keep it synchronized with a UIPageControl?

I need to dynamically create some views in my app and place some buttons on them dynamically again. If the amount (count) of the buttons is more then ten I want to place the buttons on a new view and the transitions between the views must be with UIPageControl. Even though I googled and searched from Apple's Developer page I couldn't find a way of solving my problem. Can someone please help?
Add your views as side-by-side subviews of an UIScrollView, using the addSubview method. Then use the UIPageControl with the UIScrollView, as in this example.
I made a class that manages a UIScrollView, a UIPageControl, and an array of UIViews. It is a simplified version of what I use in my own code. It does the following:
Sets up the scroll view to display an array of UIViews. It doesn't care if the views have been generated dynamically or not.
Handles scroll and page control events.
Synchronizes the scroll view with the page control.
PageViewManager.h
#import <Foundation/Foundation.h>
#interface PageViewManager : NSObject <UIScrollViewDelegate>
{
UIScrollView* scrollView_;
UIPageControl* pageControl_;
NSArray* pages_;
BOOL pageControlUsed_;
NSInteger pageIndex_;
}
- (id)initWithScrollView:(UIScrollView*)scrollView
pageControl:(UIPageControl*)pageControl;
- (void)loadPages:(NSArray*)pages;
- (void)loadControllerViews:(NSArray*)pageControllers;
#end
PageViewManager.m
#import "PageViewManager.h"
#interface PageViewManager ()
- (void)pageControlChanged;
#end
#implementation PageViewManager
- (id)initWithScrollView:(UIScrollView*)scrollView
pageControl:(UIPageControl*)pageControl
{
self = [super init];
if (self)
{
scrollView_ = scrollView;
pageControl_ = pageControl;
pageControlUsed_ = NO;
pageIndex_ = 0;
[pageControl_ addTarget:self action:#selector(pageControlChanged)
forControlEvents:UIControlEventValueChanged];
}
return self;
}
/* Setup the PageViewManager with an array of UIViews. */
- (void)loadPages:(NSArray*)pages
{
pages_ = pages;
scrollView_.delegate = self;
pageControl_.numberOfPages = [pages count];
CGFloat pageWidth = scrollView_.frame.size.width;
CGFloat pageHeight = scrollView_.frame.size.height;
scrollView_.pagingEnabled = YES;
scrollView_.contentSize = CGSizeMake(pageWidth*[pages_ count], pageHeight);
scrollView_.scrollsToTop = NO;
scrollView_.delaysContentTouches = NO;
[pages_ enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop)
{
UIView* page = obj;
page.frame = CGRectMake(pageWidth * index, 0,
pageWidth, pageHeight);
[scrollView_ addSubview:page];
}];
}
/* Setup the PageViewManager with an array of UIViewControllers. */
- (void)loadControllerViews:(NSArray*)pageControllers
{
NSMutableArray* pages = [NSMutableArray arrayWithCapacity:
pageControllers.count];
[pageControllers enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop)
{
UIViewController* controller = obj;
[pages addObject:controller.view];
}];
[self loadPages:pages];
}
- (void)pageControlChanged
{
pageIndex_ = pageControl_.currentPage;
// Set the boolean used when scrolls originate from the page control.
pageControlUsed_ = YES;
// Update the scroll view to the appropriate page
CGFloat pageWidth = scrollView_.frame.size.width;
CGFloat pageHeight = scrollView_.frame.size.height;
CGRect rect = CGRectMake(pageWidth * pageIndex_, 0, pageWidth, pageHeight);
[scrollView_ scrollRectToVisible:rect animated:YES];
}
- (void)scrollViewDidScroll:(UIScrollView*)sender
{
// If the scroll was initiated from the page control, do nothing.
if (!pageControlUsed_)
{
/* Switch the page control when more than 50% of the previous/next
page is visible. */
CGFloat pageWidth = scrollView_.frame.size.width;
CGFloat xOffset = scrollView_.contentOffset.x;
int index = floor((xOffset - pageWidth/2) / pageWidth) + 1;
if (index != pageIndex_)
{
pageIndex_ = index;
pageControl_.currentPage = index;
}
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView
{
pageControlUsed_ = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView
{
pageControlUsed_ = NO;
}
#end
To use this class, you embed it inside a UIViewController than contains the UIScrollView and the UIPageControl.
Usage:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Create some views dynamically
UIView* v1 = ...
UIView* v2 = ...
// Put the views inside an NSArray:
NSArray* pages_ = [NSArray arrayWithObjects:v1, v2, nil];
/* Create the PageViewManager, which is a member (or property) of this
UIViewController. The UIScrollView and UIPageControl belong to this
UIViewController, but we're letting the PageViewManager manage them for us. */
pageViewManager_ = [[PageViewManager alloc]
initWithScrollView:self.scrollView
pageControl:self.pageControl];
// Make the PageViewManager display our array of UIViews on the UIScrollView.
[pageViewManager_ loadViews:pages_];
}
My sample code assumes that you're using ARC.

NSOutlineView Changing group row color

In my NSOutlineview i am using custom cell which is subclassed from NSTextFieldCell,
I need to draw different color for group row and for normal row, when its selected,
To do so, i have done following ,
-(id)_highlightColorForCell:(NSCell *)cell
{
return [NSColor colorWithCalibratedWhite:0.5f alpha:0.7f];
}
Yep i know its private API, but i couldn't found any other way,
this is working very well for Normal Row, but no effect on Group Row, Is there any way to change the group color,
Kind Regards
Rohan
You can actually do this without relying on private API's, at least if your willing to require Mac OS X 10.4 or better.
Put the following in your cell subclass:
- (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
// Returning nil circumvents the standard row highlighting.
return nil;
}
And then subclass the NSOutlineView and re-implement the method, - (void)highlightSelectionInClipRect:(NSRect)clipRect;
Here's an example that draws one color for non-group rows and another for group rows
- (void)highlightSelectionInClipRect:(NSRect)clipRect
{
NSIndexSet *selectedRowIndexes = [self selectedRowIndexes];
NSRange visibleRows = [self rowsInRect:clipRect];
NSUInteger selectedRow = [selectedRowIndexes firstIndex];
while (selectedRow != NSNotFound)
{
if (selectedRow == -1 || !NSLocationInRange(selectedRow, visibleRows))
{
selectedRow = [selectedRowIndexes indexGreaterThanIndex:selectedRow];
continue;
}
// determine if this is a group row or not
id delegate = [self delegate];
BOOL isGroupRow = NO;
if ([delegate respondsToSelector:#selector(outlineView:isGroupItem:)])
{
id item = [self itemAtRow:selectedRow];
isGroupRow = [delegate outlineView:self isGroupItem:item];
}
if (isGroupRow)
{
[[NSColor alternateSelectedControlColor] set];
} else {
[[NSColor secondarySelectedControlColor] set];
}
NSRectFill([self rectOfRow:selectedRow]);
selectedRow = [selectedRowIndexes indexGreaterThanIndex:selectedRow];
}
}