I have a UIStackView with UIImageViews that might be from 1 to 5. Each UIImageView has an image coming from an array (from previously downloaded and cached images) and I'd like to keep the UIImageViews stay in a perfect circle. I change the width constant of the UIStackView along with the spacing between the images in a way to overlap if there are more than 3 images.
I had writen this code in a previous project and it works perfectly fine, but for some reason, when I call the changeNavStackWidth function to change the width of the UIStackView, the width is not being updated and I have no clue why.
var userImages = [String]()
var navStackView : UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override func viewDidLoad() {
super.viewDidLoad()
setupNavStack()
navBarStackTapGesture()
}
func setupNavStack() {
guard let navController = navigationController else { return }
navController.navigationBar.addSubview(navStackView)
// x, y, w, h
navStackView.widthAnchor.constraint(equalToConstant: 95).isActive = true
navStackView.centerYAnchor.constraint(equalTo: navController.navigationBar.centerYAnchor).isActive = true
navStackView.heightAnchor.constraint(equalToConstant: 35).isActive = true
navStackView.centerXAnchor.constraint(equalTo: navController.navigationBar.centerXAnchor).isActive = true
}
func setNavBarImages() {
for image in userImages {
let imageView = UIImageView()
// imageView.image = UIImage(named: image)
let photoURL = URL(string: image)
imageView.sd_setImage(with: photoURL)
imageView.layer.borderColor = UIColor.white.cgColor
imageView.layer.borderWidth = 1
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
navStackView.addArrangedSubview(imageView)
navStackView.layoutIfNeeded()
}
switch userImages.count {
case 0:
print("0 images")
case 1:
changeNavStackWidth(constant: 35, spacing: 0)
//changeNavStackWidth(constant: 60, spacing: 0)
case 2:
changeNavStackWidth(constant: 80, spacing: 10)
case 3:
changeNavStackWidth(constant: 95, spacing: -5)
case 4:
changeNavStackWidth(constant: 110, spacing: -10)
case 5:
changeNavStackWidth(constant: 95, spacing: -20)
case 6...1000:
// changeNavStackWidth(constant: 95, spacing: -20)
default:
print("default")
}
navigationItem.titleView = navStackView
navStackView.layoutIfNeeded()
}
func changeNavStackWidth(constant: CGFloat, spacing: CGFloat) {
navStackView.constraints.forEach { constraint in
if constraint.firstAttribute == .width {
constraint.constant = constant
print("constan is:", constant) // not being printed
}
}
navStackView.spacing = spacing
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
navStackView.subviews.forEach { $0.layer.cornerRadius = $0.frame.height / 2 }
}
based on our discussion in chat. Here is what you want:
A) if there is only one Image
B) if there are two Images:
C) if there are more than 2:
Code:
import UIKit
class TestStack: UIViewController{
var userImages = [#imageLiteral(resourceName: "images-1"),#imageLiteral(resourceName: "images-2"),#imageLiteral(resourceName: "images-4"),#imageLiteral(resourceName: "images-5")]
let imagesHolder: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
userImages.removeLast(2)
guard let bar = navigationController?.navigationBar else {return}
bar.addSubview(imagesHolder)
let size = 25
for i in 0..<userImages.count{
var x = i * size
x = userImages.count > 2 ? x/2 : x
x = (i == 1 && userImages.count == 2) ? x + 5 : x
let imageView = UIImageView(frame: CGRect(x: x, y: 0, width: size, height: size))
imageView.image = userImages[i]
imageView.clipsToBounds = true
imageView.layer.cornerRadius = CGFloat(size / 2)
imagesHolder.addSubview(imageView)
}
let width = CGFloat((userImages.count * size)/2)
NSLayoutConstraint.activate([
imagesHolder.centerYAnchor.constraint(equalTo: bar.centerYAnchor),
imagesHolder.centerXAnchor.constraint(equalTo: bar.centerXAnchor),
imagesHolder.widthAnchor.constraint(equalToConstant: width),
imagesHolder.heightAnchor.constraint(equalToConstant: CGFloat(size))
])
}
}
Instead of adjusting the StackView's width to adjust to the images:
Create a UIView and add the UIImageView as a subview to the UIView.
Add a top and bottom constraint to the UIImageView with the UIView
Add a center horizontally constraint the UIImageView with the UIView
Add an aspect ratio constraint to the UIImageView with itself and set that ratio to 1:1
Add the UIView to the Stack View
The constraints on the UIImageView in relation to the UIView will keep the images separated out equally, and the aspect ratio constraint on itself will keep the UIImage as a perfect circle.
Changing your imageView's content mode to .aspectFit will also do the trick without having to mess with your stackView's width.
Change this imageView.contentMode = .scaleAspectFill to this imageView.contentMode = .scaleAspectFit
AspectFill stretches the image to fit the view, and doesn't maintain the scale ratio of the image
AspectFit stretches the image until the horizontal bound OR vertical bound hits the view, while maintaining the scale ratio of the image.
see here
Related
I have a UIView object inside a horizontal stackView.
I'm calling this function inside LayoutSubviews:
func roundCorners(corners: UIRectCorner, radius: CurveRadius) {
var r = CGFloat()
switch radius {
case .small:
r = self.frame.width / 30
case .medium:
r = self.frame.width / 25
}
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: r, height: r))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
but it just doesn't work. I guess it has something to do with a stackView. can anyone help me out?
extension UIView {
/// Rounds ``UIView`` corners.
/// - Parameters:
/// - maskedCorners: Corners to be rounded.
/// - cornerRadius: Value to be set as corner radius.
func roundCorners(maskedCorners: CACornerMask,
cornerRadius: CGFloat) {
layer.cornerRadius = cornerRadius
layer.maskedCorners = maskedCorners
layer.masksToBounds = true
}
}
I'm not sure if the UIStackView is the problem here, but there's another, more re-usable way to do this using a CACornerMask on the UIView's layer in an extension:
extension UIView {
func roundCorners() {
self.layer.cornerRadius = 10
self.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
}
}
When you create your view, just call the roundCorners(), like this:
private lazy var roundCorneredView: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .red
view.roundCorners()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
Then add that view to your hierarchy in the usual way using addSubview.
CACornerMask docs # Apple
I am trying to recreate the iPhone App Switcher page - the one that comes up when you swipe up.
I'm building it by adding an array of views representing apps to a scroll view.
Unfortunately, I am getting caught up setting the spacing between the views. I am trying to set it using a parabolic function so the views collapse to the left hand side. I think the equation might be incorrect.
Here's my code for scrollViewDidScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
items.enumerated().forEach { (index, tabView) in
guard let tabSuperView = tabView.superview else {return}
let screenWidth = UIScreen.main.bounds.width
// Return value between 0 and 1 depending on the location of the tab within the visible screen
// 0 Left hand side or offscreen
// 1 Right hand side or offscreen
let distanceMoved = tabSuperView.convert(CGPoint(x: tabView.frame.minX, y: 0), to: view).x
let screenOffsetPercentage: CGFloat = distanceMoved / screenWidth
// Scale
let minValue: CGFloat = 0.6
let maxValue: CGFloat = 1
let scaleAmount = minValue + (maxValue - minValue) * screenOffsetPercentage
let scaleSize = CGAffineTransform(scaleX: scaleAmount, y: scaleAmount)
tabView.transform = scaleSize
// Set a max and min
let percentAcrossScreen = max(min(distanceMoved / screenWidth, 1.0), 0)
// Spacing
if let prevTabView = items.itemAt(index - 1) {
// Rest of tabs
let constant: CGFloat = 100
let xFrame = prevTabView.frame.origin.x + (pow(percentAcrossScreen, 2) * constant)
tabView.frame.origin.x = max(xFrame, 0)
} else {
// First tab
tabView.frame.origin.x = 20
}
}
}
How would you fix this to replicate the scrolling experience of the iPhone app switcher page?
Sample Project:
https://github.com/Alexander-Frost/ViewContentOffset
The general idea (I'll call the moving views "cards")...
As you "push" a card to the right, calculate the percentage of the distance from the leading edge of the container to the leading edge of the card, based on a portion of the container width. Then, position the leading edge of the next card that percentage of the width of the card.
So, if the cards are 70% of the width of the view, we want the top card to be almost pushed off to the right when the "dragging" card is 1/3rd of the distance from the leading edge of the view.
If the dragging card is one-half of 1/3rd, we want the next card's leading to be 1/2 the width of the card.
As I said in one of your earlier questions, I'm not sure using a scroll view will be of benefit, since you'll be changing the relative distances as you drag.
Here's an example:
You can try out this code - just create a new project and replace the default view controller class with:
class ViewController: UIViewController {
let switcherView = SwitcherView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
switcherView.translatesAutoresizingMaskIntoConstraints = false
switcherView.backgroundColor = .white
view.addSubview(switcherView)
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain switcher view to all 4 sides of safe area
switcherView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
switcherView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
switcherView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
switcherView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
}
}
Here's the "card" view class:
class CardView: UIView {
var theLabels: [UILabel] = []
var cardID: Int = 0 {
didSet {
theLabels.forEach {
$0.text = "\(cardID)"
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
for i in 1...5 {
let v = UILabel()
v.font = .systemFont(ofSize: 24.0)
v.translatesAutoresizingMaskIntoConstraints = false
addSubview(v)
switch i {
case 1:
v.topAnchor.constraint(equalTo: topAnchor, constant: 10.0).isActive = true
v.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0).isActive = true
case 2:
v.topAnchor.constraint(equalTo: topAnchor, constant: 10.0).isActive = true
v.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0).isActive = true
case 3:
v.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10.0).isActive = true
v.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0).isActive = true
case 4:
v.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10.0).isActive = true
v.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0).isActive = true
default:
v.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
v.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
theLabels.append(v)
}
layer.cornerRadius = 6
// border
layer.borderWidth = 1.0
layer.borderColor = UIColor.gray.cgColor
// shadow
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: -3, height: 3)
layer.shadowOpacity = 0.25
layer.shadowRadius = 2.0
}
}
and here's the "SwitcherView" class - where all the action takes place:
class SwitcherView: UIView {
var cards: [CardView] = []
var currentCard: CardView?
var firstLayout: Bool = true
// useful during development...
// if true, highlight the current "control" card in yellow
// if false, leave them all cyan
let showHighlight: Bool = false
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
clipsToBounds = true
// add 20 "cards" to the view
for i in 1...20 {
let v = CardView()
v.backgroundColor = .cyan
v.cardID = i
cards.append(v)
addSubview(v)
v.isHidden = true
}
// add a pan gesture recognizer to the view
let pan = UIPanGestureRecognizer(target: self, action: #selector(self.didPan(_:)))
addGestureRecognizer(pan)
}
override func layoutSubviews() {
super.layoutSubviews()
if firstLayout {
// if it's the first time through, layout the cards
firstLayout = false
if let firstCard = cards.first {
if firstCard.frame.width == 0 {
cards.forEach { thisCard in
//thisCard.alpha = 0.750
thisCard.frame = CGRect(origin: .zero, size: CGSize(width: self.bounds.width, height: self.bounds.height))
thisCard.transform = CGAffineTransform(scaleX: 0.71, y: 0.71)
thisCard.frame.origin.x = 0
if thisCard == cards.last {
thisCard.frame.origin.x = 10
}
thisCard.isHidden = false
}
doCentering(for: cards.last!)
}
}
}
}
#objc func didPan(_ gesture: UIPanGestureRecognizer) -> Void {
let translation = gesture.translation(in: self)
var pt = gesture.location(in: self)
pt.y = self.bounds.midY
for c in cards.reversed() {
if c.frame.contains(pt) {
if let cc = currentCard {
if let idx1 = cards.firstIndex(of: cc),
let idx2 = cards.firstIndex(of: c),
idx2 > idx1 {
if showHighlight {
currentCard?.backgroundColor = .cyan
}
currentCard = c
if showHighlight {
currentCard?.backgroundColor = .yellow
}
}
} else {
currentCard = c
if showHighlight {
currentCard?.backgroundColor = .yellow
}
}
break
}
}
switch gesture.state {
case .changed:
if let controlCard = currentCard {
// update card leading edge
controlCard.frame.origin.x += translation.x
// don't allow drag left past 1.0
controlCard.frame.origin.x = max(controlCard.frame.origin.x, 1.0)
// update the positions for the rest of the cards
updateCards(controlCard)
gesture.setTranslation(.zero, in: self)
}
case .ended:
if showHighlight {
currentCard?.backgroundColor = .cyan
}
guard let controlCard = currentCard else {
return
}
if let idx = cards.firstIndex(of: controlCard) {
// use pan velocity to "throw" the cards
let velocity = gesture.velocity(in: self)
// convert to a reasonable Int value
let offset: Int = Int(floor(velocity.x / 500.0))
// step up or down in array of cards based on velocity
let newIDX = max(min(idx - offset, cards.count - 1), 0)
doCentering(for: cards[newIDX])
}
currentCard = nil
default:
break
}
}
func updateCards(_ controlCard: CardView) -> Void {
guard let idx = cards.firstIndex(of: controlCard) else {
print("controlCard not found in array of cards - can't update")
return
}
var relativeCard: CardView = controlCard
var n = idx
// for each card to the right of the control card
while n < cards.count - 1 {
let nextCard = cards[n + 1]
// get percent distance of leading edge of relative card
// to 33% of the view width
let pct = relativeCard.frame.origin.x / (self.bounds.width * 1.0 / 3.0)
// move next card that percentage of the width of a card
nextCard.frame.origin.x = relativeCard.frame.origin.x + (relativeCard.frame.size.width * pct) // min(pct, 1.0))
relativeCard = nextCard
n += 1
}
// reset relative card and index
relativeCard = controlCard
n = idx
// for each card to the left of the control card
while n > 0 {
let prevCard = cards[n - 1]
// get percent distance of leading edge of relative card
// to half the view width
let pct = relativeCard.frame.origin.x / self.bounds.width
// move prev card that percentage of 33% of the view width
prevCard.frame.origin.x = (self.bounds.width * 1.0 / 3.0) * pct
relativeCard = prevCard
n -= 1
}
self.cards.forEach { c in
let x = c.frame.origin.x
// scale transform each card between 71% and 75%
// based on card's leading edge distance to one-half the view width
let pct = x / (self.bounds.width * 0.5)
let sc = 0.71 + (0.04 * min(pct, 1.0))
c.transform = CGAffineTransform(scaleX: sc, y: sc)
// set translucent for far left cards
if cards.count > 1 {
c.alpha = min(1.0, x / 10.0)
}
}
}
func doCentering(for cCard: CardView) -> Void {
guard let idx = cards.firstIndex(of: cCard) else {
return
}
var controlCard = cCard
// if the leading edge is greater than 1/2 the view width,
// and it's not the Bottom card,
// set cur card to the previous card
if idx > 0 && controlCard.frame.origin.x > self.bounds.width * 0.5 {
controlCard = cards[idx - 1]
}
// center of control card will be offset to the right of center
var newX = self.bounds.width * 0.6
if controlCard == cards.last {
// if it's the Top card, center it
newX = self.bounds.width * 0.5
}
if controlCard == cards.first {
// if it's the Bottom card, center it + just a little to the right
newX = self.bounds.width * 0.51
}
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.1, options: [.allowUserInteraction, .curveEaseOut], animations: {
controlCard.center.x = newX
self.updateCards(controlCard)
}, completion: nil)
}
}
When we stop panning, the code finds the card with the Leading edge closest to the center of the view... If its leading edge is less-than 50% of the width, it slides it back to the left so it becomes the "center" card. If its leading edge is greater-than 50% of the width, it slides to the right and the previous card becomes the "center" card.
Edit - added some "Pan Velocity" handling to the SwitcherView.
I am trying to punch a circular hole through a UIView that is above a UIImageView, whereby the hole can see through to the image below (I would like to interact with this image through the hole with a GestureRecognizer later). I have 2 problems, I cannot get the circular hole to centre to the middle of the UIImageView (it is currently centred to the top left of the screen), and that the effect that I am getting is the opposite to what i am trying to achieve (Everything outside of the circle is visible). Below is my code. Please can someone advise?
Result:
class UploadProfileImageViewController: UIViewController {
var scrollView: ReadyToUseScrollView!
let container: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let imgView: UIImageView = {
let imgView = UIImageView()
imgView.backgroundColor = UIColor.black
imgView.contentMode = .scaleAspectFit
imgView.image = UIImage.init(named: "soldier")!
imgView.translatesAutoresizingMaskIntoConstraints = false
return imgView
}()
var overlay: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup(){
view.backgroundColor = UIColor.white
setupViews()
}
override func viewDidLayoutSubviews() {
overlay.center = imgView.center
print("imgView.center: \(imgView.center)")
overlay.layer.layoutIfNeeded() // I have also tried view.layoutIfNeeded()
}
private func setupViews(){
let s = view.safeAreaLayoutGuide
view.addSubview(imgView)
imgView.topAnchor.constraint(equalTo: s.topAnchor).isActive = true
imgView.leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
imgView.trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
imgView.heightAnchor.constraint(equalTo: s.heightAnchor, multiplier: 0.7).isActive = true
overlay = Overlay.init(frame: .zero, center: imgView.center)
print("setup.imgView.center: \(imgView.center)")
view.addSubview(overlay)
overlay.translatesAutoresizingMaskIntoConstraints = false
overlay.topAnchor.constraint(equalTo: s.topAnchor).isActive = true
overlay.leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
overlay.trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
overlay.bottomAnchor.constraint(equalTo: imgView.bottomAnchor).isActive = true
}
private func deg2rad( number: Double) -> CGFloat{
let rad = number * .pi / 180
return CGFloat.init(rad)
}
}
class Overlay: UIView{
var path: UIBezierPath!
var viewCenter: CGPoint?
init(frame: CGRect, center: CGPoint) {
super.init(frame: frame)
self.viewCenter = center
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup(){
backgroundColor = UIColor.black.withAlphaComponent(0.8)
guard let path = createCirclePath() else {return}
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillRule = CAShapeLayerFillRule.evenOdd
self.layer.mask = shapeLayer
}
private func createCirclePath() -> UIBezierPath?{
guard let center = self.viewCenter else{return nil}
let circlePath = UIBezierPath()
circlePath.addArc(withCenter: center, radius: 200, startAngle: 0, endAngle: deg2rad(number: 360), clockwise: true)
return circlePath
}
private func deg2rad( number: Double) -> CGFloat{
let rad = number * .pi / 180
return CGFloat.init(rad)
}
}
CONSOLE:
setup.imgView.center: (0.0, 0.0)
imgView.center: (207.0, 359.0)
Try getting rid of the overlay you have and instead add the below UIView. It's basically a circular UIView with a giant black border, but it takes up the whole screen so the user can't tell. FYI, you need to use .frame to position items on the screen. The below puts the circle in the center of the screen. If you want the center of the image, replace self.view.frame with self. imgView.frame... Play around with circleSize and borderSize until you get the circle size you want.
let circle = UIView()
let circleSize: CGFloat = self.view.frame.height * 2 //must be bigger than the screen
let x = (self.view.frame.width / 2) - (circleSize / 2)
let y = (self.view.frame.height / 2) - (circleSize / 2)
circle.frame = CGRect(x: x, y: y, width: circleSize, height: circleSize)
let borderSize = (circleSize / 2) * 0.9 //the size of the inner circle will be circleSize - borderSize
circle.backgroundColor = .clear
circle.layer.cornerRadius = circle.frame.height / 2
circle.layer.borderColor = UIColor.black.cgColor
circle.layer.borderWidth = borderSize
view.addSubview(circle)
What I need:
What I have:
Here is my current code:
class SchedulerSummaryCell: UITableViewCell {
#IBOutlet weak var oneMileV: UIView! {
didSet {
oneMileV.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: .light)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
oneMileV.insertSubview(blurView, at: 0)
makeCircular(oneMileV)
NSLayoutConstraint.activate([
blurView.heightAnchor.constraint(equalTo: oneMileV.heightAnchor),
blurView.widthAnchor.constraint(equalTo: oneMileV.widthAnchor),
blurView.leadingAnchor.constraint(equalTo: oneMileV.leadingAnchor),
blurView.topAnchor.constraint(equalTo: oneMileV.topAnchor)
])
oneMileV.layer.applySketchShadow(alpha: 0.5, y: 4, blur: 10)
}
}
}
(HELPER)
extension CALayer {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.16,
x: CGFloat = 0,
y: CGFloat = 3,
blur: CGFloat = 6,
spread: CGFloat = 0)
{
masksToBounds = false
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == -1 {return}
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
When I comment out the applySketchShadow code, I get this:
Questions
Why does the gaussian blur cancel out the rounded corners?
Is there a way for me to apply both rounded corners and gaussian blur?
How should I then add an image?
You have to add your UIImageView as a subview to a UIView. The UIView will be responsible for the shadow and the UIImageView is going to take care of the rounded corners. Unfortunately you cannot add a shadow directly to the UIImageView because you need to enable clipsToBounds = true which prevents the view to show content outside of its bounds. This however is necessary for the shadow to be shown.
This will do the work:
let shadowView = UIView()
shadowView.backgroundColor = .clear
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.1, height: 1)
shadowView.layer.shadowRadius = 8
shadowView.layer.shadowOpacity = 0.14
shadowView.layer.masksToBounds = false
let imageView = UIImageView()
imageView.image = UIImage(named: "YourImage")
imageView.clipsToBounds = true
imageView.layer.cornerRadius = imageView.frame.width / 2
shadowView.addSubview(imageView)
For shadow effect use this extention it will help you.
extension UIImageView {
func addShadow() {
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 2, height: 5)
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 1.0
self.clipsToBounds = false
}
}
I am creating a vertical UISlider. I have created the view it is in using all code. (the rest of the storyboard elements are constrained using interface builder)
From what I have read, to create a vertical UISlider you give the UISlider a width and then rotate it. Since the height of the container that the UISlider sits in varies by screen size I do not want to give it a fixed height (width).
This is what I was thinking
// Mark: Slider View
let leftSlider = UISlider()
let centerSlider = UISlider()
let rightSlider = UISlider()
let colorSliders = [leftSlider, centerSlider, rightSlider]
for slider in colorSliders {
slider.translatesAutoresizingMaskIntoConstraints = false
sliderContainer.addSubview(slider)
let w = sliderContainer.bounds.width
slider.bounds.size.width = w
slider.center = CGPoint(x: w/2, y: w/2)
slider.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI_2))
slider.value = 0
slider.minimumValue = 0
slider.maximumValue = 255
let sliderTopConstraint = slider.topAnchor.constraint(equalTo: centerHiddenView.bottomAnchor, constant: 5)
let sliderBottomConstraint = slider.bottomAnchor.constraint(equalTo: sliderContainer.bottomAnchor, constant: 5)
NSLayoutConstraint.activate([sliderTopConstraint, sliderBottomConstraint])
slider.backgroundColor = .purple
slider.isEnabled = true
slider.isUserInteractionEnabled = true
}
let sliderContainerWidth: CGFloat = sliderContainer.frame.width
let centerSliderHorizontalConstraints = centerSlider.centerXAnchor.constraint(equalTo: sliderContainer.centerXAnchor)
let widthConstraint = centerSlider.widthAnchor.constraint(equalToConstant: sliderContainerWidth)
let centerSliderWidthConstraint = centerSlider.widthAnchor.constraint(equalToConstant: 90)
NSLayoutConstraint.activate([centerSliderHorizontalConstraints, centerSliderWidthConstraint, widthConstraint])
But when I run it I get this
which is far better than what I had earlier today. However, I would like the slider to be normal width..and well look normal just vertical
Any ideas on what I missed?
(Oh ignore that little purple offshoot to the left, that is the other 2 sliders that I added but didn't constrain yet.)
You're changing the bounds and the transform of your UISlider and are using Auto-Layout at the same time so it can be a little confusing.
I suggest you don't modify the bounds but use Auto-Layout instead. You should set the slider width to its superview height, and center the slider inside its superview. This way, when you rotate it, its height (after rotation) which is actually its width (before rotation) will be equal to its superview height. Centering the slider vertically will make sure the slider is touching the bottom and the top of its superview.
slider.widthAnchor.constraint(equalTo: sliderContainer.heightAnchor
slider.centerYAnchor.constraint(equalTo: sliderContainer.centerYAnchor)
slider.centerXAnchor.constraint(equalTo: sliderContainer.centerXAnchor)
If you want to place one of the 2 remaining sliders to the left or right of their superview, don't center it horizontally but do the following instead:
slider.leadingAnchor.constraint(equalTo: sliderContainer.leadingAnchor)
slider.trailingAnchor.constraint(equalTo: sliderContainer.trailingAnchor)
Also, carefully check your console for Auto-Layout error logs.
EDIT:
I checked the above code on a test project, here's my view controller code:
class ViewController: UIViewController {
#IBOutlet private weak var containerView: UIView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let leftSlider = UISlider()
let centerSlider = UISlider()
let rightSlider = UISlider()
let colorSliders = [leftSlider, centerSlider, rightSlider]
var constraints = [NSLayoutConstraint]()
for slider in colorSliders {
slider.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(slider)
slider.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI_2))
constraints.append(slider.widthAnchor.constraint(equalTo: containerView.heightAnchor))
constraints.append(slider.centerYAnchor.constraint(equalTo: containerView.centerYAnchor))
slider.backgroundColor = .purple
}
constraints.append(leftSlider.centerXAnchor.constraint(equalTo: containerView.centerXAnchor))
constraints.append(centerSlider.centerXAnchor.constraint(equalTo: containerView.leadingAnchor))
constraints.append(rightSlider.centerXAnchor.constraint(equalTo: containerView.trailingAnchor))
NSLayoutConstraint.activate(constraints)
}
}
And here's what I got:
(as of July 7, 2017)
self.customSlider = [[UISlider alloc] init]];
self.customView = [[UIView alloc] init];
//create the custom auto layout constraints that you wish the UIView to have
[self.view addSubview:self.customView];
[self.customView addSubview:self.customSlider];
self.slider.transform = CGAffineTransformMakeRotation(-M_PI_2);
/*Create the custom auto layout constraints for self.customSlider to have these 4 constraints:
//1. self.customSlider CenterX = CenterX of self.customView
//2. self.customSlider CenterY = CenterY of self.customView
//3. self.customSlider width = self.customView height
//4. self.customSlider height = self.customView width
*/