How to center a subview of UIView - iphone

I have a UIView inside a UIViewm and I want the inner UIView to be always centered inside the outer one, without it having to resize the width and height.
I've set the struts and springs so that it's on top/left/right/bottom without setting the resize. But it still doesn't center. Any idea?

You can do this and it will always work:
child.center = [parent convertPoint:parent.center fromView:parent.superview];
And for Swift:
child.center = parent.convert(parent.center, from:parent.superview)

Objective-C
yourSubView.center = CGPointMake(yourView.frame.size.width / 2,
yourView.frame.size.height / 2);
Swift
yourSubView.center = CGPoint(x: yourView.frame.size.width / 2,
y: yourView.frame.size.height / 2)

Before we'll begin, let's just remind that origin point is the Upper Left corner CGPoint of a view.
An important thing to understand about views and parents.
Lets take a look at this simple code, a view controller that adds to it's view a black square:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
createDummyView()
super.view.backgroundColor = UIColor.cyanColor();
}
func createDummyView(){
var subView = UIView(frame: CGRect(x: 15, y: 50, width: 50 , height: 50));
super.view.addSubview(subView);
view.backgroundColor = UIColor.blackColor()
}
}
This will create this view:
the black rectangle origin and center does fit the same coordinates as it's parent
Now let's try to add subView another SubSubView, and giving subSubview same origin as subView, but make subSubView a child view of subView
We'll add this code:
var subSubView = UIView();
subSubView.frame.origin = subView.frame.origin;
subSubView.frame.size = CGSizeMake(20, 20);
subSubView.backgroundColor = UIColor.purpleColor()
subView.addSubview(subSubView)
And this is the result:
Because of this line:
subSubView.frame.origin = subView.frame.origin;
You expect for the purple rectangle's origin to be same as it's parent (the black rectangle) but it goes under it, and why is that?
Because when you add a view to another view, the subView frame "world" is now it's parent BOUND RECTANGLE, if you have a view that it's origin on the main screen is at coords (15,15) for all it's sub views, the upper left corner will be (0,0)
This is why you need to always refer to a parent by it's bound rectangle, which is the "world" of it's subViews, lets fix this line to:
subSubView.frame.origin = subView.bounds.origin;
And see the magic, the subSubview is now located exactly in it's parent origin:
So, you like "ok I only wanted to center my view by my parents view, what's the big deal?"
well, it isn't big deal, you just need to "translate" the parent Center point which is taken from it's frame to parent's bounds center
by doing this:
subSubView.center = subView.convertPoint(subView.center, fromView: subSubView);
You're actually telling him "take parents view center, and convert it into subSubView world".
And you'll get this result:

I would use:
self.childView.center = CGPointMake(CGRectGetMidX(self.parentView.bounds),
CGRectGetMidY(self.parentView.bounds));
I like to use the CGRect options...
SWIFT 3:
self.childView.center = CGPoint(x: self.parentView.bounds.midX,
y: self.parentView.bounds.midY);

1. If you have autolayout enabled:
Hint: For centering a view on another view with autolayout you can use same code for any two views sharing at least one parent view.
First of all disable child views autoresizing
UIView *view1, *view2;
[childview setTranslatesAutoresizingMaskIntoConstraints:NO];
If you are UIView+Autolayout or Purelayout:
[view1 autoAlignAxis:ALAxisHorizontal toSameAxisOfView:view2];
[view1 autoAlignAxis:ALAxisVertical toSameAxisOfView:view2];
If you are using only UIKit level autolayout methods:
[view1 addConstraints:({
#[ [NSLayoutConstraint
constraintWithItem:view1
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:view2
attribute:NSLayoutAttributeCenterX
multiplier:1.f constant:0.f],
[NSLayoutConstraint
constraintWithItem:view1
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:view2
attribute:NSLayoutAttributeCenterY
multiplier:1.f constant:0.f] ];
})];
2. Without autolayout:
I prefer:
UIView *parentView, *childView;
[childView setFrame:({
CGRect frame = childView.frame;
frame.origin.x = (parentView.frame.size.width - frame.size.width) / 2.0;
frame.origin.y = (parentView.frame.size.height - frame.size.height) / 2.0;
CGRectIntegral(frame);
})];

The easiest way:
child.center = parent.center

You can use
yourView.center = CGPointMake(CGRectGetMidX(superview.bounds), CGRectGetMidY(superview.bounds))
And In Swift 3.0
yourView.center = CGPoint(x: superview.bounds.midX, y: superview.bounds.midY)

Set this autoresizing mask to your inner view.

With IOS9 you can use the layout anchor API.
The code would look like this:
childview.centerXAnchor.constraintEqualToAnchor(parentView.centerXAnchor).active = true
childview.centerYAnchor.constraintEqualToAnchor(parentView.centerYAnchor).active = true
The advantage of this over CGPointMake or CGRect is that with those methods you are setting the center of the view to a constant but with this technique you are setting a relationship between the two views that will hold forever, no matter how the parentview changes.
Just be sure before you do this to do:
self.view.addSubview(parentView)
self.view.addSubView(chidview)
and to set the translatesAutoresizingMaskIntoConstraints for each view to false.
This will prevent crashing and AutoLayout from interfering.

Using the same center in the view and subview is the simplest way of doing it. You can do something like this,
UIView *innerView = ....;
innerView.view.center = self.view.center;
[self.view addSubView:innerView];

Another solution with PureLayout using autoCenterInSuperview.
// ...
UIView *innerView = [UIView newAutoLayoutView];
innerView.backgroundColor = [UIColor greenColor];
[innerView autoSetDimensionsToSize:CGSizeMake(100, 30)];
[outerview addSubview:innerView];
[innerView autoCenterInSuperview];
This is it how it looks like:

In c# or Xamarin.ios, we can use like this
imageView.Center = new CGPoint(tempView.Frame.Size.Width / 2,
tempView.Frame.Size.Height / 2);

This worked for me
childView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor).isActive = true
childView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor).isActive = true

I would use:
child.center = CGPointMake(parent.bounds.height / 2, parent.bounds.width / 2)
This is simple, short, and sweet. If you use #Hejazi's answer above and parent.center is set to anything other than (0,0) your subview will not be centered!

func callAlertView() {
UIView.animate(withDuration: 0, animations: {
let H = self.view.frame.height * 0.4
let W = self.view.frame.width * 0.9
let X = self.view.bounds.midX - (W/2)
let Y = self.view.bounds.midY - (H/2)
self.alertView.frame = CGRect(x:X, y: Y, width: W, height: H)
self.alertView.layer.borderWidth = 1
self.alertView.layer.borderColor = UIColor.red.cgColor
self.alertView.layer.cornerRadius = 16
self.alertView.layer.masksToBounds = true
self.view.addSubview(self.alertView)
})
}// calculation works adjust H and W according to your requirement

Related

Check if a collectionViewCell is showing more than 50% on collectionView

i got a UICollectionView with (3*3 Grid), so when i scroll up and down, is there a way i can detect if the collection view cell is displaying more than 50% of its height on the screen?
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.minX, y: visibleRect.midY)
let visibleIndexPath = collectionView.indexPathForItem(at: visiblePoint)
I have tried this but it doesn't seems to work. Does anyone has solution for this?
You can ask for
#property(nonatomic) CGPoint contentOffset;
of UIScrollView because UICollectionView's inherits from them.
and don't forget your Cell's have a CALayer that knows the visible Rectangle as well.
CGRect visiblerect = cell.layer.visibleRect;
if (visiblerect.size.height > (cell.frame.size.height * 0.5)) {
// do stuff when cell is more visible then half its size
}

Position element to CGRect with Safe Area

I´m declaring a button:
let button = UIButton(frame: CGRect(x: ?, y: ?, width: 50, height: 50))
But I´m note sure how to set x and y relative to the Safe Area so that it works good on both >= iOS 11 and <= iOS 10.
I tried to use self.view.safeAreaInsets but it returns 0.0 for some reason. Any ideas how to solve this?
For iOS 11:
self.view.safeAreaInsets will be zero if you call it on viewDidLoad, viewWillAppear, etc
It should have the value if you call it on viewDidLayoutSubviews, viewDidAppear
This would be far easier if you used autolayout and constraints, but you can get the margins of the safe area this way:
if #available(iOS 11.0, *) {
if let window = UIApplication.shared.keyWindow {
let topMargin = window.safeAreaInsets.top
let leftMargin = window.safeAreaInsets.left
}
}
I have same question when make animations.
Finally solved question, idea:create a frame equal safeArea frame.
(Device Orientaion only Left and Right)
CGRect safeAreaFrame;
if (#available(iOS 11.0, *)) {
UIEdgeInsets safeAreaInsets = self.view.safeAreaInsets;
safeAreaFrame = CGRectMake(safeAreaInsets.left,
safeAreaInsets.top,
self.view.frame.size.width - safeAreaInsets.left - safeAreaInsets.right,
self.view.frame.size.height - safeAreaInsets.top - safeAreaInsets.bottom);
} else {
safeAreaFrame = self.view.frame;
}
then you have safe area frame use, for example:
UIView *view = [[UIView alloc] initWithFrame:safeAreaFrame];
view.backgroundColor = UIColor.redColor;
[self.view addSubview:view];
Note: The safe area insets return (0, 0, 0, 0) in viewDidLoad,
hence they are set later than viewDidLoad,
put into viewSafeAreaInsetsDidChange or viewDidAppear.
I hope this will help you.

Borders not covering background

I've got a UILabel is using a border the same color as a background which it is half obscuring, to create a nice visual effect. However the problem is that there is still a tiny, yet noticeable, sliver of the label's background color on the OUTSIDE of the border.
The border is not covering the whole label!
Changing the border width doesn't change anything either, sadly.
Here's a picture of what's going on, enlarged so you can see it:
And my code follows:
iconLbl.frame = CGRectMake(theWidth/2-20, bottomView.frame.minY-20, 40, 40)
iconLbl.font = UIFont.fontAwesomeOfSize(23)
iconLbl.text = String.fontAwesomeIconWithName(.Info)
iconLbl.layer.masksToBounds = true
iconLbl.layer.cornerRadius = iconLbl.frame.size.width/2
iconLbl.layer.borderWidth = 5
iconLbl.layer.borderColor = topBackgroundColor.CGColor
iconLbl.backgroundColor = UIColor.cyanColor()
iconLbl.textColor = UIColor.whiteColor()
Is there something I'm missing?
Or am I going to have to figure out another to achieve this effect?
Thanks!
EDIT:
List of things I've tried so far!
Changing layer.borderWidth
Fussing around with clipsToBounds/MasksToBounds
Playing around the the layer.frame
Playing around with an integral frame
EDIT 2:
No fix was found! I used a workaround by extending this method on to my UIViewController
func makeFakeBorder(inputView:UIView,width:CGFloat,color:UIColor) -> UIView {
let fakeBorder = UIView()
fakeBorder.frame = CGRectMake(inputView.frame.origin.x-width, inputView.frame.origin.y-width, inputView.frame.size.width+width*2, inputView.frame.size.height+width*2)
fakeBorder.backgroundColor = color
fakeBorder.clipsToBounds = true
fakeBorder.layer.cornerRadius = fakeBorder.frame.size.width/2
fakeBorder.addSubview(inputView)
inputView.center = CGPointMake(fakeBorder.frame.size.width/2, fakeBorder.frame.size.height/2)
return fakeBorder
}
I believe this is the way a border is drawn to a layer in iOS. In the document it says:
When this value is greater than 0.0, the layer draws a border using the current borderColor value. The border is drawn inset from the receiver’s bounds by the value specified in this property. It is composited above the receiver’s contents and sublayers and includes the effects of the cornerRadius property.
One way to fix this is to apply a mask to a view's layer, but I found out that even if so we still can see a teeny tiny line around the view when doing snapshot tests. So to fix it more, I put this code to layoutSubviews
class MyView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let maskInset: CGFloat = 1
// Extends the layer's frame.
layer.frame = layer.frame.inset(dx: -maskInset, dy: -maskInset)
// Increase the border width
layer.borderWidth = layer.borderWidth + maskInset
layer.cornerRadius = bounds.height / 2
layer.maskToBounds = true
// Create a circle shape layer with true bounds.
let mask = CAShapeLayer()
mask.path = UIBezierPath(ovalIn: bounds.inset(dx: maskInset, dy: maskInset)).cgPath
layer.mask = mask
}
}
CALayer's mask

Change Height of UIProgressView in Swift

I've been trying to change the size and rotation of a progress view to adopt the size of the screen. It would basically function have a filling glass effect on the screen of the phone.
I rotated it fairly harmlessly by using
self.masteryProgress.transform = CGAffineTransformMakeRotation((CGFloat(-90) / CGFloat(180.0) * CGFloat(M_PI)))
I got the size of the view encasing the screen using view.bounds trying to get the rect and the width x height. So I'm not stuck there.
I tried using .frame .drawRect but when I use those it either breaks them or does nothing.
The height attribute seems to be locked at a constant value of 2 in interfaceBuilder.
Any solutions before I build a custom animation?
I tried setting the height of the progress bar in interfaceBuilder like some other posts said, but I need to have the progress view fill the whole screen no matter what device it's running on so that solution won't work in my case.
I ended up using
CGAffineTransformScale(masteryProgress.transform, view.bounds.height / 1334, (view.bounds.width / 2))
Since I'm rotating the progressView vertically, I set the progress view to a width of 1334 (the height of the iPhone 6 Plus) and a height of 2. By dividing the height of the screen by these values it should resize to any phone perfectly.
You can scale it by set its transform like so:
Swift 2
masteryProgress.transform = CGAffineTransformScale(masteryProgress.transform, 1, 20)
Swift 3、4
masteryProgress.transform = masteryProgress.transform.scaledBy(x: 1, y: 20)
I faced problem while making the UIProgressView as round rect one. While using only the transform
masteryProgress.transform = CGAffineTransformMakeScale(1, 4)
I achieved which was not my final requirement.
while my final expectation was something like the following
I finally achieved it by following two steps
1. Set Height Constraint from Interface Builder:
2. Create a custom class of UIProgressView and override func layoutSubviews() to add a custom mask layer:
override func layoutSubviews() {
super.layoutSubviews()
let maskLayerPath = UIBezierPath(roundedRect: bounds, cornerRadius: 4.0)
let maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = maskLayerPath.cgPath
layer.mask = maskLayer
}
And here is the final result
Transform still seems to be the only way to accomplish this. Try adding it as an extension to UIProgressView like so.
extension UIProgressView {
#IBInspectable var barHeight : CGFloat {
get {
return transform.d * 2.0
}
set {
// 2.0 Refers to the default height of 2
let heightScale = newValue / 2.0
let c = center
transform = CGAffineTransformMakeScale(1.0, heightScale)
center = c
}
}
}
If you added UIProgressView using Interface Builder, just create height constraint and change to value what you need. That's it.
This will work:
masteryProgress.transform = CGAffineTransformMakeScale(1, 4)
Some modification of 'Sauvik Dolui' answer:
In UIBezierPath you should set cornerRadius: like (self.frame.height / 2).
This is more universal solution than use (4.0).
override func layoutSubviews() {
super.layoutSubviews()
let maskLayerPath = UIBezierPath(roundedRect: bounds, cornerRadius: self.frame.height / 2)
let maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = maskLayerPath.cgPath
layer.mask = maskLayer
}
In Swift 3, it's changed to CGAffineTransform(scaleX: CGFloat, y: CGFloat). So code would be:
masteryProgress.transform = CGAffineTransform(scaleX: 1, y: 4)
In Swift version 3.0.2 use this:
masteryProgress.transform = masteryProgress.transform.scaledBy(x: 1, y: 5)
I'm experiencing same with Swift - UIProgressView fills diagonally
After scale progressbar with
CGAffineTransformMakeScale(1.0, 5.0)
bar animation becomes filling from left top corner to right bottom.
Putting the UIProgressView inside a UIStackView and setting the UIStackView's constraints might work as well. At least worked for me
I tried the accepted answer for Swift 5, had a label pinned to the bottom of the progressView and the label was oddly stretched. To change the height without any other issues I used it's heightAnchor to set it to the height I wanted.
1- create a programmatic UIProgressView:
lazy var progressView: UIProgressView = {
let progressView = UIProgressView(progressViewStyle: .default)
progressView.translatesAutoresizingMaskIntoConstraints = false
progressView.trackTintColor = .white
progressView.progressTintColor = .green
progressView.progress = 0
return progressView
}()
2- set the heightAnchor constraint:
progressView.heightAnchor.constraint(equalToConstant: 12).isActive = true
progressView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
progressView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
progressView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
It works for me:
progress.transform = progress.transform.scaledBy(x: 1, y: self.bounds.size.height)
"self.bounds.size.height" is the height you want
I gave it height constraint and it worked for me. I gave it 10 Constant. But if you are creating a universal app you can also use multipliers.

How to enable zoom in UIScrollView

How do I enable zooming in a UIScrollView?
Answer is here:
A scroll view also handles zooming and panning of content. As the user makes a pinch-in or pinch-out gesture, the scroll view adjusts the offset and the scale of the content. When the gesture ends, the object managing the content view should update subviews of the content as necessary. (Note that the gesture can end and a finger could still be down.) While the gesture is in progress, the scroll view does not send any tracking calls to the subview.
The UIScrollView class can have a delegate that must adopt the UIScrollViewDelegate protocol. For zooming and panning to work, the delegate must implement both viewForZoomingInScrollView: and scrollViewDidEndZooming:withView:atScale:; in addition, the maximum (maximumZoomScale) and minimum (minimumZoomScale) zoom scale must be different.
So:
You need a delegate that implements UIScrollViewDelegate and is set to delegate on your UIScrollView instance
On your delegate you have to implement one method: viewForZoomingInScrollView: (which must return the content view you're interested in zooming). You can also implement scrollViewDidEndZooming:withView:atScale: optionally.
On your UIScrollView instance, you have to set the minimumZoomScale and the maximumZoomScale to be different (they are 1.0 by default).
Note: The interesting thing about this is what if you want to break zooming. Is it enough to return nil in the viewForZooming... method? It does break zooming, but some of the gestures will be messed up (for two fingers). Therefore, to break zooming you should set the min and max zoom scale to 1.0.
Have a read through this Ray Wenderlich tutorial:
http://www.raywenderlich.com/76436/use-uiscrollview-scroll-zoom-content-swift
If you follow through the section 'Scrolling and Zooming a Larger Image' it will get a image up and enable you to pinch and zoom.
In case the link gets altered, here's the main info:
Put this code in your view controller (this sets the main functionality):
override func viewDidLoad() {
super.viewDidLoad()
// 1
let image = UIImage(named: "photo1.png")!
imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:image.size)
scrollView.addSubview(imageView)
// 2
scrollView.contentSize = image.size
// 3
var doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollViewDoubleTapped:")
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.numberOfTouchesRequired = 1
scrollView.addGestureRecognizer(doubleTapRecognizer)
// 4
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
let scaleHeight = scrollViewFrame.size.height / scrollView.contentSize.height
let minScale = min(scaleWidth, scaleHeight);
scrollView.minimumZoomScale = minScale;
// 5
scrollView.maximumZoomScale = 1.0
scrollView.zoomScale = minScale;
// 6
centerScrollViewContents()
}
Add this to the class:
func centerScrollViewContents() {
let boundsSize = scrollView.bounds.size
var contentsFrame = imageView.frame
if contentsFrame.size.width < boundsSize.width {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0
} else {
contentsFrame.origin.x = 0.0
}
if contentsFrame.size.height < boundsSize.height {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0
} else {
contentsFrame.origin.y = 0.0
}
imageView.frame = contentsFrame
}
And then this if you want the double tap gesture to be recognised:
func scrollViewDoubleTapped(recognizer: UITapGestureRecognizer) {
// 1
let pointInView = recognizer.locationInView(imageView)
// 2
var newZoomScale = scrollView.zoomScale * 1.5
newZoomScale = min(newZoomScale, scrollView.maximumZoomScale)
// 3
let scrollViewSize = scrollView.bounds.size
let w = scrollViewSize.width / newZoomScale
let h = scrollViewSize.height / newZoomScale
let x = pointInView.x - (w / 2.0)
let y = pointInView.y - (h / 2.0)
let rectToZoomTo = CGRectMake(x, y, w, h);
// 4
scrollView.zoomToRect(rectToZoomTo, animated: true)
}
If you want more detail read the tutorial, but that pretty much covers it.
Make sure you set your viewController as the scrollViews delegate and implement:
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
I don't think this is working for iOS 5.0 and Xcode 4.3+
Im looking for the same here, I found this its for images but it may help you.
http://www.youtube.com/watch?v=Ptm4St6ySEI