Creating a view in IOS 10 Swift 3 - uiview

Hi I want to create a view which looks something like below -
What I tried so far is I put two properties called image and text inside an UIView and tried to initialise the image view and text field and added in UIView I am able to see the image but not the text. May be I am missing proper constraints to put or something else. Can please someone help on what should be the best approach for this.
I would prefer it to be done without much involvement of storyboard i.e setting constraints for different views using storyboard. I am fine setting constraints in code. As you see it is a fairly reusable element and hence I want it as an class which can be assigned to any uiview and that should be it. Please correct me if something can be done better?
Thanks & Regards

Assumptions: the entire picture provided above is contained in a UIView and we are using Storyboard.
Refer to this picture for clarity in the explanation.
Make sure the outermost View has enough constraits so AutoLayout can properly size the view. If this doesn't happen, nothing past this point will matter.
Create 2 Views. Each has the same width as the container and is half the height. Place one in the upper half and one in the lower half. These are the ones with the blue and orange backgrounds.
Add an ImageView to the top View (blue background). Make the ImageView half the height of its Superview. Make the ImageView centered vertically in container. Add a constraint for the leading edge of the ImageView to be the same as the leading edge of the Superview margin. Add an AspectRatio constraint to the ImageView of 1:1.
Add a TextField to the top View (blue background). Center the TextField vertically in the container. Add a constraint for the HorizontalSpace between TextField leading and ImageView trailing and make the constant 8. Add a constraint for the TrailingSpace to the TextView for the SuperView trailing margin.
Change the Placeholder Text for the TextField to "Email"
Place the image in the imageview.
Repeat for the orange background View.
For the bottom borders, use this:
extension UIView{
func addBottomBorder(borderThickness: CGFloat, color: UIColor , widthPct: CGFloat) {
let border = UIView()
border.backgroundColor = color
border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
var x: CGFloat = 0
let width = self.frame.size.width * widthPct
if widthPct < 1{
x = (self.frame.size.width - width) / 2
}
border.frame = CGRect(x: x, y: self.frame.size.height - borderThickness, width: width, height: borderThickness);
self.addSubview(border)
}
}

Related

Swift programmatically place UIImageView on top of another and resize

On my storyboard I have 2 image views.
One holds the main image, the second is an overlay which will be used to specify the crop area of the main image.
In my viewDidLoad I've done this.
let screen_width = UIScreen.mainScreen().bounds.width
let screen_height = UIScreen.mainScreen().bounds.height
self.overlayImage.frame = CGRect(x: self.imageView.frame.origin.x, y: self.imageView.frame.origin.y, width: screen_width, height: (screen_height * 0.1919))
The goal is to have the overlayImage's top left corner lined up properly with the imageView's top left corner. Also the height of the overlay should be about 1/5th of the screens size. However when I run the code the overlayImage is exactly the same size and in the same location it was originally on the storyboard.
How can I programmatically line it up on top of the imageView after the image has been set to it, and also resize the overlayImage dynamically in the viewDidload?
I'd just do it manually in storyboard editor but everyone will have different screen sizes so I thought it best to use the mainscreen().bounds.height variable to determine the amount of height to use dynamically at runtime.
All you have to do is to write your above code in viewDidAppear instead of viewDidLoad as below:-
override func viewDidAppear(animated: Bool) {
let screen_width = UIScreen.mainScreen().bounds.size.width
let screen_height = UIScreen.mainScreen().bounds.size.height
self.overlayImage.frame = CGRect(x: 0, y: 0, width: screen_width, height:screen_height/5)
}
And your frame will set itself as desired.
Since you're working with Storyboard, I would rather use Autolayout and Constraints to solve this problem for me... For instance you set the constraints for your first image view, then set the constraints of your overlay based and related to the former one. Example:
Lower UIImageView: set leading, trailing, top and bottom constraints
Overlay: set center X and Y constraints to be equal to the lower Image
Overlay: set height and width to be proportional to the lower image
Thus, when ever the screen size changes, the two images will always resize equally...
Interface builder:
Set overlay top, leading, width to your image view.
give your overlay a constant height constraint; the constraint cosntant value is arbitrary because you will replace it at run time
ctrl + drag an IBOutlet from this height constraint to your viewcontroller
in your viewWillLayoutSubViews set the .constant of your height constraint to UIScreen.main.bounds.size.height / 5

Aligning the bounds of a UILabel proportionally to a sister UIView in the same subview

I have a static image with an area on it that is meant to display some multi-line text. Like a speech bubble from a character in a video game. I grabbed a stock image that looks like this:
I have a UIImageView set up to be aspect-fit (see this question) inside of a subview of the main view. I have a UILabel also set up inside of the subview, which will hold the multi-line text. I want to be able to move the subview around the screen and have it be any size and still have the UIImageView stay the same aspect and have the UILabel fit inside the bubble of the image.
I made a sample project which has this set up already.
The way I intend to keep the UILabel's bounds inside the speech bubble area is by setting up constraints that are proportional to the center x and y of the UIImageView. For my image, the left edge multiplier is 0.65, the right is 1.8, the top is 0.19 and the bottom is 0.63.
I wrote a couple functions that extend from UIView to confirm this:
/**
Draws a vertical line proportional to the center x of the view.
A `proportional` value of 0 is the left edge, while 2 is the right edge.
:param: proportion The value from the left edge (0.0) to the right edge (2.0)
:param: inColor The color to draw the line in (red by default)
*/
func drawVertLineAtProportion(proportion: CGFloat, inColor: UIColor = UIColor.redColor()) {
let size = self.frame.size
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
let x = self.bounds.origin.x + proportion*self.frame.size.width/2
var vertPath = UIBezierPath()
vertPath.lineWidth = size.height/150.0
vertPath.moveToPoint(CGPointMake(x, 0))
vertPath.addLineToPoint(CGPointMake(x, self.frame.size.height))
inColor.setStroke()
vertPath.stroke()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.addSubview(UIImageView(image: image))
}
And drawHorizLineAtProportion is similar, but for horizontal lines.
I can confirm that my multiplier values are correct by running these 4 lines in viewDidAppear:
imageView.drawVertLineAtProportion(0.65)
imageView.drawVertLineAtProportion(1.8)
imageView.drawHorizLineAtProportion(0.19)
imageView.drawHorizLineAtProportion(0.63)
And then the imageView looks like this:
I can change the size of the subView that contains the imageView to any size I want, and the imageView stays aspect-fit and the red box in the intersection of those red lines is always exactly what I want.
So how come when I set up the constraints of the edges of the UILabel to follow the same formula the edges don't line up?
It seems that when the width of the imageView is maxed the left and right edges are correct but the top and bottom are wrong:
And if the height is maxed, then the top and bottom are correct, but the left and right are wrong:
So how come the UILabel bounds aren't lining up with the red lines?
EDIT to specify that I know there is a runtime fix, but I want to know why storyboard doesn't work.
If I add these lines in viewDidAppear to fix the frame of the UILabel, it works:
let upperLeft: CGPoint = CGPointMake(imageView.frame.origin.x + 0.65*imageView.frame.size.width/2, imageView.frame.origin.y + 0.19*imageView.frame.size.height/2)
let lowerRight: CGPoint = CGPointMake(imageView.frame.origin.x + 1.8*imageView.frame.size.width/2, imageView.frame.origin.y + 0.63*imageView.frame.size.height/2)
let size: CGSize = CGSizeMake(lowerRight.x - upperLeft.x, lowerRight.y - upperLeft.y)
speechLabel.frame = CGRectMake(upperLeft.x, upperLeft.y, size.width, size.height)
But I still want to know why what I have set in storyboard doesn't work.
I figured out a solution, fully in storyboard, that gets me what I want, although I do believe my original question shows that there is a bug in Xcode.
I was noticing that since Xcode didn't seem to respect that I wanted to align speechLabel to the sister-UI element imageView, I just made the UIView I was aligning to no longer the imageView.
The solution I implemented was to put the imageView and speechLabel into a new parent view, which I call aspectFitView. All of the constraints that I put on the imageView to make it aspect-fit, I put on aspectFitView instead.
Then I set the constraints of imageView to make it the size of aspectFitView: align center x and y and equal width and height.
Now that the parent of speechLabel is the exact size, position, and aspect ratio of the imageView, I set up my proportional constraints for the speechLabel against the aspectFitView instead, and voila. It all works beautifully. It looks correct on my storyboard, I don't have to add any post-viewDidAppear frame-correcting code, and it works perfectly on any device I run it on.
I updated the sample project (Xcode 6.4) with the solution for anyone interested.

Circular Image distorting with Auto layout

I have my storyboard set to 4.7" while I work. When I run my app on the iP6 simulator my circular UIImageView looks nice and round. Like so:
However, I'm experiencing distortion with my circular UIImageView when I run the app in all the other simulators
Here's iP5:
and here's iP6+:
Here's the code i'm using to round the UIImageView:
profileImageView.layer.cornerRadius = profileImageView.frame.size.width / 2
profileImageView.clipsToBounds = true
The size of the UIImageView is adjusting correctly, just the roundness gets all funky as you can see. Here are the constraints I have set just in case:
1:1 ratio, align center x to view, align top to view = 15, align bottom to view = -100
The image starts as a perfect square, so I figured that with the 1:1 ratio set it would always stay circular with the code I used...what am I doing wrong here? Thanks so much!
I had this problem before and your code is 100% correct, the problem is with the autolayout and these constraints:
align top to view = 15, align bottom to view = -100
You can't align to top view and to bottom view because the screen size changes. What you can do is only align to top and find another constraint to maintain the size. What works for me with profile pictures is having a size constraint.
Before, with up and bottom constraints:
After, with up and fixed width constraints:
Where did you put that code?
The cornerRadius will not change when the view size changes. You need to update it (to match the view's size) in viewDidLayoutSubviews in your VC, or at the end of layoutSubviews in your view class.
try this definitely it will work.
swift code
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
perform(#selector(self.setCircleForImage(_:)), with: pickedImage, afterDelay: 0)
}
#objc func setCircleForImage(_ imageView : UIImageView){
imageView.layer.cornerRadius = pickedImage.frame.size.width/2
imageView.clipsToBounds = true
}
UIImage must maintain 1:1 aspect ratio

Constraining to center horizontally & vertically in superview prevents me from changing the frame

I have a UIButton within a UIView with two constraints. Both constraints were set in Interface Builder so that the UIButton is centered vertically and horizontally in its superview.
I wanted to add a border to the UIButton and make it rounded. Having followed an answer on SO I was able to get the button partially rounded.
For some reason though, the button is not really round. I assume this is because of my height & width not being equal to each other. Each time I set the width/height of the button to be equal, the autolayout constraints reset it back to 40x34.
I am using the following code to create the rounded button.
self.startButton.clipsToBounds = true
self.startButton.layer.cornerRadius = self.startButton.frame.height / 2
self.startButton.layer.borderWidth = 1
self.startButton.layer.borderColor = self.view.tintColor.CGColor
self.startButton.layer.shadowRadius = 6.0
self.startButton.layer.shadowColor = UIColor.blackColor().CGColor
self.startButton.layer.shadowOffset = CGSizeMake(0.0, 3.0)
self.startButton.layer.shadowOpacity = 0.65
Why does having my button centered vertically and horizontally prevent me from changing its size? I don't understand why I can't set the size of the button and have the constraints re-center it based on the size values I assign to it.
Edit
I have moved the code in to the viewDidLayoutSubviews and then modified the constraints so that both the height and width are constrained to 40x40.
override func viewDidLayoutSubviews() {
self.startButton.clipsToBounds = true
self.startButton.titleLabel?.text = "Start"
self.startButton.layer.cornerRadius = self.startButton.frame.height / 2
self.startButton.layer.borderWidth = 1
self.startButton.layer.borderColor = self.view.tintColor.CGColor
self.startButton.layer.shadowRadius = 6.0
self.startButton.layer.shadowColor = UIColor.blackColor().CGColor
self.startButton.layer.shadowOffset = CGSizeMake(0.0, 3.0)
self.startButton.layer.shadowOpacity = 0.65
}
This partially solves my problem; not fully though. If I do not assign a value to the titleLabel, then the button is a proper round button.
However, if I assign a value to the titleLabel, the button becomes a rounded rect and is not a circle.
The text does not appear in the button though, which confuses me. I need to have the button be a circle, scaling to fit the content it has as well. My button type is set to Custom. I don't know if that has any effect on this as well.
Edit 2
After doing some testing, I discovered i was setting the edge insets to the button. Once I removed the following:
self.startButton.contentEdgeInsets = UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25)
I got the desired effect with #Leo's answer.
You need to add constraints about height & width of your button.
Either fixed width & height,or aspect radio is ok.
If you use aspect radio,set up the corner radius in viewDidLayoutSubviews

Storyboard and autolayout: how make a circular image

in storyboard (xcode 6) i want a circular user image profile take from Facebook.
So i have make this interface in storyboard, using auto layout:
Then, using Facebook iOS sdk i take the user profile (using swift):
var facebookProfileUrl = "http://graph.facebook.com/\(userId!)/picture?type=normal";
In storyboard i have set the image to "Scale to fit" mode.
To make the image view circular i use the following code:
self.facebookProfileImage.layer.cornerRadius = self.facebookProfileImage.frame.size.width / 2;
self.facebookProfileImage.clipsToBounds = true;
When i run the code, anyway the image doesn't look circular:
I suppose the problem is auto layout but i'm not sure.
How can i make the image perfectly circular??
Two steps:
Center the UIImageView by adding a "Horizontal Center In Container" constraint (Editor > Align > Horizontal Center in Container) to the UIImageView.
Remove the leading and trailing constraints you currently have set on the UIImageView.
Why? The UIImageView is getting stretched because Auto Layout needs to account for the leading and trailing constraints you set on the UIImageView. To prove my point, set the priority of the leading and trailing constraints to something less than the priority of the height and width constraints. You should see a rounded image like you expect, but it may not be centered.
More steps:
Add aspect ratio constraint 1:1
mark check clip to bounds attribute in attribute inspector
make outlet of your view into you controller class
set corner radius to half of either its height or width
yourImageViewOutlet.layer.cornerRadius = yourImageViewOutlet.frame.size.width / 2.0
I have made the same thing a little time ago and this worked for me
self.imageView.image = [ImageHelper getImage]; //retrieve image
self.imageView.layer.cornerRadius = self.imageView.frame.size.height / 2;
self.imageView.layer.masksToBounds = YES;
self.imageView.layer.borderWidth = 0;
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
SWIFT 3.x
Just change your imageView custom class with this and enjoy.
#IBDesignable class RoundedImageView:UIImageView {
#IBInspectable var borderColor:UIColor = UIColor.white {
willSet {
layer.borderColor = newValue.cgColor
}
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.height/2
layer.masksToBounds = true
layer.borderWidth = 1
layer.borderColor = borderColor.cgColor
}
}
When adding the constraint, just make sure that you check the height and width so that it will be fix. At least that what I always do.
You have given leading constraint, trailing constraint and the width constraint. So the image will try to leave 130 pixels before and after the image which will increase the width of the image.
So the solution is, remove either one of the leading or trailing constraint.
The best workaround is, remove both the constraint and add a horizontal centre constraint, that is what you want.
In storyboard i have set the image to "Scale to fit" mode
But that's a problem, isn't it? It means: "Stretch the image so that it matches the way the image view is stretched." If that isn't what you want, don't say that! Use Centered, or at least use one of the content modes with "Aspect" in its name so that your image is not stretched.
As for the circle itself, setting the cornerRadius is no way to make a circle. The way to create a circular boundary around an image is to mask the image. You can redraw the image with the circular mask as a clipping boundary, or you can apply the circular mask to the image view. (See, for example, my answer here: https://stackoverflow.com/a/16475824/341994.)
It is true that your image view is also being stretched, because you gave it constraints to both sides of the superview. You can prevent that by giving it a width constraint instead; now its width will be absolute. But you should still do the right thing on the other two issues.

Resources