Warning in use custom collectionViewFlowLayot - objective-c

I just modify the attributes in my flowLayout
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *array = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
for (int i = 0; i< array.count; i++) {
UICollectionViewLayoutAttributes* attributes = array[i];
if (!attributes.representedElementKind) {
array[i] = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
}
}
return array;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];
CGFloat midX = self.collectionView.contentOffset.x + self.collectionView.width * 0.5;
CGFloat distance = midX - attributes.center.x;
CGFloat activeDistance = self.collectionView.width - lineSpacing - remainSpacing * 2;
CGFloat normalizedDistance = distance / activeDistance;
attributes.alpha = 1 - (1 - alphaFactor) * ABS(normalizedDistance);
CGFloat zoom = 1 - (1 - scaleFactor) * ABS(normalizedDistance);
attributes.transform3D = CATransform3DMakeScale(1.0, zoom, 1.0);
attributes.zIndex = 1;
return attributes;
}
Question like this, console logout in the first time of change the showed cell:
UICollectionViewFlowLayout has cached frame mismatch for index path {length = 2, path = 0 - 1} - cached value: {{350, 15.75}, {265, 283.5}}; expected value: {{350, 0}, {265, 315}}
This is likely occurring because the flow layout subclass MyFlowLayout is modifying attributes returned by UICollectionViewFlowLayout without copying them

Do like this, to copy the attributes
UICollectionViewLayoutAttributes *attributes = [[super layoutAttributesForItemAtIndexPath:indexPath] copy];
This question has solved in this issue:
Warning: UICollectionViewFlowLayout has cached frame mismatch for index path 'abc'

Related

CAShapeLayer Shadow with UIBezierPath

This question continues from a previous answer.
I have the following CAShapeLayer:
- (CAShapeLayer *)gaugeCircleLayer {
if (_gaugeCircleLayer == nil) {
_gaugeCircleLayer = [CAShapeLayer layer];
_gaugeCircleLayer.lineWidth = self.gaugeWidth;
_gaugeCircleLayer.fillColor = [UIColor clearColor].CGColor;
_gaugeCircleLayer.strokeColor = self.gaugeTintColor.CGColor;
_gaugeCircleLayer.strokeStart = 0.0f;
_gaugeCircleLayer.strokeEnd = self.value;
_gaugeCircleLayer.lineCap = kCALineCapRound;
_gaugeCircleLayer.path = [self insideCirclePath].CGPath;
CAShapeLayer *cap = [CAShapeLayer layer];
cap.shadowColor = [UIColor blackColor].CGColor;
cap.shadowRadius = 8.0;
cap.shadowOpacity = 0.9;
cap.shadowOffset = CGSizeMake(0, 0);
cap.fillColor = [UIColor grayColor].CGColor;
[_gaugeCircleLayer addSublayer:cap];
}
return _gaugeCircleLayer;
}
Then I have the following UIBezierPath:
- (UIBezierPath *)insideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:CGRectGetWidth(self.bounds) / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
This produces something like the following:
What I am trying to produce with the cap sublayer is the drop shadow at the end and I'd also be interested to know how to get a GradientLayer working similar to the image below:
The problem is that the sublayer is not appearing anywhere and I'm not quite sure why. I'm also not 100% sure on whether or not this is the best way to produce the desired effect.
UPDATE:
The following bit of code creates a cap, though I'm not quite sure how to add it to my UIBezierPath properly:
let cap = CAShapeLayer()
cap.shadowColor = UIColor.blackColor().CGColor
cap.shadowRadius = 8.0
cap.shadowOpacity = 0.9
cap.shadowOffset = CGSize(width: 0, height: 0)
cap.path = UIBezierPath(ovalInRect: CGRectMake(0, 40, 20, 20)).CGPath
cap.fillColor = UIColor.grayColor().CGColor
layer.addSublayer(cap)
I don't know if this will be useful to you, since it doesn't use the CHCircleGaugeView. I was working on several projects related to this question, so I mashed them together and made some changes to produce a progress view that has a color gradient background with a tip that overlaps the end at 100%. I haven't gotten to the point where I make the rounded tip disappear at 0%, but I'll get there eventually. Here are a couple of views of it at 2 different progress levels,
The view is created with a polar gradient drawn in drawRect, masked by an annulus. The rounded tip is a separate layer that's a half circle on the end of a line connected to the center of the circle that's revolved around its center with a transform based on the progress level. Here's the code,
#implementation AnnulusProgressView{ // subclass of UIView
int slices;
CGFloat circleRadius;
CAShapeLayer *maskLayer;
CGFloat segmentAngle;
CGFloat startAngle;
CAShapeLayer *tipView;
NSMutableArray *colors;
int sign;
}
-(instancetype)initWithFrame:(CGRect)frame wantsBackgroundRing:(BOOL)wantsBackground backgroundRingColor:(UIColor *)ringColor {
if (self = [super initWithFrame:frame]) {
slices = 360;
_ringThickness = 12;
circleRadius = self.bounds.size.width/2;
_startColor = [UIColor colorWithHue:0.24 saturation:1 brightness:0.8 alpha:1];
_endColor = [UIColor colorWithHue:0.03 saturation:1 brightness:1 alpha:1];
[self setupColorArray];
_backgroundRing = wantsBackground? [self createBackgroundRingWithColor:ringColor] : nil;
self.layer.mask = [self createAnnulusMask];
}
return self;
}
-(void)setStartColor:(UIColor *)startColor {
_startColor = startColor;
[self setupColorArray];
}
-(void)setEndColor:(UIColor *)endColor {
_endColor = endColor;
[self setupColorArray];
}
-(void)setupColorArray {
colors = [NSMutableArray new];
CGFloat startHue, startSaturation, startBrightness, startAlpha, endHue, endSaturation, endBrightness, endAlpha;
[self.startColor getHue:&startHue saturation:&startSaturation brightness:&startBrightness alpha:&startAlpha];
[self.endColor getHue:&endHue saturation:&endSaturation brightness:&endBrightness alpha:&endAlpha];
for(int i=0;i<slices;i++){
CGFloat hue = startHue + (endHue - startHue)*i/slices;
CGFloat brightness = startBrightness + (endBrightness - startBrightness)*i/slices;
CGFloat saturation = startSaturation + (endSaturation - startSaturation)*i/slices;
CGFloat alpha = startAlpha + (endAlpha - startAlpha)*i/slices;
UIColor *color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
[colors addObject:color];
}
self.progress = _progress;
}
-(UIView *)createBackgroundRingWithColor:(UIColor *) color {
UIView *bgRing = [[UIView alloc] initWithFrame:self.frame];
bgRing.backgroundColor = color;
bgRing.layer.mask = [self createAnnulusMask];
[(CAShapeLayer *)bgRing.layer.mask setStrokeEnd:startAngle + 2*M_PI ];
return bgRing;
}
-(void)didMoveToSuperview {
if (self.backgroundRing) [self.superview insertSubview:self.backgroundRing belowSubview:self];
tipView = [self tipView];
[self.superview.layer addSublayer:tipView];
}
-(CAShapeLayer *)tipView {
CAShapeLayer *tip = [CAShapeLayer layer];
tip.frame = self.frame;
tip.fillColor = [UIColor redColor].CGColor;
UIBezierPath *shape = [UIBezierPath bezierPath];
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
[shape moveToPoint:center];
CGPoint bottomPoint = CGPointMake(center.x, center.y + circleRadius - self.ringThickness*2);
CGFloat tipCenterY = bottomPoint.y + self.ringThickness - 1;
[shape addLineToPoint: bottomPoint];
[shape addLineToPoint:CGPointMake(bottomPoint.x+2, bottomPoint.y)];
double fractionAlongTangent = self.ringThickness;
[shape addCurveToPoint:CGPointMake(center.x, center.y + circleRadius) controlPoint1:CGPointMake(center.x - self.ringThickness*1.5, tipCenterY - fractionAlongTangent) controlPoint2:CGPointMake(center.x - self.ringThickness*1.5, tipCenterY + fractionAlongTangent)];
[shape closePath];
tip.path = shape.CGPath;
tip.shadowColor = [UIColor darkGrayColor].CGColor;
tip.shadowOpacity = 0.8;
tip.shadowOffset = CGSizeMake(-6, 0);
tip.shadowRadius = 2;
return tip;
}
- (void)setProgress:(CGFloat)progress{
sign = (progress >= _progress)? 1 : -1;
[self animateProgressTo:#(progress)];
}
-(void)animateProgressTo:(NSNumber *) newValueNumber{
float newValue = newValueNumber.floatValue;
_progress = (_progress + (sign * 0.1) > 1)? 1 : _progress + (sign * 0.1);
if ((_progress > newValue && sign == 1) || (_progress < newValue && sign == -1)) {
_progress = newValue;
}
maskLayer.strokeEnd = _progress;
tipView.transform = CATransform3DMakeRotation(-(1 - _progress + 0.002) * M_PI*2, 0, 0, 1); //the 0.002 closes a small gap between the tip and the annulus strokeEnd
int i = (int)(_progress*(slices - 1));
tipView.fillColor = ((UIColor *)colors[i]).CGColor;
if (sign == 1) {
if (_progress < newValue) {
[self performSelector:#selector(animateProgressTo:) withObject:#(newValue) afterDelay:0.05];
}
}else{
if (_progress > newValue) {
[self performSelector:#selector(animateProgressTo:) withObject:#(newValue) afterDelay:0.05];
}
}
NSLog(#"%f",_progress);
}
- (CAShapeLayer *)createAnnulusMask {
maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.bounds;
segmentAngle = 2*M_PI/(slices);
startAngle = M_PI_2;
CGFloat endAngle = startAngle + 2*M_PI;
maskLayer.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)) radius:circleRadius - self.ringThickness startAngle:startAngle endAngle:endAngle clockwise:YES].CGPath;
maskLayer.fillColor = [UIColor clearColor].CGColor;
maskLayer.strokeColor = [UIColor blackColor].CGColor;
maskLayer.lineWidth = self.ringThickness * 2;
maskLayer.strokeEnd = self.progress;
return maskLayer;
}
-(void)drawRect:(CGRect)rect{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(ctx, NO);
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
for(int i=0;i<slices;i++){
CGContextSaveGState(ctx);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:center];
[path addArcWithCenter:center radius:circleRadius startAngle:startAngle endAngle:startAngle+segmentAngle clockwise:YES];
[path addClip];
[colors[i] setFill];
[path fill];
CGContextRestoreGState(ctx);
startAngle += segmentAngle;
}
}
This should resolve the problem.
Only remaining issue is the animation, whereby the cap is not animated.
The trick was to add the cap to the end of the gauge, and update it when the value of the gauge changed. To calculate the location, a little math magic was used. It needs to be under the gauge, so the cap is added in the trackCircleLayer
//
// CHCircleGaugeView.m
//
// Copyright (c) 2014 ChaiOne <http://www.chaione.com/>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "CHCircleGaugeView.h"
#import "CHCircleGaugeViewDebugMacros.h"
#import <CoreText/CoreText.h>
#import <QuartzCore/QuartzCore.h>
static CGFloat const CHKeyAnimationDuration = 0.5f;
static CGFloat const CHKeyDefaultValue = 0.0f;
static CGFloat const CHKeyDefaultFontSize = 32.0f;
static CGFloat const CHKeyDefaultTrackWidth = 0.5f;
static CGFloat const CHKeyDefaultGaugeWidth = 2.0f;
static NSString * const CHKeyDefaultNAText = #"n/a";
static NSString * const CHKeyDefaultNoAnswersText = #"%";
#define CHKeyDefaultTrackTintColor [UIColor blackColor]
#define CHKeyDefaultGaugeTintColor [UIColor blackColor]
#define CHKeyDefaultTextColor [UIColor blackColor]
#interface CHCircleGaugeView ()
#property (nonatomic, strong) CAShapeLayer *trackCircleLayer;
#property (nonatomic, strong) CAShapeLayer *gaugeCircleLayer;
// ADDED
#property (nonatomic, strong) CAShapeLayer *capLayer;
// END ADDED
#property (nonatomic, strong) UILabel *valueTextLabel;
#end
#implementation CHCircleGaugeView
#pragma mark - View Initialization
- (instancetype)init {
return [self initWithFrame:CGRectZero];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initSetup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initSetup];
}
return self;
}
- (void)initSetup {
_state = CHCircleGaugeViewStateNA;
_value = CHKeyDefaultValue;
_trackTintColor = CHKeyDefaultTrackTintColor;
_gaugeTintColor = CHKeyDefaultGaugeTintColor;
_textColor = CHKeyDefaultTextColor;
_font = [UIFont systemFontOfSize:CHKeyDefaultFontSize];
_trackWidth = CHKeyDefaultTrackWidth;
_gaugeWidth = CHKeyDefaultGaugeWidth;
_notApplicableString = CHKeyDefaultNAText;
_noDataString = CHKeyDefaultNoAnswersText;
[self createGauge];
}
- (void)createGauge {
[self.layer addSublayer:self.trackCircleLayer];
[self.layer addSublayer:self.gaugeCircleLayer];
[self addSubview:self.valueTextLabel];
[self setupConstraints];
}
- (void)setupConstraints {
NSDictionary *viewDictionary = #{#"valueText" : self.valueTextLabel};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[valueText]|" options:0 metrics:nil views:viewDictionary]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[valueText]|" options:0 metrics:nil views:viewDictionary]];
}
#pragma mark - Property Setters
- (void)setState:(CHCircleGaugeViewState)state {
if (_state != state) {
_state = state;
switch (state) {
case CHCircleGaugeViewStateNA: {
[self updateGaugeWithValue:0 animated:NO];
break;
}
case CHCircleGaugeViewStatePercentSign: {
[self updateGaugeWithValue:0 animated:NO];
break;
}
case CHCircleGaugeViewStateScore: {
[self updateGaugeWithValue:self.value animated:NO];
break;
}
default: {
ALog(#"Missing gauge state.");
break;
}
}
}
}
- (void)setValue:(CGFloat)value {
[self setValue:value animated:NO];
}
- (void)setValue:(CGFloat)value animated:(BOOL)animated {
self.state = CHCircleGaugeViewStateScore;
if (value != _value) {
[self willChangeValueForKey:NSStringFromSelector(#selector(value))];
value = MIN(1.0f, MAX(0.0f, value));
[self updateGaugeWithValue:value animated:animated];
_value = value;
[self didChangeValueForKey:NSStringFromSelector(#selector(value))];
}
}
- (void)setUnitsString:(NSString *)unitsString {
if ([_unitsString isEqualToString:unitsString] == NO) {
_unitsString = [unitsString copy];
self.valueTextLabel.attributedText = [self formattedStringForValue:self.value];
}
}
- (void)updateGaugeWithValue:(CGFloat)value animated:(BOOL)animated {
self.valueTextLabel.attributedText = [self formattedStringForValue:value];
BOOL previousDisableActionsValue = [CATransaction disableActions];
[CATransaction setDisableActions:YES];
self.gaugeCircleLayer.strokeEnd = value;
// ADDED
_capLayer.path = [self capPathForValue:value].CGPath;
// END ADDED
if (animated) {
self.gaugeCircleLayer.strokeEnd = value;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = CHKeyAnimationDuration;
pathAnimation.fromValue = [NSNumber numberWithFloat:self.value];
pathAnimation.toValue = [NSNumber numberWithFloat:value];
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.gaugeCircleLayer addAnimation:pathAnimation forKey:#"strokeEndAnimation"];
}
[CATransaction setDisableActions:previousDisableActionsValue];
}
- (void)setTrackTintColor:(UIColor *)trackTintColor {
if (_trackTintColor != trackTintColor) {
_trackTintColor = trackTintColor;
self.trackCircleLayer.strokeColor = trackTintColor.CGColor;
}
}
- (void)setGaugeTintColor:(UIColor *)gaugeTintColor {
if (_gaugeTintColor != gaugeTintColor) {
_gaugeTintColor = gaugeTintColor;
self.gaugeCircleLayer.strokeColor = gaugeTintColor.CGColor;
// ADDED
self.capLayer.fillColor = gaugeTintColor.CGColor;
// END ADDED
}
}
- (void)setTrackWidth:(CGFloat)trackWidth {
if (_trackWidth != trackWidth) {
_trackWidth = trackWidth;
self.trackCircleLayer.lineWidth = trackWidth;
[self.layer layoutSublayers];
}
}
- (void)setGaugeWidth:(CGFloat)gaugeWidth {
if (_gaugeWidth != gaugeWidth) {
_gaugeWidth = gaugeWidth;
self.gaugeCircleLayer.lineWidth = gaugeWidth;
[self.layer layoutSublayers];
}
}
- (void)setTextColor:(UIColor *)textColor {
if (_textColor != textColor) {
_textColor = textColor;
self.valueTextLabel.textColor = textColor;
}
}
- (void)setFont:(UIFont *)font {
if (_font != font) {
_font = font;
self.valueTextLabel.font = font;
}
}
- (void)setGaugeStyle:(CHCircleGaugeStyle)gaugeStyle {
if (_gaugeStyle != gaugeStyle) {
_gaugeStyle = gaugeStyle;
[self.layer layoutSublayers];
}
}
#pragma mark - Circle Shapes
- (CAShapeLayer *)trackCircleLayer {
if (_trackCircleLayer == nil) {
_trackCircleLayer = [CAShapeLayer layer];
_trackCircleLayer.lineWidth = self.trackWidth;
_trackCircleLayer.fillColor = [UIColor clearColor].CGColor;
_trackCircleLayer.strokeColor = self.trackTintColor.CGColor;
_trackCircleLayer.path = [self insideCirclePath].CGPath;
// ADDED
_capLayer = [CAShapeLayer layer];
_capLayer.shadowColor = [UIColor blackColor].CGColor;
_capLayer.shadowRadius = 8.0;
_capLayer.shadowOpacity = 0.9;
_capLayer.shadowOffset = CGSizeMake(0, 0);
_capLayer.fillColor = self.gaugeTintColor.CGColor;
_capLayer.path = [self capPathForValue:self.value].CGPath;
[_trackCircleLayer addSublayer:_capLayer];
// END ADDED
}
return _trackCircleLayer;
}
- (CAShapeLayer *)gaugeCircleLayer {
if (_gaugeCircleLayer == nil) {
_gaugeCircleLayer = [CAShapeLayer layer];
_gaugeCircleLayer.lineWidth = self.gaugeWidth;
_gaugeCircleLayer.fillColor = [UIColor clearColor].CGColor;
_gaugeCircleLayer.strokeColor = self.gaugeTintColor.CGColor;
_gaugeCircleLayer.strokeStart = 0.0f;
_gaugeCircleLayer.strokeEnd = self.value;
_gaugeCircleLayer.path = [self circlPathForCurrentGaugeStyle].CGPath;
}
return _gaugeCircleLayer;
}
// ADDED
- (UIBezierPath *)capPathForValue:(float)value {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = CGRectGetWidth(self.bounds) / 2.0f;
float angle = value * 360.0;
float x = radius * sin(angle*M_PI/180.0);
float y = radius * cos(angle*M_PI/180.0);
CGPoint capArcCenter = CGPointMake(arcCenter.x + x, arcCenter.y - y);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:capArcCenter
radius:self.gaugeWidth*_capLayer.shadowRadius / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
// END ADDED
- (UIBezierPath *)circlPathForCurrentGaugeStyle {
switch (self.gaugeStyle) {
case CHCircleGaugeStyleInside: {
return [self insideCirclePath];
}
case CHCircleGaugeStyleOutside: {
return [self outsideCirclePath];
}
default: {
return nil;
}
}
}
- (UIBezierPath *)insideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:CGRectGetWidth(self.bounds) / 2.0f
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
- (UIBezierPath *)outsideCirclePath {
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = (CGRectGetWidth(self.bounds) / 2.0f) + (self.trackWidth / 2.0f) + (self.gaugeWidth / 2.0f);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:radius
startAngle:(3.0f * M_PI_2)
endAngle:(3.0f * M_PI_2) + (2.0f * M_PI)
clockwise:YES];
return path;
}
#pragma mark - Text Label
- (UILabel *)valueTextLabel {
if (_valueTextLabel == nil) {
_valueTextLabel = [[UILabel alloc] init];
[_valueTextLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
_valueTextLabel.textAlignment = NSTextAlignmentCenter;
_valueTextLabel.attributedText = [self formattedStringForValue:self.value];
}
return _valueTextLabel;
}
- (NSAttributedString *)formattedStringForValue:(CGFloat)value {
NSAttributedString *valueString;
NSDictionary *stringAttributes = #{
NSForegroundColorAttributeName : self.textColor,
NSFontAttributeName : self.font
};
switch (self.state) {
case CHCircleGaugeViewStateNA: {
valueString = [[NSAttributedString alloc] initWithString:self.notApplicableString attributes:stringAttributes];
break;
}
case CHCircleGaugeViewStatePercentSign: {
valueString = [[NSAttributedString alloc] initWithString:self.noDataString attributes:stringAttributes];
break;
}
case CHCircleGaugeViewStateScore: {
NSString *suffix = self.unitsString ? self.unitsString : #"";
valueString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"%.0f %#", value * 100.0f, suffix]
attributes:stringAttributes];
break;
}
default: {
ALog(#"Missing gauge state.");
break;
}
}
return valueString;
}
#pragma mark - KVO
// Handling KVO notifications for the value property, since
// we're proxying with the setValue:animated: method.
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:NSStringFromSelector(#selector(value))]) {
return NO;
} else {
return [super automaticallyNotifiesObserversForKey:key];
}
}
#pragma mark - CALayerDelegate
- (void)layoutSublayersOfLayer:(CALayer *)layer {
[super layoutSublayersOfLayer:layer];
if (layer == self.layer) {
self.trackCircleLayer.path = [self insideCirclePath].CGPath;
self.gaugeCircleLayer.path = [self circlPathForCurrentGaugeStyle].CGPath;
}
}
#end

how to snap collection view items to one per swipe regardless of scrolling velocity

I'm using the code from the Apple demo in a subclass of UICollectionViewFlowLayout:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat horizontalCenter = proposedContentOffset.x + self.collectionView.bounds.size.width / 2.;
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0., self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
UICollectionViewLayoutAttributes *targetAttributes = nil;
NSArray *attributes = [super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes *a in attributes) {
CGFloat itemHorizontalCenter = a.center.x;
if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment)) {
offsetAdjustment = itemHorizontalCenter - horizontalCenter;
targetAttributes = a;
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
This works in the sense that whenever I swipe / pan and release, an item will snap into place. But the proposed content offset is proportional to the swipe velocity. What I'm trying to do is that, no matter how fast / slow I swipe, the collection view only snaps to the next item immediately before or after the one that's currently centered.
I tried doing this:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
NSInteger currentPage = (int)(self.collectionView.contentOffset.x / self.collectionView.bounds.size.width);
if (proposedContentOffset.x > self.collectionView.contentOffset.x) {
currentPage = (MIN(currentPage + 1, ((int)(self.collectionView.contentSize.width / self.collectionView.bounds.size.width)) - 1));
}
proposedContentOffset.x = self.collectionView.bounds.size.width * currentPage;
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat horizontalCenter = proposedContentOffset.x + self.collectionView.bounds.size.width / 2.;
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0., self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
UICollectionViewLayoutAttributes *targetAttributes = nil;
NSArray *attributes = [super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes *a in attributes) {
CGFloat itemHorizontalCenter = a.center.x;
if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment)) {
offsetAdjustment = itemHorizontalCenter - horizontalCenter;
targetAttributes = a;
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
But it's not quite right. Sometimes it'll skip an item and snap to the next one after that (that is, it'll not snap to the item immediately next to the one I start scrolling from, but the one next to the one immediately next).
Any thoughts?
Ok so I figured how to make it work by making everything relative to the current contentOffset:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
if (proposedContentOffset.x > self.collectionView.contentOffset.x) {
proposedContentOffset.x = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width / 2.;
}
else if (proposedContentOffset.x < self.collectionView.contentOffset.x) {
proposedContentOffset.x = self.collectionView.contentOffset.x - self.collectionView.bounds.size.width / 2.;
}
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat horizontalCenter = proposedContentOffset.x + self.collectionView.bounds.size.width / 2.;
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0., self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *attributes = [super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes *a in attributes) {
CGFloat itemHorizontalCenter = a.center.x;
if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment)) {
offsetAdjustment = itemHorizontalCenter - horizontalCenter;
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}

Create a sub-image from a larger image

After transferring a large image from a REST endpoint, I need to divide the image into a number of smaller image tiles.
The initial image is (for instance) 1024x1024, stored in an NSData; I need to create sub-image of size 256x256 (In this case, there will be 16 sub-images).
How would this be done? (I haven't found any articles which even come close, but I assume it must be possible since most image editing software supports image cropping.)
Thanks.
This is the function I use to crop images in some of my project.
- (UIImage *)cropImage:(UIImage *) image{
CGRect rect = CGRectMake(0, 0, 256, 256);
CGImageRef subImageRef = CGImageCreateWithImageInRect(image.CGImage, rect);
CGRect smallBounds = CGRectMake(0, 0, CGImageGetWidth(subImageRef), CGImageGetHeight(subImageRef));
UIGraphicsBeginImageContext(smallBounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, smallBounds, subImageRef);
UIImage* smallImg = [UIImage imageWithCGImage:subImageRef];
UIGraphicsEndImageContext();
return smallImg;
}
I think you can fine a way from there to call it multiple times to crop your pictures 16 times .
Hope this helps
originalImageView is a IBOutlet ImageView. This image will be cropped.
#import <QuartzCore/QuartzCore.h>
This is needed for the border around each slice for better understanding.
-(UIImage*)getCropImage:(CGRect)cropRect
{
CGImageRef image = CGImageCreateWithImageInRect([originalImageView.image CGImage],cropRect);
UIImage *cropedImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);
return cropedImage;
}
-(void)prepareSlices:(uint)row:(uint)col
{
float flagX = originalImageView.image.size.width / originalImageView.frame.size.width;
float flagY = originalImageView.image.size.height / originalImageView.frame.size.height;
float _width = originalImageView.frame.size.width / col;
float _height = originalImageView.frame.size.height / row;
float _posX = 0.0;
float _posY = 0.0;
for (int i = 1; i <= row * col; i++) {
UIImageView *croppedImageVeiw = [[UIImageView alloc] initWithFrame:CGRectMake(_posX, _posY, _width, _height)];
UIImage *img = [self getCropImage:CGRectMake(_posX * flagX,_posY * flagY, _width * flagX, _height * flagY)];
croppedImageVeiw.image = img;
croppedImageVeiw.layer.borderColor = [[UIColor whiteColor] CGColor];
croppedImageVeiw.layer.borderWidth = 1.0f;
[self.view addSubview:croppedImageVeiw];
[croppedImageVeiw release];
_posX += _width;
if (i % col == 0) {
_posX = 0;
_posY += _height;
}
}
originalImageView.alpha = 0.0;
}
Call it like this:
[self prepareSlices:16 :16];
#interface UIImage (Sprite)
- (NSArray *)spritesWithSpriteSheetImage:(UIImage *)image spriteSize:(CGSize)size;
- (NSArray *)spritesWithSpriteSheetImage:(UIImage *)image inRange:(NSRange)range spriteSize:(CGSize)size;
#end
#implementation UIImage (Sprite)
-(NSArray *)spritesWithSpriteSheetImage:(UIImage *)image spriteSize:(CGSize)size {
return [self spritesWithSpriteSheetImage:self inRange:NSMakeRange(0, lroundf(MAXFLOAT))
spriteSize:size];
}
-(NSArray *)spritesWithSpriteSheetImage:(UIImage *)image
inRange:(NSRange)range
spriteSize:(CGSize)size {
if (!image || CGSizeEqualToSize(size, CGSizeZero) || range.length == 0)
return nil;
NSLog(#"%i %i", range.location, range.length);
CGImageRef spriteSheet = [image CGImage];
NSMutableArray *tempArray = [[[NSMutableArray alloc] init] autorelease];
int width = CGImageGetWidth(spriteSheet);
int height = CGImageGetHeight(spriteSheet);
int maxI = width / size.width;
int startI = 0;
int startJ = 0;
int length = 0;
int startPosition = range.location;
// Extracting initial I & J values from range info
//
if (startPosition != 0) {
for (int k=1; k<=maxI; k++) {
int d = k * maxI;
if (d/startPosition == 1) {
startI = maxI - (d % startPosition);
break;
}
else if (d/startPosition > 1) {
startI = startPosition;
break;
}
startJ++;
}
}
int positionX = startI * size.width;
int positionY = startJ * size.height;
BOOL isReady = NO;
while (positionY < height) {
while (positionX < width) {
CGImageRef sprite = CGImageCreateWithImageInRect(spriteSheet, CGRectMake(positionX, positionY, size.width, size.height));
[tempArray addObject:[UIImage imageWithCGImage:sprite]];
CGImageRelease(sprite);
length++;
if (length == range.length) {
isReady = YES;
break;
}
positionX += size.width;
}
if (isReady)
break;
positionX = 0;
positionY += size.height;
}
return [NSArray arrayWithArray:tempArray];
}
#end

NSTableColumn size to fit contents

I am developing in and against Mac OS X 10.6 (Snow Leopard). When I double-click between two of my NSTableView's column headers, the column on the left autosizes, like you would expect.
I want to provide this in a context menu as well, but it seems there is no publicly accessible function to do this. I've Googled, and looked at the documentation for NSTableView, NSTableHeaderView, and NSTableColumn, but found nothing. I find it hard to believe they wouldn't expose something so useful when they obviously have the code written to do it.
I saw the -[NSTableColumn sizeToFit] method, but that only takes the header's size into account. I would also settle for sending the double-click event to the NSTableHeaderView, but couldn't figure out how to do that, either.
Update - I realized it's important to mention I have an NSArrayController (subclass) providing the data to my table, so I don't have an NSTableViewDataSource on which I could call -[tableView: objectValueForTableColumn: row:]. That is the crux of the problem: each column is bound to a keypath of the array controller and that's how it gets its data, so it can't loop through its contents.
You can get the length of each cell by getting the cells from the column.
NSCell myCell = [column dataCellForRow:i];
Then get the width of the cell.
width = [myCell cellSize].width;
After that you can set the width of the cell as the width of the column.
[column setMinWidth:width];
[column setWidth:width];
The following category on NSTableColumn resizes columns to the same width that double-clicking on the separator would produce.
#implementation NSTableColumn (resize)
- (void) resizeToFitContents
{
NSTableView * tableView = self.tableView;
NSRect rect = NSMakeRect(0,0, INFINITY, tableView.rowHeight);
NSInteger columnIndex = [tableView.tableColumns indexOfObject:self];
CGFloat maxSize = 0;
for (NSInteger i = 0; i < tableView.numberOfRows; i++) {
NSCell *cell = [tableView preparedCellAtColumn:columnIndex row:i];
NSSize size = [cell cellSizeForBounds:rect];
maxSize = MAX(maxSize, size.width);
}
self.width = maxSize;
}
#end
For view-based table views (OS X 10.7 and later), you have to use the NSTableView method viewAtColumn:row:makeIfNecessary:, and then get the size.
The following code works if your cell view is an NSTextField. For other views you have to figure out the way to get its minimum size.
[tableView.tableColumns enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop) {
NSTableColumn* column = (NSTableColumn*) obj;
CGFloat width = 0;
for (int row = 0; row < tableView.numberOfRows; row++) {
NSView* view = [tableView viewAtColumn: idx
row: row
makeIfNecessary: YES];
NSSize size = [[view cell] cellSize];
width = MAX(width, MIN(column.maxWidth, size.width));
}
column.width = width;
}];
}
I found the following to work for me (written in Swift):
func resizeColumn(columnName: String) {
var longest:CGFloat = 0 //longest cell width
let columnNumber = tableView.columnWithIdentifier(columnName)
let column = tableView.tableColumns[columnNumber] as! NSTableColumn
for (var row = 0; row < tableView.numberOfRows; row++) {
var view = tableView.viewAtColumn(columnNumber, row: row, makeIfNecessary: true) as! NSTableCellView
var width = view.textField!.attributedStringValue.size.width
if (longest < width) {
longest = width
}
}
column.width = longest
viewTable.reloadData()
}
Given a column's identifier as an argument, it finds the cell in that column with the longest content, and resizes the column to fit that content.
I needed to do this very thing and it turned out (for me at least) to be a bit more involved; this isn't perfect but pretty close ;-)
p.s. Edited to handle image based cells
#define GBLBIN(x) [[NSUserDefaults standardUserDefaults] boolForKey:(x)]
- (IBAction)autosizeColumns:(id)sender
{
NSMutableArray * widths = [NSMutableArray array];
NSIndexSet * rows = [table selectedRowIndexes];
NSArray * columns = [table tableColumns];
BOOL deslectAll = NO;
NSNumber * col = nil;
int i, j;
// If no row's selected, then select all, the default
if (![rows count])
{
[table selectAll:sender];
rows = [table selectedRowIndexes];
deslectAll = YES;
}
// Use the selected rows from which we'll use the columns' formatted data widths
for (i=[rows lastIndex]; i != NSNotFound; i=[rows indexLessThanIndex:i])
{
for (j=0; j<[columns count]; j++)
{
NSTableColumn * column = [columns objectAtIndex:j];
id item = [[table dataSource]
tableView:table objectValueForTableColumn:column row:i];
NSCell * cell = [column dataCell];
float width, minWidth=10;
// Depending on the dataCell type (image or text) get the 'width' needed
if ([cell isKindOfClass:[NSTextFieldCell class]])
{
NSFont * font = ([cell font]
? [cell font] : [NSFont controlContentFontOfSize:(-1)]);
NSDictionary * attrs =
[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
NSFormatter * formatter = [cell formatter];
NSString * string = item;
// We want a string, as IB would have formatted it...
if (![item isKindOfClass:[NSString class]])
if (formatter)
string = [formatter stringForObjectValue:item];
else
string = [NSString stringWithFormat:#"%#", item];
width = [string sizeWithAttributes:attrs].width + minWidth;
}
else
{
// We have NSButtonCell, NSImageCell, etc; get object's width
width = MAX(minWidth,[[cell image] size].width);
}
// First time seeing this column, go with minimum width
if (j == [widths count])
{
[widths addObject:[NSNumber numberWithFloat:minWidth]];
}
col = [widths objectAtIndex:j];
width = MAX([col floatValue], width);
[widths replaceObjectAtIndex:j withObject:[NSNumber numberWithInt:width]];
}
}
// Now go resize each column to its minimum data content width
for (j=0; j<[widths count]; j++)
{
NSTableColumn * column = [columns objectAtIndex:j];
float width = [[widths objectAtIndex:j] floatValue];
if (GBLBIN(#"debug"))
{
NSLog(#"col:%d '%#' %.f", j, [column identifier], width);
}
[column setWidth:width];
}
// Undo select all if we did it
if (deslectAll)
{
[table deselectAll:sender];
}
}
Here are some extension methods to size columns by their content in C# for Monomac developers.
NOTE: this sizes columns by their content only, other considerations no considered (empty cols are 10px wide for eg).
Thanks to slashlos for the obj-c snippet.
public static void SizeColumnsByContent(this NSTableView tableView, int minWidth = 10) {
Contract.Requires(tableView != null);
Contract.Requires(tableView.DataSource != null);
var columns = tableView.TableColumns();
var widths = new List<int>();
for (int row=0; row< tableView.RowCount; row++) {
for (int col=0; col < tableView.ColumnCount; col++) {
// Determine what the fit width is for this cell
var column = columns[col];
var objectValue = tableView.DataSource.GetObjectValue(tableView, column, row);
var width = column.DataCell.DetermineFitWidth(objectValue, minWidth);
// Record the max width encountered for current coolumn
if (row == 0) {
widths.Add(width);
} else {
widths[col] = Math.Max(widths[col], width);
}
}
// Now go resize each column to its minimum data content width
for (int col=0; col < tableView.ColumnCount; col++) {
columns[col].Width = widths[col];
}
}
}
public static int DetermineFitWidth(this NSCell cell, NSObject objectValueAtCell, int minWidth = 10) {
int width;
if (cell is NSTextFieldCell) {
var font = cell.Font ?? NSFont.ControlContentFontOfSize(-1);
var attrs = NSDictionary.FromObjectAndKey(font, NSAttributedString.FontAttributeName);
// Determine the text on the cell
NSString cellText;
if (objectValueAtCell is NSString) {
cellText = (NSString)objectValueAtCell;
} else if (cell.Formatter != null) {
cellText = cell.Formatter.StringFor(objectValueAtCell).ToNSString();
} else {
cellText = objectValueAtCell.Description.ToNSString();
}
width = (int)cellText.StringSize(attrs).Width + minWidth;
} else if (cell.Image != null) {
// if cell has an image, use that images width
width = (int)Math.Max(minWidth, (int)cell.Image.Size.Width);
} else {
// cell is something else, just use its width
width = (int)Math.Max(minWidth, (int)cell.CellSize.Width);
}
return width;
}
I had trouble implementing some of the other solutions... But I managed to get columns to automatically resize width when cells are created using the cell's string value. Seems to work pretty well so far!
- (id)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *identifier = [tableColumn identifier];
NSUInteger col = [keys indexOfObject:identifier];
// Get an existing cell with the MyView identifier if it exists
NSTextField *cell = [tableView makeViewWithIdentifier:identifier owner:self];
// Get cell data
NSDictionary *dict = [self.tableContents objectAtIndex:row];
NSString *stringValue = [dict objectForKey:[keys objectAtIndex:col]];
// There is no existing cell to reuse so create a new one
if (cell == nil) {
// Create the new NSTextField with a frame of the {0,0} with the width of the table.
// Note that the height of the frame is not really relevant, because the row height will modify the height.
NSRect rect = NSRectFromCGRect(CGRectMake(0, 0, 64, 24));
cell = [[NSTextField alloc] initWithFrame:rect];
[cell setBordered:NO];
[cell setBackgroundColor:[NSColor clearColor]];
[cell setAutoresizesSubviews:YES];
// autosize to fit width - update column min width
NSRect rectAutoWidth = [stringValue boundingRectWithSize:(CGSize){CGFLOAT_MAX, 24} options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:cell.font}];
if ( tableColumn.minWidth < rectAutoWidth.size.width )
{
[tableColumn setMinWidth:rectAutoWidth.size.width+20]; // add padding
}
// The identifier of the NSTextField instance is set to MyView. This allows the cell to be reused.
cell.identifier = identifier;
}
// result is now guaranteed to be valid, either as a reused cell or as a new cell, so set the stringValue of the cell to the nameArray value at row
// set value
cell.stringValue = stringValue;
// Return the result
return cell;
}

Drawing in Cocoa, wrong xy value

I'm trying to draw a bunch of rectangles in an NSView which is inside an NSScrollView. The code is working pretty well, but it seems like the computer is drawing at the wrong points.
When I use NSLog() to log the current x and y the values are right, but when I look at my view, there is extra space added before the drawing coordinate.
This is the code:
//
// CoverFlowView.m
// iWatch
//
// Created by ief2 on 03/11/10.
//
//
#import "CoverFlowView.h"
#pragma mark Defaults
#define COVER_HEIGHT2WIDTH(a) ((float)a / 1.4)
#define COVER_HEIGHT (200.0)
#define COVER_WIDTH (COVER_HEIGHT2WIDTH(COVER_HEIGHT))
#define COVER_SPACE_MIN (20.0)
#pragma mark Private Methods
#interface CoverFlowView (PrivateMethods)
- (CGFloat)coverSpaceForMoviesPerLine:(NSUInteger *)n;
- (void)frameDidChange;
- (void)updateFrame;
#end
NSColor *RandomColor() {
float red = (float)(rand() % 255) / 254.0;
float green = (float)(rand() % 255) / 254.0;
float blue = (float)(rand() % 255) / 254.0;
NSColor *theColor = [[NSColor colorWithDeviceRed:red
green:green
blue:blue
alpha:1.0] retain];
return [theColor autorelease];
}
#pragma mark Implementation
#implementation CoverFlowView
#pragma mark Init and Dealloc
- (void)awakeFromNib {
srand(time(NULL));
NSMutableArray *colors = [[NSMutableArray alloc] initWithCapacity:100];
register int i;
for(i = 0; i < 100; i++) {
[colors addObject:RandomColor()];
}
self.movies = colors;
[colors autorelease];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(frameDidChange)
name:NSViewFrameDidChangeNotification
object:self];
}
- (id)initWithFrame:(NSRect)frame movies:(NSArray *)movs {
self = [super initWithFrame:frame];
if(self != nil) {
self.movies = movs;
// FIXME: Debug color array to movie
NSMutableArray *colors = [[NSMutableArray alloc] initWithCapacity:100];
register int i;
srand((unsigned int)time(NULL));
for(i = 0; i < 100; i++) {
[colors addObject:RandomColor()];
}
}
return self;
}
- (void)dealloc {
[_movies release];
[super dealloc];
}
#pragma mark Drawing
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor blackColor] setFill];
NSRectFill(dirtyRect);
NSUInteger moviesPerLine = 0;
CGFloat coverSpace = 0;
// Get the values
coverSpace = [self coverSpaceForMoviesPerLine:&moviesPerLine];
// if it's less than 1, break the drawing
if(moviesPerLine < 1) {
return;
}
// get the rows not to draw
// p = top left point's y
// h = movie cover height ->\
// s = space between -->The movies height and it's space above it
//
// p
// f(x) = -------
// h + s
double rows = ((dirtyRect.origin.y) / (COVER_HEIGHT + coverSpace));
// get the first movie that has to be drawn
// could be half-draw
//
// (NSUInteger)rows * moviesPerLine
NSUInteger index = (NSUInteger)rows * moviesPerLine;
if(!(index < [self.movies count]))
return;
// variable declaration
CGFloat curY;
CGFloat curX;
// start drawing until the line after max
// start the loop
do {
NSUInteger rowIndex;
// get the y for the movie
// x = movie index (start from 1)
// m = movies per row
// h = movie height
// s = movie space
//
// [x - (x % m)]
// f(x) = (|-----------|) * (h + s) + s
// [ m ]
// BUT: if rows == 2, rows -= 1
rowIndex = ((index + 1) - ((index + 1) % moviesPerLine)) / moviesPerLine;
((index + 1) % moviesPerLine == 0) ? (rowIndex -= 1) : (rowIndex = rowIndex);
curY = rowIndex * (COVER_HEIGHT + coverSpace) + coverSpace;
// get the x fot the movie
// x = movie index (start from 1)
// m = movies per line
// w = movie width
// s = cover space
//
//
// f(x) = ((x - (x - (x % m))) - 1) * (w + s) + s
// BUT: if row == 0, row = m
//
rowIndex = (index+1) - (((index+1) - ((index + 1) % moviesPerLine)));
(rowIndex == 0) ? (rowIndex = moviesPerLine) : (rowIndex = rowIndex);
curX = (rowIndex - 1) * (COVER_WIDTH + coverSpace) + coverSpace;
NSLog(#"%s index: %3u || x: %5.0f || y: %5.0f", __FUNCTION__, index, curX, curY);
// Start the drawing
NSRect bezierPathRect = NSMakeRect(curX + coverSpace, curY, COVER_WIDTH, COVER_HEIGHT);
NSBezierPath *path = [NSBezierPath bezierPathWithRect:bezierPathRect];
[path setLineWidth:2.0];
[[NSColor whiteColor] setStroke];
[(NSColor *)[self.movies objectAtIndex:index] setFill];
[path fill];
[path stroke];
// add up values
index++;
if(!(index < [self.movies count]))
return;
} while(curY - (COVER_HEIGHT + coverSpace) < dirtyRect.origin.y + dirtyRect.size.height);
}
- (BOOL)isFlipped {
return YES;
}
#pragma mark Private Methods
- (void)frameDidChange {
}
- (CGFloat)coverSpaceForMoviesPerLine:(NSUInteger *)n {
// x = number of covers
// w = view width
// m = cover width
// y = the space
//
// w - (mx)
// f(x) = --------
// x + 1
CGFloat m = COVER_WIDTH;
CGFloat w = [self bounds].size.width;
register NSUInteger x = 1;
CGFloat space;
while(1) {
space = ((w-(m*x)) / ( x + 1.0));
if(space < COVER_SPACE_MIN) {
x--;
space = ((w - (m*x)) / (x + 1.0));
break;
}
x++;
}
if(n) *n = x;
return space;
}
#pragma mark Properties
#synthesize movies=_movies;
- (void)setMovies:(NSArray *)ar {
if(ar != _movies) {
[_movies release];
_movies = [ar retain];
// Update frame size
NSUInteger m;
CGFloat space = [self coverSpaceForMoviesPerLine:&m];
NSUInteger lines = [ar count] / m;
CGFloat height = (COVER_HEIGHT + space) * ((CGFloat)lines) + space;
CGFloat width = [self frame].size.width;
NSRect frame = [self frame];
frame.size = NSMakeSize(width, height);
[self setFrame:frame];
[self setNeedsDisplay:YES];
}
}
#end
and here you can find a screen shot of the running application:
If someone can tell me what I'm doing wrong, I'd really appreciate it!
Thank you,
ief2
EDIT: Don't mind the wrong titles, I made mistake giving the files a name :-)
Seems to me you’re calculating the X coordinate the wrong way. You have twice the spacing at the beginning of the line and no space at the end of the line. So if you subtract one spacing from curX you get the right result:
curX = (rowIndex - 1) * (COVER_WIDTH + coverSpace);
Also your code is more complicated than it needs to be. For example, you don’t really need a loop in coverSpaceForMoviesPerLine: and the loop in drawRect: can be simplified (and made more efficient at the same time) too.