MPVolumeView Dynamic Width - objective-c

Im trying to do something very simple. Have a slider for volume that changes in width based on the devices' size or orientation.
- (void)viewDidLoad {
[super viewDidLoad];
[self setUpVolumeView];
}
- (void) setUpVolumeView {
// Volume control
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:self.myBoxForVolume.bounds];
[self.myBoxForVolume addSubview:volumeView];
//[volumeView sizeToFit];
}
I set the myBoxForVolume a certain distance from the left bound of the device's view and a certain distance from the right bound of the device's view in my storyboard. I would like the volumeView to have the same exact width as the myBoxForVolume (keeping in mind that myBoxForVolume can change in size), but for some reason I am unable to establish volumeView with the correct bounds.

Missing one line of code that mades all of this happen.
volumeView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
Add a line like that to the setUpVolumeView method solves this issue of resizing.

Related

UIVIew from XIB with Autolayout to UItableView Header

I am writing because I have a problem with the Auto Layout.
I'm trying to create a simple view in InterfaceBuilder with Auto Layout I want to load code and enter as a header of a table (not as header section). I explain briefly what are the characteristics.
The imageView must be square and must be as wide as the screen.
The space under the picture to the bottom of view that contains the button and label must be high 50 points.
Between image and button has to be a fixed distance of 12 points.
Between image and label must be a fixed distance of 13 points.
All these features are able to get them with Auto Layout. I added a constraint to the aspect ratio of the image (1: 1) and the various constraints for distances. all right.
The real problem is that by launching the app on iphone 6+ simulator (414 points of width), the image (with the label and button) goes above the cells.
Enabling various transparencies I noticed that the superView of Image View, only increase the width. It does not increase its height! How do I fix?
This is the code:
- (void)viewDidLoad{
//...
PhotoDetailsHeaderView *hView = (PhotoDetailsHeaderView *)[[[NSBundle mainBundle] loadNibNamed:#"PhotoDetailsHeaderView" owner:self options:nil] objectAtIndex:0];
hView.delegate = self;
self.tableView.tableHeaderView = hView;
//...
}
This is how I create the xib:
and this is how it is on the simulator, the green box is Uiimageview and the yellow box (under green box) is the mainview (or superview):
How can fix it?
Many thanks to all!
You'll need to add a property to store your PhotoDetailsHeaderView:
#property (nonatomic, strong) PhotoDetailsHeaderView *headerView;
Then calculate its expected frame in viewDidLayoutSubviews. If it needs updating, update its frame and re-set the tableHeaderView property. This last step will force the tableView to adapt to the header's updated frame.
- (void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
CGRect expectedFrame = CGRectMake(0.0,0.0,self.tableview.size.width,self.tableView.size.width + 50.0);
if (!CGRectEqualToRect(self.headerView.frame, expectedFrame)) {
self.headerView.frame = expectedFrame;
self.tableView.tableHeaderView = self.headerView;
}
}
The problem is probably that in iOS you have to reset the header of the table view manually (if it has changed its size). Try something along these lines:
CGRect newFrame = imageView.frame;
imageView.size.height = imageView.size.width;
imageView.frame = newFrame;
[self.tableView setTableHeaderView:imageView];
This code should be in -(void)viewDidLayoutSubviews method of your view controller.

CALayer cornerRadius has no effect on 4" device (simulator)

I work on dynamically, programmatically layout a view for 3.5" devices as well as for 4" devices.
As such that works fine.
But I want rounded corners so that my images appear like playing cards.
And I get rounded corners nicely displayed in 3,5 inch devices on the simulator for simulated iOS 6.1 and 7 alike.
But when I choose iPhone retina 4 inch on 6.1 or 7, then the UIImage in the UIImageView is fully displayed.
It works nicely on simulated iPad devices (in iPhone simulation mode - it is an iPhone only app).
As for today, I do not have any 4" device with me to test it. I can test on a device during the upcoming week.
Hiere is the relevant code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.imageV.image = self.image; // The image property was set by the caller.
// Layout imageV within self.view with a margin of MARGIN
self.imageV.frame = CGRectMake(self.view.frame.origin.x + MARGIN, self.view.frame.origin.y + MARGIN, self.view.frame.size.width - 2 * MARGIN, self.view.frame.size.height - 2 * MARGIN);
// set the raidus and the mask to follow the rounded corners.
self.imageV.layer.cornerRadius = CORNER_RADIUS;
self.imageV.layer.masksToBounds = YES;
}
BTW: CORNER_RADIUS is 18 and MARGIN is 15. Changing these values has no effect on the issue.
UPDATE: Thanks to Matt I figured out that the problem disappears when I create the UIImageView programmatically. That is some really nice workaround plus it points into the right diretion, I guess, but it is not a solution. Any ideas what setting in the storyboard editor might have caused the problem?
As far as I can see, auto layout is disabled for all view controllers in this storyboard.
The answer is simple. The code did work. It did add round corners to the UIImageView object and the maskToBounds worked well.
But the actual image displayed is smaller. I used AspectFit as mode to ensure that the actual image is not squeesed but displayed in its original aspect ration. Because of the longer layout of the iPhone5 dimensions the image only filled a part of its owning UIImageView. I changed the background color to gray for the screenshot and now it gets clear.
So the solution will be that I'll have to calculate the proper size of the image view so that it matches exactly the size of the scaled image. Then it should work.
(I'll update this answer when it is done).
Update: this is what I finally did: I removed the UIImageView from the Storyboard and deal with it programmatically.
Don't get confused by the complexity. I added another view just to throw a shadow, although this is not related to the original question. The shadow I wanted to add anyway. And it turned out that CALayer's shadow and masksToBounds=YES don't really agree on. That is why I added a regular UIView which lies in between the card view and the background view.
Finally this is so much of a hassle for displaying a simple rectangle image, that I think, just subclassing UIView and drawing everything with openGL or so directly into the CALayer would be probably much easier. :-)
Anyway, this is my code:
- (void)viewDidLoad {
[super viewDidLoad];
self.state = #0;
// Create an image view to carry the image with round rects
// and create a regular view to create the shadow.
// Add the shadow view first so that it appears behind
// the actual image view.
// Explanation: We need a separate view for the shadow with the same
// dimenstions as the imageView. This is because the imageView's image
// is rectangular and will only be clipped to round rects when the
// property masksToBounds is set to YES. But this setting will also
// clip away any shadow that the imageView's layer may have.
// Therfore we add a separate mainly empty UIView just behind the
// UIImageview to throw the shadow.
self.shadowV = [[UIView alloc] init];
self.imageV = [[UIImageView alloc] initWithImage:self.image];
[self.view addSubview:self.shadowV];
[self.shadowV addSubview:self.imageV];
// set the raidus and the mask to follow the rounded corners.
[self.imageV.layer setCornerRadius:CORNER_RADIUS];
[self.imageV.layer setMasksToBounds:YES];
[self.imageV setContentMode:UIViewContentModeScaleAspectFit];
// set the shadows properties
[self.shadowV.layer setShadowColor:[UIColor blackColor].CGColor];
[self.shadowV.layer setShadowOpacity:0.4];
[self.shadowV.layer setShadowRadius:3.0];
[self.shadowV.layer setShadowOffset:CGSizeMake(SHADOW_OFFSET, SHADOW_OFFSET)];
[self.shadowV.layer setCornerRadius:CORNER_RADIUS];
[self.shadowV setBackgroundColor:[UIColor whiteColor]]; // The view needs to have some content. Otherwise it is not displayed at all, not even its shadow.
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Just to be save
if (!self.image) {
return;
}
self.imageV.image = self.image; // The image property was set by the caller.
// Layout imageV within self.view with a margin of MARGIN
self.imageV.frame = CGRectMake(MARGIN, MARGIN, self.view.bounds.size.width - 2 * MARGIN, self.view.bounds.size.height - 2 * MARGIN);
// Calculate the size and position of the image and set the image view to
// the same dimensions
// This works under the assumption, that the image content mode is aspectFit.
// Well, as we are doing so much of the layout manually, it would work with a number of content modes. :-)
float imageWidth, imageHeight;
float heightWidthRatioImageView = self.view.frame.size.height / self.view.frame.size.width;
float heightWidthRatioImage = self.image.size.height / self.image.size.width;
if (heightWidthRatioImageView > heightWidthRatioImage) {
// The ImageView is "higher" than the image itself.
// --> The image width is set to the imageView width and its height is scaled accordingly.
imageWidth = self.imageV.frame.size.width;
imageHeight = imageWidth * heightWidthRatioImage;
} else {
// The ImageView is "wider" than the image itself.
// --> The image height is set to the imageView height and its width is scaled accordingly.
imageHeight = self.imageV.frame.size.height;
imageWidth = imageHeight / heightWidthRatioImage;
}
// Layout imageView and ShadowView accordingly.
CGRect imageRect =CGRectMake((self.view.bounds.size.width - imageWidth) / 2,
(self.view.bounds.size.height - imageHeight) / 2,
imageWidth, imageHeight);
[self.shadowV setFrame:imageRect];
[self.imageV setFrame:CGRectMake(0.0f, 0.0f, imageWidth, imageHeight)]; // Origin is (0,0) because it overlaps its superview which just throws the shadow.
}
And this is how it finally looks like:
The problem is due to some issue with code or configuration you have not told us about. Proof: I ran the following and it works fine. Note that I create the image view in code (to avoid the auto layout problem) and fixed your frame/bounds confusion, and that I've skipped your self.image, but none of that is really relevant to the issue you are seeing:
#define CORNER_RADIUS 18
#define MARGIN 15
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.imageV = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"im"]];
[self.view addSubview:self.imageV];
// Layout imageV within self.view with a margin of MARGIN
self.imageV.frame = CGRectMake(self.view.bounds.origin.x + MARGIN, self.view.bounds.origin.y + MARGIN, self.view.bounds.size.width - 2 * MARGIN, self.view.bounds.size.height - 2 * MARGIN);
// set the raidus and the mask to follow the rounded corners.
self.imageV.layer.cornerRadius = CORNER_RADIUS;
self.imageV.layer.masksToBounds = YES;
}
It works fine (and you can prove that to yourself). Here is a screen shot of the 4-inch simulator:
Therefore the problem is outside the code that you quote in your question, and cannot be analyzed without further information.

Autolayout: how does fittingSize work exactly?

I have created a subclass of NSTextField that changes its height according to the text it contains. I now want to insert it in another view (an NSTableCellView) and make the view resize according to the height of the text field.
I want to use the -(NSSize)fittingSize method of NSView but unfortunately it doesn't seem to call the fittingSize method of its subviews nor their intrinsicContentSize method.
Here is the code I use for the NSTableCellView subclass:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
self.expandingTextField = [[JSExpandingTextField alloc] init];
[self addSubview:self.expandingTextField];
[self removeConstraints:self.constraints];
NSDictionary *row = NSDictionaryOfVariableBindings(expandingTextField);
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|-20-[expandingTextField]-28-|"
options:0 metrics:nil views:row]];
}
return self;
}
- (NSSize)fittingSize
{
return [super fittingSize];
}
I override the fittingSize method here only to put a breakpoint or an NSLog.
Here is the code of the table view delegate that provides the height of the table cell:
- (JSDynamicTableCellView *)dummyCell
{
if (!_dummyCell) {
_dummyCell = [[JSDynamicTableCellView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 100, 100)];
}
return _dummyCell;
}
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
self.dummyCell.expandingTextField.stringValue = #"Test";
NSLog(#"Cell size %#",NSStringFromSize([self.dummyCell fittingSize]));
return [self.dummyCell fittingSize].height;
}
All of this always returns an height for dummyCell of 69 independent of the size of the expanding textfield in the cell.
The question is: how does the 'fittingSize' method figure out the size of its subviews? Should it call their 'fittingSize' or 'ntrinsicContentSize' methods or is it something else?
fittingSize is conceptually simple. It collects all of the constraints that have been added to your view or one of its subviews (recursively), and then it determines the size of the view based on only those constraints. You can think of it as determining the smallest size that is big enough to show the contents of that view hierarchy.
Edit:We need to be quite clear here. fittingSize returns minimum values and will return 0 for any dimension that is not fully specified. For example, if the vertical constraint tying a view to the bottom of its superview is omitted then the fitted height will be 0.
Edit: I just realized what you're probably running into: fittingSize is computing the text field as if it were just one long line, that does not wrap. You can confirm this by giving it a string value with one or more newlines: now the height should be bigger!
So how to fix this? Well, you have a text field, and you give it contents, and you want to know its height. But if the text field wraps, the height is going to depend on the width: make it narrower, and the text field will wrap to more lines, so it will consume more height. So in order to measure the preferred height for a wrapping text field, you have to tell it the width to wrap at.
On OS X Mountain Lion and iOS 6, you can do that with the [NSTextField setPreferredMaxLayoutWidth:] method. For example, if you want to compute the height based on a width of 100, you would call [textField setPreferredMaxLayoutWidth:100]; now fittingSize will report a height for the text field based on it wrapping at a width of 100.
By the way, this is a bad idea:
[self removeConstraints:self.constraints];
Because it removes constraints that other parts of the system have added. You should only ever remove a constraint that you created, either in code or in IB.
Try this (to do this click on the background of the xib or storyboard)

Dynamic UIPopoverController Size?

I have a UIPopoverController in my app which simply displays two UILabels beside each other with a list of words in each of them. However sometimes there are only a couple of words in each list meaning there is tons of blank space in the popover view.
How can I make it so that the popover view in at least height dynamically adapts to how many lines of words there are in my label?
Any help always appreciated, thanks.
If text in label is specified before popover shows, you can achieve this by using similar code in viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// ...
CGFloat height = [label.text sizeWithFont:label.font
forWidth:label.frame.size.width
lineBreakMode:label.lineBreakMode].height;
// This calculates only height of the label, you may want to add some margins, etc.
CGSize size = CGSizeMake(self.view.frame.size.width, height);
self.contentSizeForViewInPopover = size;
}

Synchronised scrolling between two instances of NSScrollView

I have two instances of NSScrollView both presenting a view on the same content. The second scroll view however has a scaled down version of the document view presented in the first scroll view. Both width and height can be individually scaled and the original width - height constraints can be lost, but this is of no importance.
I have the synchronised scrolling working, even taking into account that the second scroll view needs to align its scrolling behaviour based on the scaling. There's one little snag I've been pulling my hairs out over:
As both views happily scroll along the smaller view needs to slowly catch up with the larger view, so that they both "arrive" at the end of their document at the same time. Right now this is not happening and the result is that the smaller view is at "end-of-document" before the larger view.
The code for synchronised scrolling is based on the example found in Apple's documentation titled "Synchronizing Scroll Views". I have adapted the synchronizedViewContentBoundsDidChange: to the following code:
- (void) synchronizedViewContentBoundsDidChange: (NSNotification *) notification {
// get the changed content view from the notification
NSClipView *changedContentView = [notification object];
// get the origin of the NSClipView of the scroll view that
// we're watching
NSPoint changedBoundsOrigin = [changedContentView documentVisibleRect].origin;;
// get our current origin
NSPoint curOffset = [[self contentView] bounds].origin;
NSPoint newOffset = curOffset;
// scrolling is synchronized in the horizontal plane
// so only modify the x component of the offset
// "scale" variable will correct for difference in size between views
NSSize ownSize = [[self documentView] frame].size;
NSSize otherSize = [[[self synchronizedScrollView] documentView] frame].size;
float scale = otherSize.width / ownSize.width;
newOffset.x = floor(changedBoundsOrigin.x / scale);
// if our synced position is different from our current
// position, reposition our content view
if (!NSEqualPoints(curOffset, changedBoundsOrigin)) {
// note that a scroll view watching this one will
// get notified here
[[self contentView] scrollToPoint:newOffset];
// we have to tell the NSScrollView to update its
// scrollers
[self reflectScrolledClipView:[self contentView]];
}
}
How would I need to change that code so that the required effect (both scroll bars arriving at an end of document) is achieved?
EDIT: Some clarification as it was confusing when I read it back myself: The smaller view needs to slow down when scrolling the first view reaches the end. This would probably mean re-evaluating that scaling factor... but how?
EDIT 2: I changed the method based on Alex's suggestion:
NSScroller *myScroll = [self horizontalScroller];
NSScroller *otherScroll = [[self synchronizedScrollView] horizontalScroller];
//[otherScroll setFloatValue: [myScroll floatValue]];
NSLog(#"My scroller value: %f", [myScroll floatValue]);
NSLog(#"Other scroller value: %f", [otherScroll floatValue]);
// Get the changed content view from the notification.
NSClipView *changedContentView = [notification object];
// Get the origin of the NSClipView of the scroll view that we're watching.
NSPoint changedBoundsOrigin = [changedContentView documentVisibleRect].origin;;
// Get our current origin.
NSPoint curOffset = [[self contentView] bounds].origin;
NSPoint newOffset = curOffset;
// Scrolling is synchronized in the horizontal plane so only modify the x component of the offset.
NSSize ownSize = [[self documentView] frame].size;
newOffset.x = floor(ownSize.width * [otherScroll floatValue]);
// If our synced position is different from our current position, reposition our content view.
if (!NSEqualPoints(curOffset, changedBoundsOrigin)) {
// Note that a scroll view watching this one will get notified here.
[[self contentView] scrollToPoint: newOffset];
// We have to tell the NSScrollView to update its scrollers.
[self reflectScrolledClipView:[self contentView]];
}
Using this method the smaller view is "overtaken" by the larger view when both scrollers reach a value of 0.7, which is not good. The larger view then scrolls past its end of document.
I think you might be approaching this in the wrong way. I think you should be getting a percentage of how far down each scroll be is scrolled in relation to itself and apply that to the other view. One example of how this could be done is this way using NSScroller's -floatValue:
NSScroller *myScroll = [self verticalScroller];
NSScroller *otherScroll = [otherScrollView verticalScroller];
[myScroll setFloatValue:otherScroll.floatValue];
I finally figured it out. The answer from Alex was a good hint but not the full solution as just setting the float value of a scroller doesn't do anything. That value needs translation to specific coordinates to which the scroll view needs to scroll its contents.
However, due to differences in size of the scrolled document view, you cannot just simply use this value, as the scaled down view will be overtaken by the "normal" view at some point. This will cause the normal view to scroll past its end of document.
The second part of the solution was to make the normal sized view wait with scrolling until the scaled down view has scrolled its own width.
The code:
// Scrolling is synchronized in the horizontal plane so only modify the x component of the offset.
NSSize ownSize = [[self documentView] frame].size;
newOffset.x = MAX(floor(ownSize.width * [otherScroll floatValue] - [self frame].size.width),0);
The waiting is achieved by subtracting the width of the scroll view from the width times the value of the scroller. When the scaled down version is still traversing its first scroll view width of pixels, this calculation will result in a negative offset. Using MAX will prevent strange effects and the original view will quietly wait until the value turns positive and then start its own scrolling. This solution also works when the user resizes the app window.