Swift 2 Xcode - CGRect (moving in frame) on UIImageView - ios

So I've got three images that I want arranged randomly. I'm using the origin of the three objects for the base point. I've tried a few different things, but they won't budge.
It's important to note that I have constraints on these images. They have a padding on top & bottom with a padding of 0 on the sides. They are also constrained to be the same size, in case constraint settings limit the movement of objects.
//the starting positions of the objects
spot_1 = img1.frame.origin
spot_2 = img2.frame.origin
spot_3 = img3.frame.origin
I've tried a few different tweaks on these sets of code:
img2.frame = CGRect(x: spot_1.x, y: spot_1.y, width: box_width, height: box_width)
img3.frame = CGRect(x: spot_2.x, y: spot_2.y, width: box_width, height: box_width)
img1.frame = CGRect(x: spot_3.x, y: spot_3.y, width: box_width, height: box_width)
&
img2.frame = spot_1
img3.frame = spot_2
img1.frame = spot_3
I've tried animating, different versions of CGrect etc. Can't get it figured out. Thank you for any help!

Trying to use both (Auto Layout) constraints and frames just isn't compatible. Constraints determine and set a view's frame. You shouldn't programmatically change the frame.
If you want to move a constrained object, what you would do is programmatically modify its position constraint(s).
You can programmatically change a constraint by adding an IBOutlet and connecting the Storyboard constraint to your property.
#IBOutlet weak var image1LeadingSpaceConstraint: NSLayoutConstraint!
Then in code, you can change the constraint's constant to set a new margin for your image
image1LeadingSpaceConstraint.constant = ...
You can even animate your change so the image animates to its new location
[UIView animateWithDuration:2.0 animations:^{
[self.view layoutIfNeeded];
}];

Related

Unable to minimize subview alongside mainView during animation

I have two views, videoView (which is the mainView) and subVideoView (which is the subView of mainView).
I am trying to minimize both views using animation at the same time, as shown in the below code. I am able to minimize the videoView (i.e mainView) and not the subVideoView.
However, when I hide code for minimising videoView (i.e mainView), I am able to minimise the subVideoView.
I believe it has to do something with the way I am animating.
Can some one please advice how I can minimise both views (proportionally) with animation at the same time and end up with the below result.
RESULTS OF EXISTING CODE
func minimiseOrMaximiseViews(animationType: String){
UIView.animate(withDuration: 0.5, delay: 0, options: [],
animations: { [unowned self] in
switch animationType {
case "minimiseView" :
// Minimising subVideoView
self.subVideoView.frame = CGRect(x: self.mainScreenWidth - self.minSubVideoViewWidth - self.padding,
y: self.mainScreenHeight - self.minSubVideoViewHeight - self.padding,
width: self.minSubVideoViewWidth,
height: self.minSubVideoViewHeight)
// Minimising self i.e videoView
self.frame = CGRect(x: self.mainScreenWidth - self.videoViewWidth - self.padding,
y: self.mainScreenHeight - self.videoViewHeight - self.padding,
width: self.videoViewWidth,
height: self.videoViewHeight)
self.layoutIfNeeded()
case "maximiseView":
// Maximising videoView
self.frame = CGRect(x: 0, y: 0, width: self.mainScreenSize.width, height: self.mainScreenSize.height)
// Maximising subVideoView
self.subVideoView.frame = CGRect(x: self.mainScreenWidth - self.maxSubVideoViewWidth - self.padding,
y: self.mainScreenHeight - self.maxSubVideoViewHeight - self.padding - self.buttonStackViewBottomPadding - buttonStackViewHeight,
width: self.maxSubVideoViewWidth,
height: self.maxSubVideoViewHeight)
default:
break
}
As per your requirement add subViews in mainView and set
mainView.clipsToBounds = true
and In the minimiseOrMaximiseViews method you just need to manage Y axis of mainView for show and hide.
I could minimise both views simultaneously using animation, when I've set both views (videoView and subVideoView) as subViews of window i.e UIApplication.shared.keywindow.
Unless I'm missing something, this kind of animation is much more easily achieved by animating the transform property of the videoView (the main view). You will need to concatenate scaling and translation transforms for that, because scaling is by default applied to a view's center.
The trick is that applying a transform to a view affects all its subviews automatically, e.g. applying a scaling transform to a view scales both the view and all its subviews.
To make a reverse animation just set videoView.transform = CGAffineTransformIdentity inside an animation block.
Caveat: You should be careful, though, with the frame property of a transformed view. The frame property is synthetic and is derived from bounds, center and transform. This means that setting a view's frame resets its transform property. In other words, if you manipulate your views using transform, you most likely want to avoid setting their frames directly.

UIScrollview zoomToRect center of currently visible view

I have a UIScrollView displaying a image. I want to programmatically zoom in to the a rect somewhere near the center (doesn't have to be exact) of the currently visible area. How would I get the coordinates of this rect to use with zoomToRect? Note that this image could be already zoomed in and only showing a fraction of the scrollView content area.
The the X and Y position of that image are relative to the scrollview's contentSize. The area shown on screen is defined by the scrollview's contentOffset.
You then take the position of your scrollview on screen and the position of the selection rectangle on your screen.
Finally you need to do rather simple maths (a few additions/subtractions) for both X and Y using the above values.
Grab UIImageView's frame and call insetBy(dx:dy:):
Returns a rectangle that is smaller or larger than the source
rectangle, with the same center point.
From Apple Documentation
Here's a quick visualisation in a PlayGround:
let blueSquare = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let yellowSquare = UIView(frame: blueSquare.frame.insetBy(dx: 100, dy: 100))
blueSquare.backgroundColor = .blue
yellowSquare.backgroundColor = .yellow
blueSquare.addSubview(yellowSquare)
Will result in this:

Getting correct frame after setting constraint

I have a method that creates a mask layer based on the current frame of my UIImageView, which works as I expected. However, in one case, I modify the height constraint of a UIImageView in the viewWillAppear method, and then apply the mask creator to the UIImageView, which creates a mask with a worng size. So my question is, how can I force to get the modified correct frame of that UIImageView?
Right now I'm getting the height 140 insted of 105, but if I use layoutIfNeeded after setting the constraint it become 118, which is closer, but still not 105.
Updated:
The method that I use for creating the mask:
public func setupMaskForImageView() {
let mask = CALayer()
let resizedMaskImage = resizeImage(UIImage(named: imageMaskName)!, newSize: frame.size)
mask.contents = resizedMaskImage
mask.frame = CGRectMake(0, 0, frame.size.width, frame.size.height)
layer.mask = mask
layer.masksToBounds = true
}
and I modified the constraint in viewWillAppear with
imageHeight.constant = UIScreen.mainScreen().bounds.width / 2.5
It's always ideal to keep your framing kept in layoutSubiews. From the Apple developer UIKit doc:
"layoutSubviews - Implement this method if you need more precise control over the layout of your subviews than either the constraint or autoresizing behaviors provide."
That being said, your best option is probably to create a subclass for your custom UIImageView and insert your reframing logic in layoutSubviews. This will ensure your view and mask are always properly framed.

Parallax effect with UIScrollView subviews

I'm trying to create a Parallax effect on a UIView inside a UIScrollView.
The effect seems to work, but not so well.
First i add two UIView sub-views to a UIScrollView and set the UIScrollViews contentSize.
The Views sum up and create a contentSize of {320, 1000}.
Then I implemented the following in scrollViewDidScroll:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat percentage = offsetY / scrollView.contentSize.height;
NSLog(#"percent = %f", percentage);
if (offsetY < 0) {
firstView.center = CGPointMake(firstView.center.x, firstView.center.y - percentage * 10);
} else if (offsetY > 0){
firstView.center = CGPointMake(firstView.center.x, firstView.center.y + percentage * 10);
}
}
These lines of code do create a parallax effect, but as the scrolling continues, the view does not return to it's original position if i scroll to the original starting position.
I have tried manipulating the views layers and frame, all with the same results.
Any Help will be much appreciated.
The problem you have is that you are basing your secondary scrolling on a ratio of offset to size, not just on the current offset. So when you increase from an offset of 99 to 100 (out of say 100) your secondary scroll increases by 10, but when you go back down to 99 your secondary scroll only decreases by 9.9, and is thereby no longer in the same spot as it was last time you were at 99. Non-linear scrolling is possible, but not the way you are doing it.
A possible easier way to deal with this is to create a second scrollview and place it below your actual scrollview. Make it non intractable (setUserInteractionEnabled:false) and modify it's contentOffset during the main scrolling delegate instead of trying to move a UIImageView manually.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[scrollView2 setContentOffset:CGPointMake(scrollView.contentOffset.x,scrollView.contentOffset.y * someScalingFactor) animated:NO];
}
But make sure not to set a delegate for the scrollView2, otherwise you may get a circular delegate method call that will not end well for you.
Scaling Factor being the key element...
...let me offer a 1:1 calculation:
Assuming 2 UIScrollView, one in the foreground and on in the rear, assuming the foreground controls the rear, and further assuming that a full width in the foreground corresponds to a full width in the background, you then need to apply the fore ratio, not the fore offset.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let foreSpan = foreScrolView.bounds.width - foreScrolView.contentSize.width
let foreRatio = scrollView.contentOffset.x / foreSpan
let rearSpan = rearScrollView.bounds.width - rearScrollView.contentSize.width
rearScrollView.setContentOffset(
CGPoint(x: foreRatio * rearSpan, y: 0),
animated: false)
}
Final effect
The two scrollers, fore and rear, each contain a UIImageView displayed at its full width:
let foreImg = UIImageView.init(image: UIImage(named: "fore"))
foreImg.frame = CGRect(x: 0, y: 0,
width: foreImg.frame.width,
height: foreScrolView.bounds.height)
foreScrolView.contentSize = foreImg.frame.size
foreScrolView.addSubview(foreImg)
let rearImg = UIImageView.init(image: UIImage(named: "rear"))
rearImg.frame = CGRect(x: 0, y: 0,
width: rearImg.frame.width,
height: rearScrollView.bounds.height)
rearScrollView.contentSize = rearImg.frame.size
rearScrollView.addSubview(rearImg)
This will scroll both images at a different speed, covering each image in full from edge to edge.
► Find this solution on GitHub and additional details on Swift Recipes.

iOS: Programatically align button x pixels from bottom of screen

I'm creating compatibility for iOS6 for an app made by someone else. I'm used to using making buttons/UI elements with autoresizing masks but I don't really know how they work when you're creating the button programatically.
For example:
- (UIButton*) createSampleButton {
UIButton* b = createSampleViewButton(CGRectMake(67, 270, 191, 45),
#"btn_shuffle",
#"btn_shuffle_active",
self,
#selector(sampleAction));
b.autoresizingMask = UIViewAutoresizingFlexibleTopMargin;
[self attachButton:b];
return b;
}
How can I change these buttons such that they'll be placed according to some scale/margin instead of arbitrarily choosing points until everything "looks right" ?
I was thinking of something like:
- (UIButton*) createSampleButton {
CGFloat height = self.bounds.size.height;
CGFloat bottomBound = 80;
UIButton* b = createSampleViewButton(CGRectMake(67, height-bottomBound, 191, 45),
#"btn_shuffle",
#"btn_shuffle_active",
self,
#selector(sampleAction));
[self attachButton:b];
return b;
}
This would guarantee me that the button is placed 80 points from the bottom of the screen every time right? Is there a more graceful or purposeful way of doing this?
The masks are the same as when created in IB or code. The thing you want to make sure to do in code though is make sure the frames are set properly proportioned once. In your case, yes you do want UIViewAutoResizingFlexibleTopMargin, and setting the correct y-value on the origin in terms of y = parentView.bounds.size.height - (x points as you described), is all you need to do.
EDIT:
According to your updated question, maybe this will help you. If the button has a constant size, set the frame to that size with CGPointZero as the origin when you create the button. If a UIView owns the button, then put this code in layoutSubviews. If a UIViewController owns the button, replace self.bounds with self.view.bounds and put this in view(Will/Did)LayoutSubviews (Assuming iOS5+).
// Aligning the button at it's current x value, current size, with its bottom border margin pizels from the bottom of the parent view.
CGFloat margin = 10;
CGRect buttonFrame = button.frame;
buttonFrame.origin.y = self.bounds.size.height - buttonFrame.size.height - margin;
button.frame = buttonFrame;
Also, define constant values at the top of the implementation file. Feel free to create convenience methods for readability (if you find this more readable and not doing too much on one line) such as
CGRect CGSetYInRect(CGFloat y, CGRect rect)
...
button.frame = CGSetYInRect(self.bounds.size.height - button.frame.size.height - margin, button.frame);
Use AutoResizing when appropriate to avoid explicit logic in layoutSubviews.
When you move to iOS 6 + only, use AutoLayout.

Resources