Alter CGRect (or any struct)? - objective-c

I do this quite a bit in my code:
self.sliderOne.frame = CGRectMake(newX, 0, self.sliderOne.frame.size.width, self.sliderOne.frame.size.height);
Is there any way to avoid this tedious code? I have tried this type of thing:
self.sliderOne.frame.origin.x = newX;
but I get a Lvalue required as left operand of assignment error.

I finally followed #Dave DeLong's suggestion and made a category. All you have to do is import it in any class that wants to take advantage of it.
UIView+AlterFrame.h
#import <UIKit/UIKit.h>
#interface UIView (AlterFrame)
- (void) setFrameWidth:(CGFloat)newWidth;
- (void) setFrameHeight:(CGFloat)newHeight;
- (void) setFrameOriginX:(CGFloat)newX;
- (void) setFrameOriginY:(CGFloat)newY;
#end
UIView+AlterFrame.m
#import "UIView+AlterFrame.h"
#implementation UIView (AlterFrame)
- (void) setFrameWidth:(CGFloat)newWidth {
CGRect f = self.frame;
f.size.width = newWidth;
self.frame = f;
}
- (void) setFrameHeight:(CGFloat)newHeight {
CGRect f = self.frame;
f.size.height = newHeight;
self.frame = f;
}
- (void) setFrameOriginX:(CGFloat)newX {
CGRect f = self.frame;
f.origin.x = newX;
self.frame = f;
}
- (void) setFrameOriginY:(CGFloat)newY {
CGRect f = self.frame;
f.origin.y = newY;
self.frame = f;
}
#end
I could DRY up the methods using blocks... I'll do that at some point soon, I hope.
Later: I just noticed CGRectOffset and CGRectInset, so this category could be cleaned up a bit (if not eliminated altogether).

Yeah, you have to do:
CGRect newFrame = self.sliderOne.frame;
newFrame.origin.x = frame.size.width - MARGIN * 2 - totalWidth;
self.sliderOne.frame = newFrame;
It sucks, I know. If you find yourself doing this a lot, you may want to add categories to UIView/NSView to alter this stuff for you:
#interface UIView (FrameMucking)
- (void) setWidth:(CGFloat)newWidth;
#end
#implementation UIView (FrameMucking)
- (void) setWidth:(CGFloat)newWidth {
CGRect f = [self frame];
f.size.width = newWidth;
[self setFrame:f];
}
#end
Etc.

The issue here is that self.sliderOne.frame.origin.x is the same thing as [[self sliderOne] frame].origin.x. As you can see, assigning back to the lValue here is not what you want to do.
So no, that "tedious" code is necessary, although can be shortened up a bit.
CGRect rect = thing.frame;
thing.frame = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect) + 10, etc...);

Related

How to implement a multiline NSTokenField?

I want to implement an NSTokenField that spans multiple lines. For example:
Multi-line NSTokenField
I found a some sample code of multi-line NSTokenField on the internet:
IBOutlet NSTokenField *tokenField;
- (void)awakeFromNib
{
[[tokenField cell] setWraps:YES];
}
made my class delegate of the NSTokenField and implement the following method
- (void)controlTextDidChange:(NSNotification *)obj {
NSRect oldTokenFieldFrame = [tokenField frame];
NSRect tokenFieldBounds = [tokenField bounds];
float height = oldTokenFieldFrame.size.height;
tokenFieldBounds.size.height = CGFLOAT_MAX;
NSSize cellSize = [[tokenField cell] cellSizeForBounds:tokenFieldBounds];
float y = oldTokenFieldFrame.origin.y + height - cellSize.height;
[tokenField setFrame:NSMakeRect(oldTokenFieldFrame.origin.x,
y,
oldTokenFieldFrame.size.width,
cellSize.height)];
}
but this code does not work correctly.
Could you please help me with this issue?
Thank you in advance.
For everybody who is just looking for an answer with working code, I've found this super short solution. Here we go:
#import "MyExpandingTokenField.h"
#implementation MyExpandingTokenField
- (NSSize)intrinsicContentSize {
NSSize intrinsicContentSize = [self sizeThatFits:NSMakeSize(self.frame.size.width, CGFLOAT_MAX)];
intrinsicContentSize = NSMakeSize(intrinsicContentSize.width, intrinsicContentSize.height + 5);
return intrinsicContentSize;
}
- (void)textDidChange:(NSNotification *)notification {
[super textDidChange:notification];
[self invalidateIntrinsicContentSize];
}
#end

NSPopover show relative to NSStatusItem

This is my code:
if #available(OSX 10.10, *) {
if let b = statusItem.button {
popover.showRelativeToRect(b.bounds, ofView: b, preferredEdge: .MinY)
}
} else {
}
The else block is for OS X Mavericks because NSStatusItem.button is not available. Is there a simple way of showing the popover relative to the status item? If not, is it possible to show the popover in the center of the screen instead without the arrow?
before you had access to the statusitem button you had to provide your own view. Then all works the same
to retain the original behaviour, draw a custom view that looks like a status item ;)
e.g.
#interface DDQuickMenuStatusItemView : NSView
#property(weak) NSStatusItem *item;
//...
#end
#implementation DDQuickMenuStatusItemView
//...
- (void)drawRect:(NSRect)dirtyRect {
NSImage *image = nil;
if(self.item) {
[self.item drawStatusBarBackgroundInRect:self.bounds withHighlight:NO];
image = self.item.image;
}
if(image) {
NSRect r = self.bounds;
r.size = [image size];
r = [self.class centerRect:r inRect:self.bounds];
r = [self centerScanRect:r];
[image drawInRect:r fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1];
}
}
#pragma mark -
+ (CGRect)centerRect:(CGRect)rect inRect:(CGRect)inRect
{
CGRect result = rect;
result.origin.x = inRect.origin.x + (inRect.size.width - result.size.width)*0.5f;
result.origin.y = inRect.origin.y + (inRect.size.height - result.size.height)*0.5f;
return result;
}
#end
Note that the view is a sample and not production ready ;)

CAEmitterLayer not visible in my View Controller

I'm trying to add a simple particle effect overlay to one of my UIViewController instances. I've followed a couple of tutorials I found, but neither of them deal with view controllers and their storyboards are just a view out on its own with no controller, which is confusing.
Here's my code, I'm just trying to figure out why I can't see the particle effects. What am I missing? The view itself is definitely there and added (if I change its color or something I can see it), it's just empty, and isn't showing any particle effects. The image file referenced is definitely in the project and target, so what else have I done wrong here? Do I need to add the CAEmitterLayer to rainView somehow? The tutorials didn't offer any help on this part!
RainfallOverlay.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface RainfallOverlay : UIView {
CAEmitterLayer *rainEmitter;
}
#end
RainfallOverlay.m
#import "RainfallOverlay.h"
#implementation RainfallOverlay
- (void) awakeFromNib
{
rainEmitter = (CAEmitterLayer *) self.layer;
rainEmitter.emitterPosition = CGPointMake(160, 100);
rainEmitter.emitterSize = CGSizeMake(10, 10);
rainEmitter.renderMode = kCAEmitterLayerAdditive;
CAEmitterCell *rain = [CAEmitterCell emitterCell];
rain.birthRate = 200;
rain.lifetime = 2.0;
rain.lifetimeRange = 1.5;
rain.color = [[UIColor colorWithRed: 0.2 green: 0.4 blue: 0.8 alpha: 0.1] CGColor];
rain.contents = (id) [[UIImage imageNamed: #"Particles_rain.png"] CGImage];
rain.name = #"rain";
rain.velocity = 150;
rain.velocityRange = 100;
rain.emissionRange = M_PI_2;
rain.emissionLongitude = 0.025 * 180 / M_PI;
rain.scaleSpeed = 0;
rain.spin = 0.5;
rainEmitter.emitterCells = [NSArray arrayWithObject: rain];
}
#end
ViewController.m viewDidLoad
RainfallOverlay *rainView = [[RainfallOverlay alloc] initWithFrame: CGRectMake(0, 0, 320, 250)];
[rainView setUserInteractionEnabled: NO];
[self.view bringSubviewToFront: rainView];
[self.view addSubview: rainView];
There are 2 problems:
awakeFromNib would only be called if the view is loaded from a nib file. In your case, you have to implement initWithFrame.
You have to override layerClass to that self.layer returns a CAEmitterLayer. Just casting the layer to CAEmitterLayer does not work.
So your RainfallOverlay implementation should look like this:
+ (Class)layerClass
{
return [CAEmitterLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
rainEmitter = (CAEmitterLayer *) self.layer;
// ... remaining setup
}
return self;
}

Implementing a Simple Line Graph in iOS

What would be the best way to implament a simple line graph inside my iPad app. Im only looking to plot 4-8 points and the graph does need to be fancy looking. HTML5 will be good enough to use for what I need.
How would I go about implementing HTML code to plot a line graph?
I have been looking at using Google Charting Tools, but any other suggestions that I could use?
Here is a very basic class that will give you the basic line. It takes an NSArray of NSNumbers for the Y axis. It should provide a good starting point for you as it is as simple as it gets. You might consider adding labels to the points and axis markers to give a sense of scale.
Header:
#import <UIKit/UIKit.h>
#interface SOGenericGraphView : UIView {
NSArray *yValues;
}
#property (strong, atomic) NSArray *yValues;
#end
Implementation:
#import "SOGenericGraphView.h"
#implementation SOGenericGraphView
#synthesize yValues;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
[super drawRect:rect];
CGRect insetRect = CGRectInset(self.frame, 10, 15);
double maxSpeed = [[yValues valueForKeyPath:#"#max.doubleValue"] doubleValue];
CGFloat yRatio = insetRect.size.height/maxSpeed;
CGFloat xRatio = insetRect.size.width/(yValues.count-1);
UIBezierPath *sparkline = [UIBezierPath bezierPath];
for (int x = 0; x< yValues.count; x++) {
CGPoint newPoint = CGPointMake(x*xRatio + insetRect.origin.x, insetRect.size.height - (yRatio*[[yValues objectAtIndex:x] doubleValue] - insetRect.origin.y));
if (x == 0) {
[sparkline moveToPoint:newPoint];
}
else {
[sparkline addLineToPoint:newPoint];
}
}
[[UIColor redColor] set];
[sparkline stroke];
}
#end

How do I implement snap to grid functionality for a UIImageView?

I have a draggable view that I have set up with the following code:
#import <UIKit/UIKit.h>
#interface DraggableView : UIImageView {
CGPoint startLocation;
}
#end
#import "DraggableView.h"
#implementation DraggableView
- (id) initWithImage: (UIImage *) anImage
{
if (self = [super initWithImage:anImage])
self.userInteractionEnabled = YES;
return self;
}
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
// Calculate and store offset, and pop view into front if needed
CGPoint pt = [[touches anyObject] locationInView:self];
startLocation = pt;
[[self superview] bringSubviewToFront:self];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
// Calculate offset
CGPoint pt = [[touches anyObject] locationInView:self];
float dx = pt.x - startLocation.x;
float dy = pt.y - startLocation.y;
CGPoint newcenter = CGPointMake(self.center.x + dx, self.center.y + dy);
// Set new location
self.center = newcenter;
}
How do I go about snapping this view to a grid? From a broad viewpoint, I understand that I could offset the new location in a touchesEnded method call. However, I am hitting a brick wall when I try to implement this.
Thanks in advance for any assistance with this issue.
In touchesMoved, before applying newcenter to your view, round it to your grid step size:
float step = 10.0; // Grid step size.
newcenter.x = step * floor((newcenter.x / step) + 0.5);
newcenter.y = step * floor((newcenter.y / step) + 0.5);
This will cause your view to "snap" as you drag it.
although the code in jnic's answer is semantically equivalent to the code below, i think this code is more elegant.
here it is in pseudocode (javaish)
int gridCubeWidth = 3; //for instance
int gridCubeHeight = 3;
int newX = Math.Round(oldX / gridCubeWidth) * gridCubeWidth;
int newY = Math.Round(oldY / gridCubeHeight) * gridCubeHeight;
using this example, a point on x like x=4 should map to 3 but x=5 should map to 6.