iOS multiple CALayers being added but only one gets applied - ios

I want to add a left/right border to my textfield. It is an IBOutlet. Also if I also add the function to style the textfield inside of the didEdit delegate callback it will update correctly.
Here is my code. Inside of ViewDidLoad.
let leftLayer = CALayer()
leftLayer.frame = CGRect(x: 0, y: 0, width: 1, height: (password.frame.size.height - 1))
leftLayer.backgroundColor = UIColor.gray.cgColor
let rightLayer = CALayer()
rightLayer.frame = CGRect(x: password.frame.size.width - 1, y: 0, width: 1, height: (password.frame.size.height - 1))
rightLayer.backgroundColor = UIColor.gray.cgColor
password.layer.setNeedsDisplay()
password.layer.addSublayer(leftLayer)
password.layer.display()
password.layer.setNeedsDisplay()
password.layer.addSublayer(rightLayer)
password.layer.display()
I've taken out all code except
let rightLayer = CALayer()
rightLayer.frame = CGRect(x: password.frame.size.width - 1, y: 0, width: 1, height: (password.frame.size.height - 1))
rightLayer.backgroundColor = UIColor.gray.cgColor
password.layer.setNeedsDisplay()
password.layer.addSublayer(rightLayer)
password.layer.display()
But iOS simply will not add this layer. I tried the snippet below
var once = true
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if once {
styleTextViews()
once = false
}
}
The only thing that does work is if I call it from
func textFieldDidBeginEditing(_ textField: UITextField) {
styleTextViews()
}
Then it adds it just fine..

Your problem is in ViewDidLoad you need to set this code inside viewDidLayoutSubviews because it contains the correct frame
var once = true // as the function runs multiple times
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if once {
// here
once = false
}
}

Related

UIButton loses responsivity(i.e can't press it), maybe because of CGAffineTransform

Firstly, I think that the problem is on the UIVisualEffectView, but now I think that the problem is on the CGAffineTransform that I'm using to animate the view on the openOptionsTooltip()
First, I found a similar problem on that question (see the comments on the accepted answer), but in my case I'm adding a button as subview of the UIVisualEffectView, not adding it as a button subview.
Apple documentation says:
Depending on the desired effect, the effect may affect content layered behind the view or content added to the visual effect view’s contentView.
But I can't find a better explanation on how or why it can affect the content added to the contentView.
I'm doing all the stuff programmatically, without any xib.
The visual effect view is a balloon like a tooltip that appears when I touch the titleView.
I'm creating that view on viewDidLoad and holding a
reference to it on the viewController.
After created, when I click on the titleView I'm adding it as a subview of UIWindow
Here's my code:
The method to create the view:
func createOptionsTooltip(withNamesAndActions options: [(name: String , action: Selector)]){
self.tooltipSize = CGSize(width: 250, height: CGFloat(60*options.count+12))
var optionItens : [UIView] = []
for i in 0..<options.count{
let optionView = UIView(frame: CGRect(x: .zero, y: CGFloat(60*i), width: self.tooltipSize.width, height: 60))
optionView.backgroundColor = .clear
let separatorView = UIView(frame: CGRect(x: .zero, y: 59, width: self.tooltipSize.width, height: 1))
separatorView.backgroundColor = UIColor(white: 0.8, alpha: 0.5)
let button = UIButton(frame: CGRect(x: .zero, y: .zero, width: self.tooltipSize.width, height: 59))
button.setTitle(options[i].name, for: .normal)
button.setTitleColor(self.view.tintColor, for: .normal)
button.addTarget(self, action: options[i].action, for: .touchUpInside)
button.isEnabled = true
button.isUserInteractionEnabled = true
optionView.addSubview(button)
optionView.addSubview(separatorView)
optionView.bringSubviewToFront(button)
optionItens.append(optionView)
}
self.blackOpaqueView = UIView(frame: self.view.bounds)
self.blackOpaqueView.backgroundColor = UIColor.black
self.blackOpaqueView.alpha = 0
let gesture = UITapGestureRecognizer(target: self, action: #selector(ScheduleNavigationController.closeOptionsTooltip))
// blackOpaqueView.addGestureRecognizer(gesture)
self.vwTooltip = UIView(frame: CGRect(origin: CGPoint(x: self.view.frame.midX - self.tooltipSize.width/2, y: self.view.frame.minY+self.navigationBar.frame.size.height+UIApplication.shared.statusBarFrame.height+10), size: CGSize(width: 1, height: 1)))
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 0.01)
self.vwTooltip.isHidden = true
let vwAim = UIVisualEffectView(frame: CGRect(x: self.tooltipSize.width/2 - 6, y: 0, width: 12, height: 10))
let vwBalloon = UIVisualEffectView(frame: CGRect(x: 0, y: vwAim.frame.size.height, width: self.tooltipSize.width, height: CGFloat(60*options.count)))
vwBalloon.cornerRadius = 8
vwBalloon.contentView.isUserInteractionEnabled = true
vwBalloon.isUserInteractionEnabled = true
let bezPath = trianglePathWithCenter(rect: vwAim.frame)
let maskForPath = CAShapeLayer()
maskForPath.path = bezPath.cgPath
vwAim.layer.mask = maskForPath
vwBalloon.effect = UIBlurEffect(style: .prominent)
vwAim.effect = UIBlurEffect(style: .prominent)
for optionView in optionItens{
vwBalloon.contentView.addSubview(optionView)
vwBalloon.bringSubviewToFront(optionView)
}
self.vwTooltip.addSubview(vwBalloon)
self.vwTooltip.addSubview(vwAim)
self.vwTooltip.bringSubviewToFront(vwBalloon)
vwBalloon.bringSubviewToFront(vwBalloon.contentView)
}
And I'm calling't on the viewDidLoad that way:
createOptionsTooltip(withNamesAndActions: [("Sair", #selector(ScheduleNavigationController.closeSchedule(_:))), ("Hoje", #selector(ScheduleNavigationController.scrollToToday(_:)))])
And I'm open the tooltip animated this way:
func openOptionsTooltip(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
appDelegate.window?.addSubview(blackOpaqueView)
appDelegate.window?.addSubview(vwTooltip)
appDelegate.window?.bringSubviewToFront(vwTooltip)
UIView.animate(withDuration: 0.2, animations: {
self.blackOpaqueView.alpha = 0.5
}) { _ in
self.vwTooltip.isHidden = false
UIView.animate(withDuration: 0.1, animations: {
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 100)
})
}
}
}
What I have tried:
Bring the buttons and all it's superview in hierarch to front using bringSubviewToFront. (I have checked, there's no view on the front of the button)
change the isUserInteractEnabled for all button superviews in hierarch.(for both false and true)
Change the way that I'm adding the target to buttons, without using the method parameter.
Change the selector method to any other method that work on other targets and switch the method between #objc and #IBOulet
Change the buttons to a view other than the UIVisualEffectView
PS: I found that the UIVisualEffectView has 3 subviews, but apparently the contentView is the above view, see the log of printing it's subviews:
▿ 3 elements
- 0 : <_UIVisualEffectBackdropView: 0x102dcd140; frame = (0 0; 250 120); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x280d1b480>>
- 1 : <_UIVisualEffectSubview: 0x102dcd350; frame = (0 0; 250 120); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x280d15c80>>
- 2 : <_UIVisualEffectContentView: 0x102dcc940; frame = (0 0; 250 120); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x280d1ace0>>
I can't figure out what is blocking the button's user interaction
Here's the result:
I have found the problem:
After calling the openOptionsTooltip() the transform inside't are resizing all it's subview, but the vwTooltp itself don't change it's frames, but because it clipToBounds property are false, all the subviews are showing, but the user can't access the button actions because the view has 1x1 size. Changing the openOptionsTooltip() method to that:
func openOptionsTooltip(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
appDelegate.window?.addSubview(blackOpaqueView)
appDelegate.window?.addSubview(vwTooltip)
appDelegate.window?.bringSubviewToFront(vwTooltip)
UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations:{
self.blackOpaqueView.alpha = 0.5
}) { _ in
self.vwTooltip.isHidden = false
UIView.animate(withDuration: 0.1, delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations:{
self.vwTooltip.transform = self.vwTooltip.transform.translatedBy(x: 1, y: 1)
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 100)
}){ _ in
self.vwTooltip.frame = self.tooltipFrame
}
}
}
}
And now it's working properly. Thanks for the responses

object value doesn't change after didset

I am using some cocoa pods frame to make UICollectionView. I am able to get the right data for each cell and setup views.
I want to have different ani
The animation end point should be related to the value of dataSource.The problem is that I can't use the didSet value as I wish. The frame of subviews I added are (0,0,0,0), the values of objects keep the same as initial value outside the didSet. I am not able to access the dataSource Value in setupView function.
Expected effect is that the red bar will move from left side to the right position:
class CereCell : DatasourceCell {
override var datasourceItem: Any?{
didSet {
guard let cere = datasourceItem as? CeremonyDay else { return }
nameLabel.text = cere.name
nameLabel.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
currentBar.frame = CGRect(x: 100, y: 100, width: cere.percentage*100 , height: 10)
position.x = cere.percentage
}
}
let currentBar: UIView = {
let currentBar = UIView(frame: CGRect(x: 290, y: 100, width: 300, height: 10))
currentBar.backgroundColor = .red
return currentBar
}()
override func setupViews() {
super.setupViews()
contentView.addSubview(currentBar)
let frame = currentBar.frame
print(frame)
It just print the (290, 100, 300, 10), which is not the value in didSet.

Swift : drawing the dots in loop

I am trying to draw multiple dots in a loop by passing set of points. But not a single dot is getting drawn on view. Idea - I am parsing xml document and extracting point to draw it on view.
Edit - I have updated the code with suggested changes and it is working.
//View Class
class DrawTrace: UIView
{
var points = [CGPoint]()
{
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect)
{
let size = CGSize(width: 1, height: 1)
UIColor.white.set()
for point in points
{
print(point.x,point.y)
let dot = UIBezierPath(ovalIn: CGRect(origin: point, size: size))
dot.fill()
}
}
}
//View Controller Class
class ViewController: UIViewController
{
var object : traceDoc = traceDoc()
let trace = DrawTrace(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
override func viewDidLoad()
{
super.viewDidLoad()
object.collectionOfData()
trace.points = object.tracePoints
self.view.addSubview(trace)
}
}
Added view instance to view hierarchy that is in view controller.Created instance of DrawTrace and appended to tracepoints array.
Your code is way more complex than it needs to be. One big issue is that you keep adding more and more layers each time draw is called.
There is no need to use layers to draw dots in your custom view. Here is a much simpler solution:
class DrawTrace: UIView
{
var points = [CGPoint]() {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect)
{
let size = CGSize(width: 2, height: 2)
UIColor.black.set()
for point in points {
let dot = UIBezierPath(ovalIn: CGRect(origin: point, size: size))
// this just draws the circle by filling it.
// Update any properties of the path as needed.
// Use dot.stroke() if you need an outline around the circle.
dot.fill()
}
}
}
let trace = DrawTrace(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
trace.backgroundColor = .white
trace.points = [ CGPoint(x: 10, y: 10), CGPoint(x: 35, y: 100)]
The above can be copied into a playground.
Black dotes on black view aren't visible, try to make dotes white:
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
Also, in UIViewController code you are not adding your DrawTrace to views hierarchy, try in viewDidLoad:
self.view.addSubview(drawTraceInstance)
Finally, it seems that you have to iterate over array of CGPoint's. Instead of use stride, user FastEnumeration over Array:
for point in points
{
drawDot(x: point.x, y: point.y,Color: fillColor, size: size)
}

Radial Gradience Colors Not Updating in UIView

I am attempting to use radial gradience within my app on a background UIView. My issue comes to play, where I want to update the view colors of the gradience multiple times. I have no errors with my code, but I can't seem to figure out how to get around this.
What I have tried is reloading the Input Views within the regular UIView as-well as the gradience class; remove the subview of the uiview, and adding a new view to the screen, which worked for only change of set colors; and I have looked over the internet, but can't seem to resolve this. All I want is for the UIView to update its colors based on the new color parameters I give it.
Here is my radial gradience code:
import UIKit
class RadialGradient: UIView {
var innerColor = UIColor.yellow
var outterColor = UIColor.red
override func draw(_ rect: CGRect) {
let colors = [innerColor.cgColor, outterColor.cgColor] as CFArray
let endRadius = min(frame.width, frame.height)
let center = CGPoint(x: bounds.size.width/2, y: bounds.size.height/2)
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
UIGraphicsGetCurrentContext()!.drawRadialGradient(gradient!,
startCenter: center,
startRadius: 0.0,
endCenter: center,
endRadius: endRadius,
options: CGGradientDrawingOptions.drawsAfterEndLocation)
}
}
Here is where I am using it:
import UIKit
class TestIssuesVC: UIViewController {
var check : Bool = false
#IBAction func buttonClicked(_ sender: Any) {
if check == true {
backgroundsetting.removeFromSuperview()
print("Why wont you change to purple and black?????")
cheapFix(inner: UIColor.purple, outter: UIColor.black)
} else {
backgroundsetting.removeFromSuperview()
cheapFix(inner: UIColor.red, outter: UIColor.blue)
check = true
}
}
func cheapFix(inner: UIColor, outter: UIColor) {
let backgroundsetting = RadialGradient()
backgroundsetting.innerColor = inner
backgroundsetting.outterColor = outter
backgroundsetting.frame = (frame: CGRect(x: self.view.frame.size.width * 0, y: self.view.frame.size.height * 0, width:self.view.frame.size.width, height: self.view.frame.size.height))
self.view.addSubview(backgroundsetting)
self.view.sendSubview(toBack: backgroundsetting)
self.reloadInputViews()
}
let backgroundsetting = RadialGradient()
override func viewWillAppear(_ animated: Bool) {
backgroundsetting.innerColor = UIColor.green
backgroundsetting.outterColor = UIColor.red
backgroundsetting.frame = (frame: CGRect(x: self.view.frame.size.width * 0, y: self.view.frame.size.height * 0, width:self.view.frame.size.width, height: self.view.frame.size.height))
self.view.addSubview(backgroundsetting)
self.view.sendSubview(toBack: backgroundsetting)
self.reloadInputViews()
}
}
I see two things.
Your cheapFix method never updates the backgroundsetting property. It creates its own local variable of the same name. So you are actually adding new views over and over but each is sent to the back so you only ever see the first one. This is why nothing ever appears to change.
None of that is necessary. Simply create one RadialGradient view. When you want its colors to change, simply update its colors. That class needs to be fixed so it redraws itself when its properties are updated.
Make the following change to the two properties in your RadialGradient class:
var innerColor = UIColor.yellow {
didSet {
setNeedsDisplay()
}
}
var outterColor = UIColor.red {
didSet {
setNeedsDisplay()
}
}
Those changes will ensure the view redraws itself when its colors are updated.

UIActivityIndicatorView dialog over view

In my app I'm using a custom UIActivityIndicator view (this) and it works great.
I have an UITableView and I want the indicator over the tableview on loading data. To solve the problem I have created an empty view with a background above all and when I need to start indicator's animation I simply hide tableview and show the empty view + the indicator.
The result is this:
I saw somewhere that I can use instead something like this:
How can I do?
Thanks in advance.
You could try adding a translucent Black view over the Table View and put the custom activity indicator code as a subview in the Black view.
let aBlackView = UIView(frame: CGRectMake(0, 0, self.view.bounds.width, self.view.bounds.height))
aBlackView.backgroundColor = UIColor.blackColor()
aBlackView.alpha = 0.75 // Change the alpha depending on how much transparency you require
let aMainWindow = UIApplication.sharedApplication().delegate!.window
aMainWindow!!.addSubview(aBlackView)
/* Enter you code for NVActivityIndicatorViewable here */
aBlackView.addSubview(/* NVActivityIndicatorViewable */)
Once you are done with your request, you can remove the Black view by calling this code:
aBlackView.removeFromSuperview()
I created a activity showing screen . I used in every where in my project.
//ProgressBarNotification.swift
import Foundation
import UIKit
class ProgressBarNotification {
internal static var strLabel = UILabel()
internal static var messageFrame = UIView()
internal static var activityIndicator = UIActivityIndicatorView()
internal static func progressBarDisplayer(msg:String,indicator:Bool ){
let view1 = UIApplication.sharedApplication().delegate?.window!!.rootViewController?.view
view1?.userInteractionEnabled = false
strLabel = UILabel(frame: CGRect(x: 50, y: 0, width: 200, height: 50))
strLabel.text = msg
strLabel.textColor = UIColor.whiteColor()
messageFrame = UIView(frame: CGRect(x: view1!.frame.midX - 90, y: view1!.frame.midY - 25 , width: 180, height: 50))
messageFrame.layer.cornerRadius = 15
messageFrame.backgroundColor = UIColor(white: 0, alpha: 0.7)
if indicator {
activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.White)
activityIndicator.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
activityIndicator.startAnimating()
messageFrame.addSubview(activityIndicator)
}
messageFrame.addSubview(strLabel)
view1!.addSubview(messageFrame)
}
internal static func removeProgressBar() {
let view1 = UIApplication.sharedApplication().delegate?.window!!.rootViewController?.view
_ = view1?.subviews.filter({ (view) -> Bool in
if view == ProgressBarNotification.messageFrame {
view.removeFromSuperview()
}
return false
})
ProgressBarNotification.messageFrame.removeFromSuperview()
view1?.userInteractionEnabled = true
}
deinit{
}
}
Usage
//To start Loader
ProgressBarNotification.progressBarDisplayer("Loading", indicator: true)
//Stop
ProgressBarNotification.removeProgressBar()

Resources