How to constrain a UIImageView inside another view? - ios

I have a universal iOS swift project in xcode, and am having trouble getting the sizes of a UIImageView right. The XIB structure is as follows:
View
|-- Card (View)
|-- CoverImage (UIImageView)
View is loaded into a UIScrollView by ViewController.swift. Card has top, bottom, trailing, and leading constraints set up so that there is a 20px 'margin' around it. Card also has rounded corners. CoverImage has similar constraints, with top, trailing, and leading being set to superview and bottom being defined in viewDidLoad() as 40% of the height of Card. The problem is that the width of CoverImage exceeds the width of Card, with the excess not being displayed. As a result, when I try to round the top corners of CoverImage, only the Top Left corner is rounded.
Setting leadingConstraint.constant or trailingConstraint.constant to 0 seems to have no effect, as the top right corner still remains unrounded.
How can I set CoverImage's constraints properly so that it has the correct size? Is it an issue with the constraints I'm setting in the XIB, or is there a better way to define the size of a child view?
For reference, this is the controller swift file for the View containing Card and CoverImage:
class CardViewController: UIViewController {
// MARK: Connect All Subviews/Objects
#IBOutlet weak var card: UIView!
#IBOutlet weak var coverImage: UIImageView!
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
#IBOutlet weak var topConstraint: NSLayoutConstraint!
#IBOutlet weak var trailingConstraint: NSLayoutConstraint!
#IBOutlet weak var leadingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// MARK: Draw Card
card.layer.cornerRadius = 2
card.layer.shadowRadius = 3
card.layer.shadowOpacity = 0.15;
card.layer.shadowOffset = CGSizeMake(1, 1)
card.backgroundColor = UIColor.whiteColor()
// MARK: Draw Cover Image
let imageViewShape : CAShapeLayer = CAShapeLayer()
imageViewShape.bounds = coverImage.frame
imageViewShape.position = coverImage.center
imageViewShape.path = UIBezierPath(roundedRect: coverImage.bounds, byRoundingCorners: [UIRectCorner.TopRight, UIRectCorner.TopLeft], cornerRadii: CGSize(width: 2, height: 2)).CGPath
coverImage.layer.mask = imageViewShape
bottomConstraint.constant = card.frame.height * 0.4
}
}
Thanks to anyone who can help!

First of all, if you try to set the bounds of CAShapeLayer on viewDidLoad you'll get incorrect dimensions as for the superview isn't rendered on the device at the moment yet; it should be created on viewDidAppear.
Now that we have the correct frame we have to position it properly in superview and by default the mask we append does inherit the constraints of the view it's applied to, thus we have to adjust the origin point of it by 20 pixels on both axis.
The code I used to make it all look fine as you requested it to:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
let imageViewShape : CAShapeLayer = CAShapeLayer()
imageViewShape.frame = coverImage.frame
imageViewShape.bounds = CGRect(origin: CGPoint(x: coverImage.bounds.origin.x + 20, y: coverImage.bounds.origin.y + 20), size: coverImage.bounds.size)
imageViewShape.path = UIBezierPath(roundedRect: coverImage.bounds, byRoundingCorners: [UIRectCorner.TopRight, UIRectCorner.TopLeft, UIRectCorner.BottomLeft, UIRectCorner.BottomRight], cornerRadii: CGSize(width: 10, height: 10)).CGPath
coverImage.layer.mask = imageViewShape
}

Related

Swift ScrollView Layout Issue With PageController and Images iOS

I can't figure out how to set constraints for a scrollView with an imageView inside.
I am using the scrollView with a pageConroller to swipe thru a bunch of images.
See my layout in the picture below.
// Code for imageView
for index in 0..<drinksImagesArray.count {
frame.origin.x = scrollView.frame.size.width * CGFloat(index)
frame.size = scrollView.frame.size
let imageView = UIImageView(frame: frame)
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(named: imagesArray[index].name)
self.scrollView.addSubview(imageView)
}
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(imagesArray.count), height: scrollView.frame.size.height)
scrollView.delegate = self
Any suggestions? Thank you!
Layout
You will have much better luck using auto-layout --- it can handle all of the frame sizes and .contentSize for you.
Here's a quick example - it uses a view controller with a scroll view added in Storyboard, so it should be pretty easy for you to integrate with your code:
class ScrollingImagesViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
var drinksImagesArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// however you're populating your array...
drinksImagesArray = [
"drink1",
"drink2",
"drink3",
// etc...
]
// create a horizontal stack view
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
stack.spacing = 0
// add the stack view to the scroll view
stack.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stack)
// use scroll view's contentLayoutGuide for content constraints
let svCLG = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// stack view constrained Top / Bottom / Leading / Trailing of scroll view CONTENT guide
stack.topAnchor.constraint(equalTo: svCLG.topAnchor),
stack.bottomAnchor.constraint(equalTo: svCLG.bottomAnchor),
stack.leadingAnchor.constraint(equalTo: svCLG.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: svCLG.trailingAnchor),
// stack view height == scroll view FRAME height
stack.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor),
])
// create image views and add them to the stack view
drinksImagesArray.forEach { imgName in
let v = UIImageView()
v.backgroundColor = .lightGray
v.contentMode = .scaleAspectFit
// make sure we load a valid image
if let img = UIImage(named: imgName) {
v.image = img
}
stack.addArrangedSubview(v)
}
// stack distribution is set to .fillEqually, so we only need to set the
// width constraint on the first image view
// unwrap it
if let firstImageView = stack.arrangedSubviews.first {
firstImageView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor).isActive = true
}
}
}
Edit
After reviewing your Storyboard...
Auto-layout doesn't seem to like it when you add a UINavigationBar and a UIToolbar and a UIScrollView as subviews. In particular, it appears to confuse the scroll view's frame related constraints.
The fix is to first add constraints for your scroll view:
Top to Navigation Bar Bottom
Bottom to Page Control Top
Leading and Trailing to safe-area
Storyboard / Interface builder will complain that the scroll view is not configured properly. You can either ignore that, or select the scroll view and set Ambiguity to Never Verify:
Then, in your view controller class, we need to create a height constraint for the stack view we're adding to the scroll view, and set that height constant in viewDidLayoutSubviews().
Here's the full code:
//
// WasserhaushaltViewController.swift
// deSynthTheOceans
//
// Created by robinsonhus0 on 24.03.20.
// Copyright © 2020 robinsonhus0. All rights reserved.
//
import UIKit
import AVFoundation
import Charts
import FSCalendar
import HealthKit
struct WasserSpeicher: Codable {
let wassermenge: Double
let speicherdatum: String
let speicherStelle: Double
}
class WasserhaushaltViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var diagrammView: UIView!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
let drinksImagesArray = ["tapWater", "water", "milk", "cola", "coffee", "tea", "juice", "beer"]
var imageIndex = Int()
struct Drinks {
var name: String
var tagesMengeFactor: Double
var gesamtMengeFactor: Double
}
var frame = CGRect(x: 0, y: 0, width: 0, height: 0)
var pageNumber = CGFloat()
#IBOutlet weak var todaysWaterConsumptionLabel: UILabel!
#IBOutlet weak var waterGoalProgress: UIProgressView!
#IBOutlet weak var waterGoalLabel: UILabel!
#IBOutlet weak var wasserMengeStepper: UIStepper!
#IBOutlet weak var motivationTextView: UITextView!
#IBOutlet weak var wasserglasButton: UIBarButtonItem!
#IBOutlet weak var kleineFlascheButton: UIBarButtonItem!
#IBOutlet weak var grosseFlascheButton: UIBarButtonItem!
#IBOutlet weak var overAllWaterConsumptionLabel: UILabel!
// added
let scrollingImagesStackView = UIStackView()
var stackHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
pageControl.numberOfPages = drinksImagesArray.count
setupDrinkImages()
}
func setupDrinkImages() {
// set stack view properties
scrollingImagesStackView.axis = .horizontal
scrollingImagesStackView.alignment = .fill
scrollingImagesStackView.distribution = .fillEqually
scrollingImagesStackView.spacing = 0
// add the stack view to the scroll view
scrollingImagesStackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(scrollingImagesStackView)
// use scroll view's contentLayoutGuide for content constraints
let svCLG = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// stack view constrained Top / Bottom / Leading / Trailing of scroll view CONTENT guide
scrollingImagesStackView.topAnchor.constraint(equalTo: svCLG.topAnchor),
scrollingImagesStackView.bottomAnchor.constraint(equalTo: svCLG.bottomAnchor),
scrollingImagesStackView.leadingAnchor.constraint(equalTo: svCLG.leadingAnchor),
scrollingImagesStackView.trailingAnchor.constraint(equalTo: svCLG.trailingAnchor),
])
// create the stack view height constraint - it will be updated in viewDidLayoutSubviews
stackHeightConstraint = scrollingImagesStackView.heightAnchor.constraint(equalToConstant: 0)
stackHeightConstraint.isActive = true
// create image views and add them to the stack view
drinksImagesArray.forEach { imgName in
let v = UIImageView()
v.backgroundColor = .orange
v.contentMode = .scaleAspectFit
// make sure we load a valid image
if let img = UIImage(named: imgName) {
v.image = img
}
scrollingImagesStackView.addArrangedSubview(v)
}
// stack distribution is set to .fillEqually, so we only need to set the
// width constraint on the first image view
// unwrap it
if let firstImageView = scrollingImagesStackView.arrangedSubviews.first {
firstImageView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor).isActive = true
}
scrollView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// since we have a UINavigationBar and a UIToolBar in the view hierarchy,
// we need to set this here
// Note: if the view size changes
// stack view height == scroll view FRAME height
stackHeightConstraint.constant = scrollView.frame.height
}
// func setupDrinkImages() {
// for index in 0..<drinksImagesArray.count {
// frame.origin.x = scrollView.frame.size.width * CGFloat(index)
// frame.size = scrollView.frame.size
//
// let imageView = UIImageView(frame: frame)
// imageView.contentMode = .scaleAspectFit
// imageView.image = UIImage(named: drinksImagesArray[index])
// self.scrollView.addSubview(imageView)
// }
// scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(drinksImagesArray.count), height: scrollView.frame.size.height)
// scrollView.delegate = self
// }
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(pageNumber)
}
}
Your (modified) Storyboard is too big to add here... if you have any trouble with the changes mentioned above, here it is: https://pastebin.com/2Q1uFUgL

UIView within StackView - Position of Layers

I'm pretty new to Swift and coding in general. I'm trying to just create 4 simple grey circles that are positioned within 4 UIViews that are inside 2 stackViews...etc
With the 4 UIViews, I have them basically in a grid square in the centre of the screen, I'll add a screenshot for clarity below. At the moment, it seems to render the bottom circles quite far below the boxes that I have laid out in the XCode IB.
I've tried a few different ways of trying to get the circles positioned properly, but I've hit a brick wall and can't seem to work out why it's not working. I can get them to position properly if I add a UIView within the UIViews (Named 'topLeftBox', 'topRightBox'...etc). And whilst it's a simple work around, I'm just trying to understand why I can't just add the layers in the original 4 UIViews.
Any help would be much appreciated, and apologies in advance for the glaring Newbie mistakes!
Heres a screenshot of the output as the code stands now and also the XCodeIB:
XCode Storyboard
Grey Circles Simulator Output
And here is the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var topLeftBox: UIView!
#IBOutlet weak var topRightBox: UIView!
#IBOutlet weak var bottomLeftBox: UIView!
#IBOutlet weak var bottomRightBox: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
drawCircle(box: topLeftBox)
drawCircle(box: bottomLeftBox)
drawCircle(box: topRightBox)
drawCircle(box: bottomRightBox)
}
func createCircle(lineColor: UIColor, strokeEnd: CGFloat) -> CAShapeLayer {
let layer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: .zero, radius: 50, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
// Styling for the circular line
layer.path = circularPath.cgPath
layer.strokeColor = lineColor.cgColor
layer.fillColor = UIColor.clear.cgColor
layer.lineWidth = 10
layer.lineCap = kCALineCapRound
layer.strokeEnd = strokeEnd
layer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
return layer
// Style the track
}
fileprivate func drawCircle(box: UIView) {
// Create Circles
let trackLayer = createCircle(lineColor: .lightGray, strokeEnd: 1)
box.layer.addSublayer(trackLayer)
trackLayer.position = box.center
}
}
Your problem is this line:
trackLayer.position = box.center
That is using the center of the box's frame which is in the coordinate system of its parent view. You need the center of the box in the coordinate system of the box itself (ie. its bounds).
Replace the line above with this:
trackLayer.position = CGPoint(x: box.bounds.midX, y: box.bounds.midY)

Circle Image View

I am trying to make image view circled for profile pics.
It was working properly before I had put constrains of UiScreen width.
so here is the code
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var proPicH: NSLayoutConstraint! //Profile Picture Height
#IBOutlet weak var proPicW: NSLayoutConstraint! // Profile Picture Width
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
circleImage()
}
func circleImage() {
let screenSize = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let width = screenWidth / 2
print("ScreenWidth: \(screenWidth)")
proPicW.constant = width
proPicH.constant = width
print("H:\(proPicH.constant)")
print("W:\(proPicW.constant)") //Here the height and width comes to my expectations
imageView.layer.cornerRadius = imageView.bounds.width / 2
imageView.clipsToBounds = true
print("Height: \(imageView.bounds.height)") // Here the height and width becomes more
}
}
Please help me with this to make image round
At the point in time where you set the image view corner radius its bounds haven't been updated to match the constraints yet. Change this line
imageView.layer.cornerRadius = imageView.bounds.width / 2
To
imageView.layer.cornerRadius = width / 2
So that the same value used to set the constraints is also used for the corner radius.
Note that if you update the constraints in some other piece of code you also new to update the corner radius to match.
I would suggest you to put
circleImage()
in viewDidAppears Methods
Use imageView.layer.cornerRadius = imageView.frame.size.width / 2
imageView.layer.maskToBounds=Yes;

How to set content size of scrollview to maximum width of frame on any size device

I created a UIScrollview in my storyboard with any width and any height, and I am trying to set the content size width to the width of the frame on the device that it is viewed on. No matter what I try, the width is being set to the width that was displayed on the storyboard, 584, even though when running it on the iPhone 6 simulator, it should be around 300. This results in the scrollview having the ability to scroll horizontally, which I do not want. I did manage to get the scrollview to stop scrolling horizontally by adding a UIView as a subview. However, I still can't figure out how to get the correct size for the width. Not only am I trying to set the conent size width, but I am also trying to set a UILabel inside the scrollview with the same width as the content size of the uiscrollview, but getting the frame width or the bounds width of the scrollview and the UIView subview are too large. Any help with this would be appreciated.
The scrollview is inside a custom table view cell. Here is the class for the cell.
import UIKit
class CustomTableViewCell: UITableViewCell, UIScrollViewDelegate {
#IBOutlet weak var winLoseValueLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var newTotalLabel: UILabel!
#IBOutlet weak var locationLabel: UILabel!
#IBOutlet weak var playersScrollView: CustomTableCellScrollView!
#IBOutlet weak var scrollContentView: UIView!
var numLinesInScrollView : CGFloat?
var tblView : UITableView?
var people : String?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.playersScrollView.tblView = tblView
numLinesInScrollView = 0.0
//self.playersScrollView.contentSize.width = self.scrollContentView.frame.width
self.playersScrollView.contentSize.width = self.playersScrollView.frame.size.width
self.playersScrollView.contentSize.height = 100
//self.playersScrollView.contentSize.width = UIScreen.mainScreen().bounds.width - 24
self.playersScrollView.addSubview(self.scrollContentView)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func addLabelToScrollView(str : String) {
// Increment the number of lines in the scrollview
if numLinesInScrollView != nil {
numLinesInScrollView!++
}
else{
numLinesInScrollView = 1
}
// Get the bounds of the screen
let screenSize : CGRect = UIScreen.mainScreen().bounds
// Update the height of the scrollview
self.playersScrollView.contentSize.height = 20 * numLinesInScrollView!
// Add a new label to the players scroll view
let w : CGFloat = self.scrollContentView.frame.width - 350
let h : CGFloat = 20
let x : CGFloat = 0
let y : CGFloat = (numLinesInScrollView! - 1) * h
let frame : CGRect = CGRect(x: x, y: y, width: w, height: h)
var person : UILabel = UILabel(frame: frame)
person.font = UIFont(name: "HelveticaNeue-Bold", size: 12.0)
person.textColor = UIColor.blackColor()
person.textAlignment = NSTextAlignment.Center
switch numLinesInScrollView!{
case 1:
person.backgroundColor = UIColor(red: 1.0, green: 0, blue: 0, alpha: 1.0)
case 2:
person.backgroundColor = UIColor(red: 0, green: 1.0, blue: 0, alpha: 1.0)
case 3:
person.backgroundColor = UIColor(red: 0, green: 0, blue: 1.0, alpha: 1.0)
case 4:
person.backgroundColor = UIColor(red: 1.0, green: 0, blue: 1.0, alpha: 1.0)
default:
break
}
person.text? = "Hellow World"
self.scrollContentView.addSubview(person)
}
}
The problem is merely that you are setting the contentSize too soon. At the time awakeFromNib is called, the scroll view (and interface in general) has not yet acquired its size, so you are, as you say, setting the contentSize width to the scroll view's old width — the width it had in the storyboard — because it still has its old width.
Take the code out of awakeFromNib and put it into a place that runs after layout has occurred — like, for example, viewDidLayoutSubviews.

circular image in table view cell swift

I am trying to create some a circular image in every row of my table view. I have followed tutorials but my image is turning out to be a diamond shape and not a circular one. What am I doing wrong:
var cellImage = UIImage(named: pic)
cell.imageView!.image = cellImage
cell.imageView!.layer.frame = CGRectMake(0, 0, 190, 190)
cell.imageView!.layer.borderWidth = 0.5
cell.imageView!.layer.masksToBounds = false
cell.imageView!.layer.borderColor = UIColor.blueColor().CGColor
cell.imageView!.layer.cornerRadius = cell.imageView!.layer.frame.height/2
cell.imageView!.clipsToBounds = true
If you are creating your own imageView, it's better to set the cornerRadius inside the custom TableViewCell.
class CircularTableViewCell: UITableViewCell {
#IBOutlet weak var circularImageView: UIImageView!
override func layoutSubviews() {
circularImageView.layer.cornerRadius = circularImageView.bounds.height / 2
circularImageView.clipsToBounds = true
}
}
Note the cornerRadius property can't guarantee that the view will be absolutely round unless you set the imageView's width and height ratio to be 1:1. Another approach to create round view is using Mask.
public extension UIView {
public func round() {
let width = bounds.width < bounds.height ? bounds.width : bounds.height
let mask = CAShapeLayer()
mask.path = UIBezierPath(ovalInRect: CGRectMake(bounds.midX - width / 2, bounds.midY - width / 2, width, width)).CGPath
self.layer.mask = mask
}
}
This will allow you to call round() with any UIView and make sure the view is always round. e.g.
class CircularTableViewCell: UITableViewCell {
#IBOutlet weak var circularImageView: UIImageView!
override func layoutSubviews() {
circularImageView.round()
}
}
Try giving the static value(half of width or height) as corner radius.This may solve your problem.

Resources