I pulled this code from a reputable tutorial, and I get a NaN error on the CGFloat instance *imageHeight when I run the application. However, if I change the type to NSUInteger, the app builds out just fine. Is there anyone who could potentially explain whats happening. I understand the NaN = Not A Number, but CGFloat should work fine as the data type.
Crashes
CGFloat imageHeight = self.mediaItem.image.size.height / self.mediaItem.image.size.width * CGRectGetWidth(self.contentView.bounds);
self.mediaImageView.frame = CGRectMake(0, 0, CGRectGetWidth(self.contentView.bounds), imageHeight);<--NaN ERROR is thrown here
Does Not Crash
NSUInteger imageHeight = self.mediaItem.image.size.height / self.mediaItem.image.size.width * CGRectGetWidth(self.contentView.bounds);
self.mediaImageView.frame = CGRectMake(0, 0, CGRectGetWidth(self.contentView.bounds), imageHeight);
Full method body
- (void) layoutSubviews {
[super layoutSubviews];
NSUInteger imageHeight = self.mediaItem.image.size.height / self.mediaItem.image.size.width * CGRectGetWidth(self.contentView.bounds);
self.mediaImageView.frame = CGRectMake(0, 0, CGRectGetWidth(self.contentView.bounds), imageHeight);
CGSize sizeOfUsernameAndCaptionLabel = [self sizeOfString:self.usernameAndCaptionLabel.attributedText];
self.usernameAndCaptionLabel.frame = CGRectMake(0, CGRectGetMaxY(self.mediaImageView.frame), CGRectGetWidth(self.contentView.bounds), sizeOfUsernameAndCaptionLabel.height);
CGSize sizeOfCommentLabel = [self sizeOfString:self.commentLabel.attributedText];
self.commentLabel.frame = CGRectMake(0, CGRectGetMaxY(self.usernameAndCaptionLabel.frame), CGRectGetWidth(self.bounds), sizeOfCommentLabel.height);
// Hide the line between cells
self.separatorInset = UIEdgeInsetsMake(0, CGRectGetWidth(self.bounds)/2.0, 0, CGRectGetWidth(self.bounds)/2.0);
}
The only likely way that you could get a NaN there is by computing 0 / 0, so I deduce that the height and width members of self.mediaItem.image.size are probably both zero. That is, self.mediaImage.image.size is probably CGSizeZero.
Note that if self.mediaItem is nil, or if self.mediaItem.image is nil, then self.mediaItem.image.size returns CGSizeZero. My guess is one of those is nil.
You can easily check this. Either put a breakpoint in layoutSubviews, or add an NSLog at the top of it:
NSLog(#"layoutSubviews: mediaItem=%p image=%p size=%#",
self.mediaItem, self.mediaItem.image,
NSStringFromCGSize(self.mediaItem.image.size));
Related
I'm building a magazine app via TextKit, here is a TextKit demo (check out the developer branch). It loads a NSAttributeString from a rtfd file as text storage object, all the pages have the same size with custom NSTextContainer object, the pagination feature is done.
When I tried to add image to the source rtfd file, the image attachment shows in UITextView directly without any additional code, that's great! However, some big images will be clipped by default in text view frame. I tried all kinds of delegate methods and override methods to resize and re-layout it but failed at the end.
- (void)setAttachmentSize:(CGSize)attachmentSize forGlyphRange:(NSRange)glyphRange
- (CGSize)attachmentSizeForGlyphAtIndex:(NSUInteger)glyphIndex;
The setter method is called in glyph layout process, the latter getter method is called in glyph draw process from the call stack.
- (BOOL)layoutManager:(NSLayoutManager *)layoutManager shouldSetLineFragmentRect:(inout CGRect *)lineFragmentRect lineFragmentUsedRect:(inout CGRect *)lineFragmentUsedRect baselineOffset:(inout CGFloat *)baselineOffset inTextContainer:(NSTextContainer *)textContainer forGlyphRange:(NSRange)glyphRange
{
NSTextAttachment *attachment = ...;
NSUInteger characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphRange.location];
UIImage *image = [attachment imageForBounds:*lineFragmentRect textContainer:textContainer characterIndex:characterIndex];
CGSize imageSize = GetScaledToFitSize(image.size, self.textContainerSize);
CGFloat ratio = imageSize.width / imageSize.height;
CGRect rect = *lineFragmentRect, usedRect = *lineFragmentUsedRect;
CGFloat dy = *baselineOffset - imageSize.height;
if (dy > 0) {
*baselineOffset -= dy;
usedRect.size.height -= dy;
usedRect.size.width = ratio * usedRect.size.height;
}
if (!CGRectContainsRect(usedRect, rect)) {
if (rect.size.height > usedRect.size.height) {
*baselineOffset -= rect.size.height - usedRect.size.height;
rect.size.height = usedRect.size.height;
rect.size.width = ratio * usedRect.size.height;
}
if (rect.size.width > usedRect.size.width) {
//...
}
}
*lineFragmentRect = rect;
*lineFragmentUsedRect = usedRect;
return YES;
}
This delegate method could resize the layout size but not affect to the final width and image scale. I tried serval solutions with no luck. It seems there aren't many threads about images on TextKit on SO and Apple example code.
I have ever done similar work for image attachment auto resize. How about handle it just after you get the attributed string.
That is, enumerate the original string with NSAttachmentAttributeName, replace the attachment with a subclass of NSTextAttachment, with implies NSTextAttachmentContainer protocol.
- (CGRect)attachmentBoundsForTextContainer:(nullable NSTextContainer *)textContainer proposedLineFragment:(CGRect)lineFrag glyphPosition:(CGPoint)position characterIndex:(NSUInteger)charIndex {
CGFloat lineWidth = CGRectGetWidth(lineFrag);
CGSize size = self.bounds.size;
size.height *= (size.width > 0) ? (lineWidth / size.width) : 0;
size.width = lineWidth;
return CGRectMake(0, 0, size.width, size.height);
}
Code above resize attachment to fit the width, you don't need to resize image, since it will be auto resize to the bound when drawing.
Hoping to be helpful.
I am new to Objective-C. I created an animation that move 3 buttons upwards. These buttons contain images. However, when this button animation occurs it resizes the button images and makes them HUGE. I tried to correct the code below to resize the button images, but I still get HUGE buttons. Can someone please help me? It should be Width:78 Height:76. I tried replacing width and height but it still doesn't work. Note: Just correct the code, I don't need a completely different answer.
-(IBAction)Search:(id)sender {
CGFloat screenWidth = self.view.bounds.size.width;
CGFloat screenHeight = self.view.bounds.size.height;
CGFloat normalizedX = (124 / 320); // You calculate these 'normalized' numbers, possibly from a designer's spec.
// it's the percent the amount should be over, as number from 0-1.
// This number is based on screen width of 320 having x 124 pt.
CGFloat startingX = normalizedX * screenWidth;
CGFloat startingY = (475 / 200) * screenHeight;
CGFloat width = (42 / 40) * screenWidth;
CGFloat height = (30 / 30) * screenHeight;
CGRect startingRect = CGRectMake(startingX, startingY, width, height);
self.button.frame = startingRect;
self.buttonTwo.frame = startingRect;
self.buttonThree.frame = startingRect;
// animate
[UIView animateWithDuration:0.75 animations:^{
CGFloat firstX = (13 / 770) * screenWidth;
CGFloat lowerY = (403 / 370) * screenHeight;
self.button.frame = CGRectMake(firstX, lowerY, width, height);
CGFloat secondX = (124 / 424) * screenWidth;
CGFloat upperY = (347 / 447) * screenHeight;
self.buttonTwo.frame = CGRectMake(secondX, upperY, width, height);
CGFloat thirdX = (232 / 680) * screenWidth;
self.buttonThree.frame = CGRectMake(thirdX, lowerY, width, height);
}];
}
Looks to me like your height and width math is wrong.
(42/40) * screenWidth will simplify to (1) * screenWidth, or the full width of the screen (The expression 42/40 will be done using integer math, resulting in 1.0. If it used floating point, you'd get 1.05 * screenWidth, which would make the images even bigger.)
You have a similar problem with your height calculation. You are setting the button to be the full height of the screen, and slightly wider.
I have an animation that moves three buttons upward on the screen programmatically. However, when I test it on the simulator for iPhone 6 or 4 the position of the buttons are all wrong (but are only right on the iPhone 5). How do I fix this. The buttons are built programmatically so I can't really use auto layout to position them on the view controller.
-(IBAction)Search:(id)sender {
self.button.frame = CGRectMake(124, 475, 78, 72);
self.buttonTwo.frame = CGRectMake(124, 475, 78, 76);
self.buttonThree.frame = CGRectMake(124, 475, 78, 76);
// animate
[UIView animateWithDuration:0.75 animations:^{
self.button.frame = CGRectMake(13, 403, 78, 72);
self.buttonTwo.frame = CGRectMake(124, 347, 78, 76);
self.buttonThree.frame = CGRectMake(232, 403, 78, 76);
You are using fixed values for frames, but the screens are different widths and/or heights.
If they are correct for iPhone 5-5s (Also known as iPhone 4") then one way to handle it is:
-(IBAction)Search:(id)sender {
CGFloat screenWidth = self.view.bounds.size.width;
CGFloat screenHeight = self.view.bounds.size.height;
CGFloat normalizedX = (124 / 320) // You calculate these 'normalized' numbers, possibly from a designer's spec.
// it's the percent the amount should be over, as number from 0-1.
// This number is based on screen width of 320 having x 124 pt.
CGFloat startingX = normalizedX * screenWidth;
CGFloat startingY = (475 / 588) * screenHeight;
CGFloat width = (78 / 320) * screenWidth;
CGFloat height = (72 / 588) * screenHeight;
CGRect startingRect = CGRectMake(startingX, startingY, width, height)
self.button.frame = startingRect;
self.buttonTwo.frame = startingRect;
self.buttonThree.frame = startingRect;
// animate
[UIView animateWithDuration:0.75 animations:^{
CGFloat firstX = (13 / 320) * screenWidth;
CGFloat lowerY = (403 / 588) * screenHeight;
self.button.frame = CGRectMake(firstX, lowerY, width, height);
CGFloat secondX = (124 / 320) * screenWidth;
CGFloat upperY = (347 / 588) * screenHeight;
self.buttonTwo.frame = CGRectMake(secondX, upperY, width, height);
CGFloat thirdX = (233 / 320) * ScreenWidth;
self.buttonThree.frame = CGRectMake(thirdX, lowerY, width, height);
}];
}
This will scale the size of everything up and keep the relative positions. Note UIButtons will have the same text size. You can play with these numbers until you get the effect you want. Hope this helps!
I'm using JTCalendar to build a custom calendar app, and it's set to scroll through the months horizontally by default. From my understanding, setting it to scroll vertically instead would entail laying out the contents (months) in a vertical fashion.
The author of JTcalendar suggested this, but it's unclear how exactly contentOffset should be modified for this purpose. Here are the functions that contain contentOffset:
JTCalendar.m:
- (void)scrollViewDidScroll:(UIScrollView *)sender
{
if(self.calendarAppearance.isWeekMode){
return;
}
if(sender == self.menuMonthsView && self.menuMonthsView.scrollEnabled){
self.contentView.contentOffset = CGPointMake(sender.contentOffset.x * calendarAppearance.ratioContentMenu, self.contentView.contentOffset.y);
}
else if(sender == self.contentView && self.contentView.scrollEnabled){
self.menuMonthsView.contentOffset = CGPointMake(sender.contentOffset.x / calendarAppearance.ratioContentMenu, self.menuMonthsView.contentOffset.y);
}
}
JTCalendarContentView.m:
- (void)configureConstraintsForSubviews
{
self.contentOffset = CGPointMake(self.contentOffset.x, 0); // Prevent bug when contentOffset.y is negative
CGFloat x = 0;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
for(UIView *view in monthsViews){
view.frame = CGRectMake(x, 0, width, height);
x = CGRectGetMaxX(view.frame);
}
self.contentSize = CGSizeMake(width * NUMBER_PAGES_LOADED, height);
}
In scrollViewDidScroll:
This line:
CGPointMake(sender.contentOffset.x * calendarAppearance.ratioContentMenu, self.contentView.contentOffset.y);
Should probably be something like this:
CGPointMake(sender.contentOffset.x, self.contentView.contentOffset.y * calendarAppearance.ratioContentMenu);
And this line:
self.menuMonthsView.contentOffset = CGPointMake(sender.contentOffset.x / calendarAppearance.ratioContentMenu, self.menuMonthsView.contentOffset.y);
Should probably be this:
self.menuMonthsView.contentOffset = CGPointMake(sender.contentOffset.x, self.menuMonthsView.contentOffset.y / calendarAppearance.ratioContentMenu);
In configureConstraintsForSubviews there are a few places that might need modifying. Not sure about the following line since it was set to fix a specific bug, so you could just comment it out for now and see what happens:
// Probably comment this out
self.contentOffset = CGPointMake(self.contentOffset.x, 0); // Prevent bug when contentOffset.y is negative
This block of code:
for(UIView *view in monthsViews){
view.frame = CGRectMake(x, 0, width, height);
x = CGRectGetMaxX(view.frame);
}
Should probably be something like this: (rename the x variable to y)
for(UIView *view in monthsViews){
view.frame = CGRectMake(0, y, width, height);
y = CGRectGetMaxY(view.frame);
}
Last, this line:
self.contentSize = CGSizeMake(width * NUMBER_PAGES_LOADED, height);
Should probably be:
self.contentSize = CGSizeMake(width, height * NUMBER_PAGES_LOADED);
I have not tested any of this but based on the code you posted and the fact that I have used JTCal in the past, this should put you on the right path.
I'm using PEPhotoCropEditor library in one of my iOS project. I able to having constant aspect ratio (1:1) with following code.
- (void)openEditor {
PECropViewController *controller = [[PECropViewController alloc] init];
controller.delegate = self;
controller.image = self.imageView.image;
UIImage *image = self.imageView.image;
CGFloat width = image.size.width;
CGFloat height = image.size.height;
CGFloat length = MIN(width, height);
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
controller.imageCropRect = CGRectMake((width - length) / 2,
(height - length) / 2,
length,
length);
// Restricted to a square aspect ratio
controller.keepingCropAspectRatio = YES;
controller.cropAspectRatio = 1.0;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentViewController:navigationController animated:YES completion:NULL];
}
But then I needed to have aspect ratio (height:width) of (1:3) instead of (1:1).
controller.cropAspectRatio = 1.0f / 3.0f;
I tried above and nothing happened. Still having same aspect ratio. Then I change below code too.
controller.imageCropRect = CGRectMake((width - length) / 2,
(height - length) / 2,
length * 3,
length);
Again nothing happened. After commenting above I hard coded the value like below.
controller.imageCropRect = CGRectMake(0.0f, 0.0f, 300.0f, 100.0f);
Then the cropping area displayed correctly but when resizing that aria will lead to destroy the crop aspect ratio (1:3)
Any idea how to remain aspect ratio (1:3) till all editing process complete?
Set this in ViewdidAppear in PECropViewController.m
self.cropAspectRatio = 1.0f;
self.keepingCropAspectRatio = YES;
And pass value to function setKeepingAspectRatio in PECropRectView.m
- (void)setKeepingAspectRatio:(BOOL)keepingAspectRatio
{
_keepingAspectRatio = keepingAspectRatio;
if (self.keepingAspectRatio) {
CGFloat width = CGRectGetWidth(self.bounds);
CGFloat height = CGRectGetHeight(self.bounds);
self.fixedAspectRatio = your_fixed_aspect_ratio_value;//fminf(width / height, height / width);
}
}
you will get expected result.