Programmatically center UIImage inside parent view vertically - ios

I am on Swift 5.
The goal is to center a UIImageView vertically inside a view. Currently it looks like
Note all the image bubbles are running off of the cell.
This is the code that lead to this:
let imageView = UIImageView()
let width = self.frame.width
let height = self.frame.height
let img_width = height //* 0.8
let img_height = height
let y = (height - img_height)/2
let x = width*0.05
imageView.frame = CGRect(
x: x
, y: CGFloat(y)
, width: img_width
, height: img_height
)
let rounded = imageView
.makeRounded()
.border(width:1.0, color:Color.white.cgColor)
self.addSubview(rounded)
The imageView extension functions are:
func makeRounded() -> UIImageView {
self.layer.borderWidth = 0.5
self.layer.masksToBounds = false
self.layer.borderColor = Color.white.cgColor
self.layer.cornerRadius = self.frame.width/2
self.clipsToBounds = true
// see https://developer.apple.com/documentation/uikit/uiview/contentmode
self.contentMode = .scaleAspectFill
return self
}
func border( width: CGFloat, color: CGColor ) -> UIImageView{
self.layer.borderWidth = width
self.layer.borderColor = color
return self
}
Which is very vanilla.
This is odd because I laid out the textview vertically in the exact same way, that is: (parentHeight - childHeight)/2, and it is centered. You can see it in the blue text boxes in cell two and three.
____ EDIT _______
This is how I laid out the cell
let data = dataSource[ row - self._data_source_off_set ]
let cell = tableView.dequeueReusableCell(withIdentifier: "OneUserCell", for: indexPath) as! OneUserCell
// give uuid and set delegate
cell.uuid = data.uuid
cell.delegate = self
// render style: this must be set
cell.hasFooter = false //true
cell.imageSource = data
cell.headerTextSource = data
cell.footerTextSource = data
// color schemes
cell.backgroundColor = Color.offWhiteLight
cell.selectionColor = Color.graySecondary

Add these constraints to you imageView and remove frame and its calculations
self.contentView.addSubview(rounded)
self.mimageView.translatesAutoresizingMaskIntoConstraints = false
self.mimageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 20).isActive = true
self.mimageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
self.mimageView.heightAnchor.constraint(equalTo: contentView.heightAnchor).isActive = true
self.mimageView.widthAnchor.constraint(equalTo: contentView.heightAnchor).isActive = true

Related

Building a circular facepile of profile pictures in Swift: how to have the last photo tucked under the first?

I am trying to build a UIView that has a few UIImageViews arranged in a circular, overlapping manner (see image below). Let's say we have N images. Drawing out the first N - 1 is easy, just use sin/cos functions to arrange the centers of the UIImageViews around a circle. The problem is with the last image that seemingly has two z-index values! I know this is possible since kik messenger has similar group profile photos.
The best idea I have come up so far is taking the last image, split into something like "top half" and "bottom half" and assign different z-values for each. This seems doable when the image is the left-most one, but what happens if the image is the top most? In this case, I would need to split left and right instead of top and bottom.
Because of this problem, it's probably not top, left, or right, but more like a split across some imaginary axis from the center of the overall facepile through the center of the UIImageView. How would I do that?!
Below Code Will Layout UIImageView's in Circle
You would need to import SDWebImage and provide some image URLs to run the code below.
import Foundation
import UIKit
import SDWebImage
class EventDetailsFacepileView: UIView {
static let dimension: CGFloat = 66.0
static let radius: CGFloat = dimension / 1.68
private var profilePicViews: [UIImageView] = []
var profilePicURLs: [URL] = [] {
didSet {
updateView()
}
}
func updateView() {
self.profilePicViews = profilePicURLs.map({ (profilePic) -> UIImageView in
let imageView = UIImageView()
imageView.sd_setImage(with: profilePic)
imageView.roundImage(imageDimension: EventDetailsFacepileView.dimension, showsBorder: true)
imageView.sd_imageTransition = .fade
return imageView
})
self.profilePicViews.forEach { (imageView) in
self.addSubview(imageView)
}
self.setNeedsLayout()
self.layer.borderColor = UIColor.green.cgColor
self.layer.borderWidth = 2
}
override func layoutSubviews() {
super.layoutSubviews()
let xOffset: CGFloat = 0
let yOffset: CGFloat = 0
let center = CGPoint(x: self.bounds.size.width / 2, y: self.bounds.size.height / 2)
let radius: CGFloat = EventDetailsFacepileView.radius
let angleStep: CGFloat = 2 * CGFloat(Double.pi) / CGFloat(profilePicViews.count)
var count = 0
for profilePicView in profilePicViews {
let xPos = center.x + CGFloat(cosf(Float(angleStep) * Float(count))) * (radius - xOffset)
let yPos = center.y + CGFloat(sinf(Float(angleStep) * Float(count))) * (radius - yOffset)
profilePicView.frame = CGRect(origin: CGPoint(x: xPos, y: yPos),
size: CGSize(width: EventDetailsFacepileView.dimension, height: EventDetailsFacepileView.dimension))
count += 1
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let requiredSize = EventDetailsFacepileView.dimension + EventDetailsFacepileView.radius
return CGSize(width: requiredSize,
height: requiredSize)
}
}
I don't think you'll have much success trying to split images to get over/under z-indexes.
One approach is to use masks to make it appear that the image views are overlapped.
The general idea would be:
subclass UIImageView
in layoutSubviews()
apply cornerRadius to layer to make the image round
get a rect from the "overlapping view"
convert that rect to local coordinates
expand that rect by the desired width of the "outline"
get an oval path from that rect
combine it with a path from self
apply it as a mask layer
Here is an example....
I was not entirely sure what your sizing calculations were doing... trying to use your EventDetailsFacepileView as-is gave me small images in the lower-right corner of the view?
So, I modified your EventDetailsFacepileView in a couple ways:
uses local images named "pro1" through "pro5" (you should be able to replace with your SDWebImage)
uses auto-layout constraints instead of explicit frames
uses MyOverlapImageView class to handle the masking
Code - no #IBOutlet connections, so just set a blank view controller to OverlapTestViewController:
class OverlapTestViewController: UIViewController {
let facePileView = MyFacePileView()
override func viewDidLoad() {
super.viewDidLoad()
facePileView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(facePileView)
facePileView.dimension = 120
let sz = facePileView.sizeThatFits(.zero)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
facePileView.widthAnchor.constraint(equalToConstant: sz.width),
facePileView.heightAnchor.constraint(equalTo: facePileView.widthAnchor),
facePileView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
facePileView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
facePileView.profilePicNames = [
"pro1", "pro2", "pro3", "pro4", "pro5"
]
}
}
class MyFacePileView: UIView {
var dimension: CGFloat = 66.0
lazy var radius: CGFloat = dimension / 1.68
private var profilePicViews: [MyOverlapImageView] = []
var profilePicNames: [String] = [] {
didSet {
updateView()
}
}
func updateView() {
self.profilePicViews = profilePicNames.map({ (profilePic) -> MyOverlapImageView in
let imageView = MyOverlapImageView()
if let img = UIImage(named: profilePic) {
imageView.image = img
}
return imageView
})
// add MyOverlapImageViews to self
// and set width / height constraints
self.profilePicViews.forEach { (imageView) in
self.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: dimension).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
}
// start at "12 o'clock"
var curAngle: CGFloat = .pi * 1.5
// angle increment
let incAngle: CGFloat = ( 360.0 / CGFloat(self.profilePicViews.count) ) * .pi / 180.0
// calculate position for each image view
// set center constraints
self.profilePicViews.forEach { imgView in
let xPos = cos(curAngle) * radius
let yPos = sin(curAngle) * radius
imgView.centerXAnchor.constraint(equalTo: centerXAnchor, constant: xPos).isActive = true
imgView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: yPos).isActive = true
curAngle += incAngle
}
// set "overlapView" property for each image view
let n = self.profilePicViews.count
for i in (1..<n).reversed() {
self.profilePicViews[i].overlapView = self.profilePicViews[i-1]
}
self.profilePicViews[0].overlapView = self.profilePicViews[n - 1]
self.layer.borderColor = UIColor.green.cgColor
self.layer.borderWidth = 2
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
let requiredSize = dimension * 2.0 + radius / 2.0
return CGSize(width: requiredSize,
height: requiredSize)
}
}
class MyOverlapImageView: UIImageView {
// reference to the view that is overlapping me
weak var overlapView: MyOverlapImageView?
// width of "outline"
var outlineWidth: CGFloat = 6
override func layoutSubviews() {
super.layoutSubviews()
// make image round
layer.cornerRadius = bounds.size.width * 0.5
layer.masksToBounds = true
let mask = CAShapeLayer()
if let v = overlapView {
// get bounds from overlapView
// converted to self
// inset by outlineWidth (negative numbers will make it grow)
let maskRect = v.convert(v.bounds, to: self).insetBy(dx: -outlineWidth, dy: -outlineWidth)
// oval path from mask rect
let path = UIBezierPath(ovalIn: maskRect)
// path from self bounds
let clipPath = UIBezierPath(rect: bounds)
// append paths
clipPath.append(path)
mask.path = clipPath.cgPath
mask.fillRule = .evenOdd
// apply mask
layer.mask = mask
}
}
}
Result:
(I grabbed random images by searching google for sample profile pictures)

How to resize UIImage ratio into view in ios swift?

I am trying to resize uiimage ratio. Right now i am getting image into square not in correct aspect ratio. If i change the width and height uiimage into pickImage.frame.size.width, pickImage.frame.size.hight then UIImage look so large.
If i set pickImage?.contentMode = .scaleAspectFit then image set its position but drop shadow is shown full image view not image picked one.
Here is the screenshots of result i got
And close button should be top left corner, here when i pick image from image picker any other position of close button image set to properly based on landscape or portrait.
Here is the code i used:
func addImage(url : URL) {
let tag = Int(arc4random_uniform(6))
pickImage = UIImageView()
pickImage?.sd_setImage(with:url)
pickImage?.sd_setShowActivityIndicatorView(true)
pickImage?.backgroundColor = UIColor.lightGray
pickImage?.sd_setIndicatorStyle(.gray)
pickImage?.frame = CGRect(x: randomNumber(inRange:
200...Int(touchDrawview.frame.width - 200)), y: Int(getYValue(maxYValue:
Int(touchDrawview.frame.height - 200))), width: 200, height: 200)
pickImage?.autoresizingMask = [.flexibleTopMargin, .flexibleHeight,
.flexibleRightMargin, .flexibleLeftMargin, .flexibleTopMargin,
.flexibleWidth]
pickImage?.contentMode = .scaleAspectFit
pickImage?.tag = tag
pickImage?.isUserInteractionEnabled = true
let imageclose = UIImage(named: "imageclose")
closeImage = UIImageView(image : imageclose)
closeImage?.frame = CGRect(x: 10, y: 10, width: 30, height: 30)
closeImage?.tag = tag
closeImage?.isHidden = true
closeImage?.isUserInteractionEnabled = true
pickImage?.layer.shadowColor = UIColor.white.cgColor
pickImage?.layer.shadowOffset = CGSize(width: 0, height: 3)
pickImage?.layer.shadowOpacity = 1
pickImage?.layer.shadowRadius = 1.0
pickImage?.clipsToBounds = false
let longGuetureImage = UILongPressGestureRecognizer(target: self, action:
#selector(longPressImage(sender:)))
longGuetureImage.minimumPressDuration = 0.1
pickImage?.isUserInteractionEnabled = true
longGuetureImage.delegate = self
pickImage?.addGestureRecognizer(longGuetureImage)
let panGesture = UIPanGestureRecognizer(target: self, action:
#selector(handlePanImage(recognizer:)))
panGesture.delegate = self
pickImage?.isUserInteractionEnabled = true
pickImage?.addGestureRecognizer(panGesture)
let tapGuetureImage = UITapGestureRecognizer(target: self, action:
#selector(removeImage(sender:)))
tapGuetureImage.delegate = self
closeImage?.addGestureRecognizer(tapGuetureImage)
let tapGueturemainImage = UITapGestureRecognizer(target: self, action:
#selector(selectdragImageTap(_:)))
tapGueturemainImage.delegate = self
pickImage?.addGestureRecognizer(tapGueturemainImage)
let rotate = UIRotationGestureRecognizer(target: self, action:
#selector(handlerotateImage(recognizer:)))
rotate.delegate = self
pickImage?.addGestureRecognizer(rotate)
let pinch = UIPinchGestureRecognizer(target: self, action:
#selector(handlePinchImage(sender:)))
pinch.delegate = self
pickImage?.addGestureRecognizer(pinch)
pickImage?.dropShadowOff()
addPickedImage(image: pickImage!, closeimage: closeImage!,imageType :
PickedType.image.rawValue,imageData: url.absoluteString)
pickImage = nil
closeImage = nil
}
Try This:
pickImage?.contentMode = .scaleAspectFill
pickImage?.clipsToBounds = true
Create a UIView with clipsToBounds = true first, apply shadow on this view and then add your pickimage and button as a subView in this view. because clipsToBounds = true stop dropping shadow on current view.
When the image is resized, the aspect ratio of image may not be the same as the view. There are two approaches for what you are trying to achieve.
Approach 1:
Resize the image to fit the view. Here the aspect ratio may be different than view hence image may get distorted. To resize image, refer below,
Refer:
https://aurvan.github.io/atkit-ios-release/index.html
Class Reference:
https://aurvan.github.io/atkit-ios-release/helpbook/Extensions/UIImage.html
Code:
import ATKit
let anImage :UIImage = UIImage(named: "DefaultAvatar")!
let aResizedImage :UIImage? = anImage.resize(size: CGSize(width: 100.0, height: 200.0), scaleMode: UIImageScaleMode.aspectFit)
Approach 2: Calculate the image size manually and adjust the close button from the horizontal center of the view. I have not tried the code, but something like below should work,
anImageX = (anImageViewWidth - anImageWidth) / 2.0
anImageY = (anImageViewHeight - anImageHeight) / 2.0

Create a masked UIView

I am attempting to do something that I thought would be possible, but have no idea how to start.
I have created a UIView and would like it to be filled with colour, but in the shape defined by an image mask. i.e. I have a PNG with alpha, and I would like the UIView I have created to be UIColor.blue in the shape of that PNG.
To show just how stupid I am, here is the absolute rubbish I have attempted so far - trying to generate just a simple square doesn't even work for me, hence it's all commented out.
let rocketColourList: [UIColor] = [UIColor.blue, UIColor.red, UIColor.green, UIColor.purple, UIColor.orange]
var rocketColourNum: Int = 0
var rocketAngleNum: Int = 0
var rocketAngle: Double {
return Double(rocketAngleNum) * Double.pi / 4
}
var rocketColour = UIColor.black
public func drawRocket (){
rocketColour.set()
self.clipsToBounds = true
self.backgroundColor = UIColor.red
imageView.image = UIImage(named: ("rocketMask"))
addSubview(imageView)
//maskimage.frame = self.frame
//let someRect = CGRect (x: 1, y: 1, width: 1000, height: 1000)
//let someRect = CGRect (self.frame)
//let fillPath = UIBezierPath(rect:someRect)
//fillPath.fill()
//setNeedsDisplay()
}
}
Set image as template, then set tint color for UIImageView. Something like that:
guard let let image = UIImage(named: "rocketMask")?.withRenderingMode(.alwaysTemplate) else { return }
imageView.image = image
imageView.tintColor = .blue
Also you can do that through the images assets:

Limit and scale the size of text in UITextField

I have a UITextField with two CAShapeLayers. I want to have my text always centered and limited (in size) to the inner, white circle.
How can I limit the size of the text within that white circle, best with a padding, but also make the text always fill that space? The second part prob has something to do with a scaling factor which sets the text font size smaller, if there is more text.
Here is my MWE:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .darkGray
let size:CGFloat = 300.0
let centerPoint:CGFloat = 200.0
let valueLabel = UITextField()
valueLabel.isUserInteractionEnabled = false
valueLabel.contentVerticalAlignment = .center
valueLabel.textAlignment = .center
valueLabel.text = "300"
valueLabel.textColor = .black
valueLabel.font = UIFont.init(name: "HelveticaNeue-Medium", size: 100)
valueLabel.bounds = CGRect(x:0.0, y:0.0, width:size, height:size)
valueLabel.center = CGPoint(x:centerPoint, y:centerPoint)
let redCircle:CAShapeLayer = CAShapeLayer()
redCircle.path = UIBezierPath(ovalIn: valueLabel.bounds).cgPath
redCircle.fillColor = UIColor.red.cgColor
redCircle.strokeColor = UIColor.white.cgColor
redCircle.lineWidth = 10
valueLabel.layer.addSublayer(redCircle)
let whiteCircle:CAShapeLayer = CAShapeLayer()
let tmpRect = CGRect(x:valueLabel.bounds.origin.x,y:valueLabel.bounds.origin.x,width:valueLabel.bounds.width-80.0,height:valueLabel.bounds.height-80.0)
whiteCircle.path = UIBezierPath(ovalIn: tmpRect).cgPath
whiteCircle.fillColor = UIColor.white.cgColor
whiteCircle.strokeColor = UIColor.white.cgColor
whiteCircle.lineWidth = 10
let posX = valueLabel.bounds.midX - (size-80.0)/2.0
let posY = valueLabel.bounds.midY - (size-80.0)/2.0
whiteCircle.position = CGPoint(x:posX, y:posY)
valueLabel.layer.addSublayer(whiteCircle)
self.view.addSubview(valueLabel)
}
}
For that purpose I can suggest using UITextView, it has native support for that via NSTextContainer. docs
textView.textContainer.exclusionPaths = [..] // array of UIBezierPaths
You can try
valueLabel.adjustsFontSizeToFitWidth = true
valueLabel.minimumFontSize = 0.5
I hope that would be useful for you.

UILabel takes long to load swift

I'm trying to create a view of a "match" image and the "match price" label in the top right corner of the match image. So far everything works fine- I create the image in the container view, I create a UIImageView for the background of my price label, but when I create the actual label and customize it, it takes forever to load in my app (that is to say, everything loads- the match image, the price label background image, but not the actual label detailing the price). Can anyone spot where in my code I'm going wrong?
func setupMiniContentScroll(contentScroll: UIScrollView) {
let scalar:Double = 6/19
let contentViewDimension = contentScroll.frame.width * CGFloat(scalar)
let contentScrollWidth = CGFloat(LocalUser.matches.count) * (contentViewDimension + CGFloat(12)) - CGFloat(12)
contentScroll.backgroundColor = UIColorFromHex(0x34495e)
for index in 0..<LocalUser.matches.count {
let match = LocalUser.matches[index]
MatchesManager.globalManager.retrieveMatchThumbnail(match) { img, error in
if let img = img {
//create the mini matches views
let xOrigin = index == 0 ? 12 : CGFloat(index) * contentViewDimension + (CGFloat(12) * CGFloat(index) + CGFloat(12))
let contentFrame = CGRectMake(xOrigin, 10, contentViewDimension, contentViewDimension)
let contentView = self.makeMiniContentView(contentFrame, image: img, matchedPrice: match.matchedPrice)
contentView.match = match
let tap = UITapGestureRecognizer(target: self, action: #selector(BrowseViewController.toggleItemInfo(_:)))
contentView.addGestureRecognizer(tap)
//update the contentScrollView
dispatch_async(dispatch_get_main_queue()) {
contentScroll.addSubview(contentView)
contentScroll.contentSize = CGSizeMake(contentScrollWidth + CGFloat(16), contentScroll.frame.height)
}
}
}
}
}
//functions to create labels and imgViews for MiniMyMatches
func makeMiniContentView(frame: CGRect, image: UIImage, matchedPrice: Int) -> ItemContainer {
let containerView = ItemContainer(frame: frame)
//create the item image
let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: containerView.frame.width, height: containerView.frame.height))
imgView.image = image
imgView.layer.cornerRadius = 5
imgView.layer.masksToBounds = true
imgView.userInteractionEnabled = true
//create the price label
dispatch_async(dispatch_get_main_queue()) {
let priceLabel = self.makeMiniPriceLabel(containerView, matchedPrice: matchedPrice)
containerView.addSubview(imgView)
containerView.addSubview(priceLabel)
}
return containerView
}
func makeMiniPriceLabel(containerView: ItemContainer, matchedPrice: Int) -> UIView {
//price label var
let priceLabelFrame = CGRectMake(containerView.frame.size.width - 35, -7, containerView.frame.size.width * 0.50, containerView.frame.size.height * 0.35)
//create the price container
let priceContainer = UIImageView(frame: priceLabelFrame)
priceContainer.image = UIImage(named: "venn.png")
//create the price label
let priceLabel = UILabel(frame: CGRect(x: 3, y:0, width: priceContainer.frame.width, height: priceContainer.frame.height))
priceLabel.text = "$\(matchedPrice)"
priceLabel.numberOfLines = 1
priceLabel.textColor = UIColor.whiteColor()
priceLabel.font = priceLabel.font.fontWithSize(20)
priceContainer.addSubview(priceLabel)
return priceContainer
}
My guess is that the closure for your retrieveMatchThumbnail function is being called on a background thread. You have code in that closure that is manipulating UI objects. I would move ALL the UI code inside your call to dispatch_async():
MatchesManager.globalManager.retrieveMatchThumbnail(match) { img, error in
if let img = img {
//create the mini matches views
let xOrigin = index == 0 ? 12 : CGFloat(index) * contentViewDimension + (CGFloat(12) * CGFloat(index) + CGFloat(12))
let contentFrame = CGRectMake(xOrigin, 10, contentViewDimension, contentViewDimension)
//update the contentScrollView
dispatch_async(dispatch_get_main_queue()) {
let contentView = self.makeMiniContentView(contentFrame, image: img, matchedPrice: match.matchedPrice)
contentView.match = match
let tap = UITapGestureRecognizer(target: self, action: #selector(BrowseViewController.toggleItemInfo(_:)))
contentView.addGestureRecognizer(tap)
contentScroll.addSubview(contentView)
contentScroll.contentSize = CGSizeMake(contentScrollWidth + CGFloat(16), contentScroll.frame.height)
}
}
}

Resources