Swift Scale Animation is not animating view - ios

I would simply like to have a pulsating UIView. For that I have set up this:
let highlightView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .lightBlueCustom
v.alpha = 0.9
return v
}()
let scaleAnimation: CABasicAnimation = {
let v = CABasicAnimation(keyPath: "transform.scale")
v.duration = 0.5
v.repeatCount = .infinity
v.autoreverses = true
v.fromValue = 1.0
v.toValue = 1.4
return v
}()
And this is how I call it:
func showHighlightView(viewToHighlight: UIView, height: CGFloat) {
self.view.addSubview(highlightView)
highlightView.heightAnchor.constraint(equalTo: viewToHighlight.heightAnchor).isActive = true
highlightView.widthAnchor.constraint(equalTo: highlightView.heightAnchor).isActive = true
highlightView.centerXAnchor.constraint(equalTo: viewToHighlight.centerXAnchor).isActive = true
highlightView.centerYAnchor.constraint(equalTo: viewToHighlight.centerYAnchor).isActive = true
highlightView.layer.cornerRadius = height/2
highlightView.layer.add(self.scaleAnimation, forKey: "scale")
self.view.bringSubviewToFront(viewToHighlight)
self.view.bringSubviewToFront(highlightView)
}
func showWishIntro() {
showHighlightView(viewToHighlight: self.addWishButton, height: 60)
}
But this is not working. It shows the highlightView correctly but there is no animation. What am I missing here?

ok so I fixed it... it was just a lucky guess but I am calling showWishIntro now in viewDidLayoutSubviews and not it is working as expected. I have no idea why, so if anyone would care to explain just let me know :D I'm happy that it works.

Related

Programmatically center UIImage inside parent view vertically

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

Efficient off-screen UIView rendering and mirroring

I have a "off-screen" UIView hierarchy which I want render in different locations of my screen. In addition it should be possible to show only parts of this view hierarchy and should reflect all changes made to this hierarchy.
The difficulties:
The UIView method drawHierarchy(in:afterScreenUpdates:) always calls draw(_ rect:) and is therefore very inefficient for large hierarchies if you want to incorporate all changes to the view hierarchy. You would have to redraw it every screen update or observe all changing properties of all views. Draw view hierarchy documentation
The UIView method snapshotView(afterScreenUpdates:) also does not help much since I have not found a way to get a correct view hierarchy drawing if this hierarchy is "off-screen". Snapshot view documentation
"Off-Screen": The root view of this view hierarchy is not part of the UI of the app. It has no superview.
Below you can see a visual representation of my idea:
Here's how I would go about doing it. First, I would duplicate the view you are trying to duplicate. I wrote a little extension for this:
extension UIView {
func duplicate<T: UIView>() -> T {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self)) as! T
}
func copyProperties(fromView: UIView, recursive: Bool = true) {
contentMode = fromView.contentMode
tag = fromView.tag
backgroundColor = fromView.backgroundColor
tintColor = fromView.tintColor
layer.cornerRadius = fromView.layer.cornerRadius
layer.maskedCorners = fromView.layer.maskedCorners
layer.borderColor = fromView.layer.borderColor
layer.borderWidth = fromView.layer.borderWidth
layer.shadowOpacity = fromView.layer.shadowOpacity
layer.shadowRadius = fromView.layer.shadowRadius
layer.shadowPath = fromView.layer.shadowPath
layer.shadowColor = fromView.layer.shadowColor
layer.shadowOffset = fromView.layer.shadowOffset
clipsToBounds = fromView.clipsToBounds
layer.masksToBounds = fromView.layer.masksToBounds
mask = fromView.mask
layer.mask = fromView.layer.mask
alpha = fromView.alpha
isHidden = fromView.isHidden
if let gradientLayer = layer as? CAGradientLayer, let fromGradientLayer = fromView.layer as? CAGradientLayer {
gradientLayer.colors = fromGradientLayer.colors
gradientLayer.startPoint = fromGradientLayer.startPoint
gradientLayer.endPoint = fromGradientLayer.endPoint
gradientLayer.locations = fromGradientLayer.locations
gradientLayer.type = fromGradientLayer.type
}
if let imgView = self as? UIImageView, let fromImgView = fromView as? UIImageView {
imgView.tintColor = .clear
imgView.image = fromImgView.image?.withRenderingMode(fromImgView.image?.renderingMode ?? .automatic)
imgView.tintColor = fromImgView.tintColor
}
if let btn = self as? UIButton, let fromBtn = fromView as? UIButton {
btn.setImage(fromBtn.image(for: fromBtn.state), for: fromBtn.state)
}
if let textField = self as? UITextField, let fromTextField = fromView as? UITextField {
if let leftView = fromTextField.leftView {
textField.leftView = leftView.duplicate()
textField.leftView?.copyProperties(fromView: leftView)
}
if let rightView = fromTextField.rightView {
textField.rightView = rightView.duplicate()
textField.rightView?.copyProperties(fromView: rightView)
}
textField.attributedText = fromTextField.attributedText
textField.attributedPlaceholder = fromTextField.attributedPlaceholder
}
if let lbl = self as? UILabel, let fromLbl = fromView as? UILabel {
lbl.attributedText = fromLbl.attributedText
lbl.textAlignment = fromLbl.textAlignment
lbl.font = fromLbl.font
lbl.bounds = fromLbl.bounds
}
if recursive {
for (i, view) in subviews.enumerated() {
if i >= fromView.subviews.count {
break
}
view.copyProperties(fromView: fromView.subviews[i])
}
}
}
}
to use this extension, simply do
let duplicateView = originalView.duplicate()
duplicateView.copyProperties(fromView: originalView)
parentView.addSubview(duplicateView)
Then I would mask the duplicate view to only get the particular section that you want
let mask = UIView(frame: CGRect(x: 0, y: 0, width: yourNewWidth, height: yourNewHeight))
mask.backgroundColor = .black
duplicateView.mask = mask
finally, I would scale it to whatever size you want using CGAffineTransform
duplicateView.transform = CGAffineTransform(scaleX: xScale, y: yScale)
the copyProperties function should work well but you can change it if necessary to copy even more things from one view to another.
Good luck, let me know how it goes :)
I'd duplicate the content I wish to display and crop it as I want.
Let's say I have a ContentViewController which carries the view hierarchy I wish to replicate. I would encapsule all the changes that can be made to the hierarchy inside a ContentViewModel. Something like:
struct ContentViewModel {
let actionTitle: String?
let contentMessage: String?
// ...
}
class ContentViewController: UIViewController {
func display(_ viewModel: ContentViewModel) { /* ... */ }
}
With a ClippingView (or a simple UIScrollView) :
class ClippingView: UIView {
var contentOffset: CGPoint = .zero // a way to specify the part of the view you wish to display
var contentFrame: CGRect = .zero // the actual size of the clipped view
var clippedView: UIView?
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
clippedView?.frame = contentFrame
clippedView?.frame.origin = contentOffset
}
}
And a view controller container, I would crop each instance of my content and update all of them each time something happens :
class ContainerViewController: UIViewController {
let contentViewControllers: [ContentViewController] = // 3 in your case
override func viewDidLoad() {
super.viewDidLoad()
contentViewControllers.forEach { viewController in
addChil(viewController)
let clippingView = ClippingView()
clippingView.clippedView = viewController.view
clippingView.contentOffset = // ...
viewController.didMove(to: self)
}
}
func somethingChange() {
let newViewModel = ContentViewModel(...)
contentViewControllers.forEach { $0.display(newViewModel) }
}
}
Could this scenario work in your case ?

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.

iOS How to make user-resizable uiview like below?

I make CAShapeLayer, and create four uiview at all corner of that Layer.
And add pan gesture to all these 4 UIView. when anyone of them moves it Layer update its frame according to it.
Any idea? Efforts would be appreciated.
topLeftControl = addControl(tl, pos: 1)
topRightControl = addControl(tr, pos: 2)
bottomLeftControl = addControl(bl, pos: 3)
bottomRightControl = addControl(br, pos: 4)
func addControl(p : CGPoint,pos : Int) -> (UIView)
{
var r : CGRect = CGRectZero
r.origin = p
r.size = CGSizeMake(15, 15)
if(DeviceType.iPad)
{
r.size = CGSizeMake(55, 55)
}
let v = UIView.init(frame: r)
v.layer.cornerRadius = v.frame.size.width / 2
v.clipsToBounds = true
v.backgroundColor = UIColor.whiteColor()
v.userInteractionEnabled = true
self.mainView.addSubview(v)
let pan = UIPanGestureRecognizer.init(target: self, action: #selector(ViewController.dragMaker(_:)))
v.addGestureRecognizer(pan)
return v
}

How come I can't access the methods of my subview?

In my ViewDidLoad:
let spiralDimension = CGFloat(ScreenWidth! * 0.10)
let spiralName = "spiral.png"
let spiralImage = UIImage(named: spiralName)
spiralView = UIImageView(image: spiralImage!)
spiralView!.frame = CGRect(x: ScreenWidth! / 2 - spiralDimension/2, y: (ScreenHeight!-TabBarHeight!) / 2 - spiralDimension/2 , width: spiralDimension, height: spiralDimension)
spiralView!.tintColor = UIColor.redColor()
self.view.addSubview(spiralView!) //works!!!
Somewhere later...
func fadeBackground(){
UIView.animateWithDuration(self.fadeTime, delay: 0, options: UIViewAnimationOptions.AllowUserInteraction, animations: {
var randomIndex = Int(arc4random_uniform(UInt32(CONSTANTS.MainColorScheme.count)))
var subviews = self.view.subviews
for v in subviews{
if v.isKindOfClass(UIImageView){
println(v) //correctly prints my spiral!
v.tintColor = CONSTANTS.MainColorScheme[randomIndex] //can't do it. XCode won't even auto-complete
}
}
}) { (stuff Bool) -> Void in
}
}
I can't assign tintColor to v, even though it prints my class correctly. Can't build successfully.
subviews returns an array of AnyObject. Thus, you need to cast v in order to set tintColor.
Try:
for v in subviews{
if let v = v as? UIImageView {
println(v)
v.tintColor = CONSTANTS.MainColorScheme[randomIndex]
}
}
You have to typecast it.
if v.isKindOfClass(UIImageView){
let iv = v as! UIImageView
v.tintColor = ...
The problem is that the subviews property is defined in iOS as a variable of type [AnyObject] so Swift doesn't know that your subviews are members of UIView, which has the property tintColor.

Resources