UITableViewCell stacking layers - ios

I have a custom UITableViewCell that contains an image, I added an extension to UIImageView that adds a layer to it to overlay a lighter color.
func overlay(widht: CGFloat, height: CGFloat, color: UIColor) {
let colorOverlay = CALayer()
colorOverlay.frame = CGRect(x: 0, y: 0, width: widht, height: height)
colorOverlay.backgroundColor = color.cgColor
self.layer.addSublayer(colorOverlay)
self.layer.masksToBounds = true
}
The issue here is that every time the cell gets reused, the layer gets added and thus they get stacked until, well, the image is no longer visible and all that's visible is a gray rectangle
self.image.overlay(widht: self.image.frame.width, height: self.image.frame.height, color: UIColor.gray)
How could I prevent the layer from stacking? Or how could I add an overlaying color to the image without using CALayer() that works with reusable cells

The problem should be you are adding the overlay to your image view every time you config your cell. Keep in mind, UITableView reuse the same cell instance if applicable. Doing through what to control your visual output is totally open. But make sure if your code run twice won't lead to any difference or to limit the execution of that piece of code only once is what you should focus here.

Related

how to change size of customView passed as UICalanderView Decoration?

I could not find much detail about how to add a customView as decoration for UICalenderView. There are many blogs telling how to add images but could not find anyone about CustomView. In images, we can return the decoration item with size parameter however in case of customView there is no option to pass size along with customView that you are adding. So in the end, I was able to add a view with red background, but the size is wrong. I tried to create a view and give it frame but it had no effect. So im confused how to adjust its size. Here is the method in which I add customView that im creating:
func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
return .customView(addActivityCircle)
}
And this is my addActivityCircle method which for now just creating a view with red background color:
private func addActivityCircle() -> UIView {
let view = UIView()
view.backgroundColor = .red
view.clipsToBounds = false
view.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
return view
}
When I run this code I do see a view with red color but it's like a small rectangle, not 50x50. If I pass small values like 20x20, I do see a small rectangle but anything above that I see a rectangle of fixed size. I think that's the limit of decoration item but in apps like Fitness app by apple, there are bigger activity rings than that so there should be a way to have bigger sized custom views as this is just too small. The width is fine but the height is just too less. This is what im getting and it does not get any higher than that:

Color unselected segment of UISegmentedControl without hiding selection

I wanted to color each segment in my UISegmentedControl in a different color. I used the following Swift code (there were some changes with segmentedControl in iOS 13):
segmentedControl.selectedSegmentTintColor = .white
let col: UIColor = .yellow
var subViewOfSegment: UIView = segmentedControl.subviews[2] as UIView
subViewOfSegment.layer.backgroundColor = col.cgColor
This works in general, one segment is now coloured. However, the selected segment is not shown anymore. The selected segment is supposed to be white, but it seems to be overlaid by the colour. The following image show how it looks when I select each segment from left to right:
I already tried subViewOfSegment.backgroundColor = col instead (same effect) or subViewOfSegment.tintColor = col (no effect at all in iOS 13) but I can't get the colors without hiding the selection. On other posts I only find this answer which doesn't say how to color unselected segments.
iOS 13 (Xcode 13.4) Segment Control 100% Working
mySegmentedControl.selectedSegmentTintColor = .white
let subView = mySegmentedControl.subviews[1] as UIView
subView.layer.backgroundColor = UIColor.yellow.cgColor
let subViewOfSegment: UIView = segmentedControl.subviews[1] as UIView
subViewOfSegment.backgroundColor = UIColor.red
subViewOfSegment.layer.zPosition = -999
> above solution is not proper but one of the way you can achieve it. you need manage one more label while select segment is red"`
`
You can do it by using the following code:
init(){
UISegmentedControl.appearance().backgroundColor = .yellow
}
var body: some View{
your code…
}
if you want to change the selected segment’s color, you can use:
UISegmentedControl.appearance().selectedSegmentTintColor = .blue
to change it. I hope these codes can help you :D
For detail, you can visit here.
Using Apple APIs, you could try drawing an image with the 4 colors and set that as background using setBackgroundImage(_:for:barMetrics:).
Here's some code to draw a 40x10 image with just one color to get you started:
let rect = CGRect(origin: CGPoint(x: 0, y:0), size: CGSize(width: 40, height: 10))
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(color.cgColor)
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
To get an image with your four colors, you need to repeat context.setFillColor(<varying color>) and context.fill(<varying rect>) four times.
The image should stretch, which means you it only needs to have four equally sized colored rectangles; you don't need to create it in the exact size of the segmented control.
You can then set the selected segment color using the selectedSegmentTintColor property.

Setting opacity multiple times within a UIView.animate block results in unexpected initial animation conditions

I am seeing an unexpected initial condition when I set layer opacity multiple times within a UIView.animate block:
movingView.frame = CGRect(x: 120, y: 120, width: 100, height: 100)
movingView.backgroundColor = UIColor.blue
opacityView.backgroundColor = UIColor.red
opacityView.layer.opacity = 0.5
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
UIView.animate(withDuration: 3.0) {
opacityView.layer.opacity = 0
opacityView.layer.opacity = 1
movingView.frame = CGRect(x: 10, y: 120, width: 100, height: 100)
movingView.frame = CGRect(x: 190, y: 120, width: 100, height: 100)
}
}
When this code is run in the playground, the blue (bottom) square animates from the middle of the view to the right edge, as expected, even though the frame is initially set to the left hand position, only the last value is used in the animation.
The red square however, does not animate from 0.5 opacity directly to 1.0. It first jumps to 0.0 opacity with no animation before then animating to 1.0 opacity.
Obviously this is a contrived case, but I ran into this when animating only one of a group of elements, I set all elements to opacity 0, then set the one I wished to be visible to animate to opacity 1. But this did not give the expected animations. I was able to work around this by removing the one I wished to be visible from list of all items.
But I am curious and would like to understand what is occurring here, and why the opacity behaviour differs from animating frame values, and if there is another cleaner way to work around this.
============================================
For clarity, I understand what will make this work, but this is a contrived example, in order to make it work in the real world, my code becomes unnecessarily more complex. I want to know why it is exhibiting this behaviour, and why it is different from animating the frame.
If I understand what you expect, you want to animate your view from 0.5 to 0 then to 1.
In order to do that you should use two animations. Fire the first one then add a completion handler to fire the second one.
Also, you can try to work with alpha instead of opacity.
I hope it helps.

iOS - UITextField: extend the bottom line for ANY resolution

I created a UITextFiled with a bottom line using this:
let Bottomline CALayer = ()
bottomLine.frame CGRect = (x: 0, y: usernameTextField.frame.height-7, width: usernameTextField.frame.width, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
TextField.borderStyle = UITextBorderStyle.none
TextField.layer.addSublayer (Bottomline)
and the result of an iPhone 6 (right) is this:
Ok.
✄------------------------
The problem is to run the same application on a Pro iPad, because the
bottom line does not extend following the UITextField, but is shorter
This is the result on iPad Pro:
I do not understand why the bottom line does not follow the UITextField. When I called the bottom line I defined as:
bottomLine.frame CGRect = (x: 0, y: usernameTextField.frame.height-7, width: usernameTextField.frame.width, height: 1)
I have specified that the length of the line at the bottom must be:
width: usernameTextField.frame.width
What's the problem?
EDIT 1: The contrains are correct, because the UITextField adapts to
all types of resolution
EDIT:2 Thanks Matt! Now work!!!
I do not understand why the bottom line does not follow the UITextField
Because it's a layer. Layers do not automatically change size when their superlayer (the text field) changes size.
So, you need to redraw the bottom line every time the text field changes size.
At the moment, though, you are configuring the "bottom line" layer in viewDidLoad. So you are basing it on the frame that the text field has at that moment. But the text field has not yet attained its real size. Then it does change size, and meanwhile your "bottom line" layer just sits there — so now it is the wrong size.
An easy solution is to subclass UITextField and redraw the line every time layoutSubviews is called:
class MyTextField : UITextField {
var lineLayer : CALayer?
override func layoutSubviews() {
super.layoutSubviews()
self.lineLayer?.removeFromSuperlayer()
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: self.bounds.height-7, width: self.bounds.width, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(bottomLine)
self.lineLayer = bottomLine
}
}
If your text field is a MyTextField, it will behave exactly as you desire.

Swift Dynamically created UILabel shows up twice

I have written a custom graph UIView subclass, and I use it to graph some basic data, and insert some user-defined data. As a final step, I'd like to add a UILabel on top of the graph with the user-defined data-point called out.
I highlight the point, and then create and add the UILabel:
if(graphPoints[i] == highlightPoint){
var point2 = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
point2.x -= 8.0/2
point2.y -= 8.0/2
let circle2 = UIBezierPath(ovalInRect:
CGRect(origin: point2,
size: CGSize(width: 8.0, height: 8.0)))
highlightColor.setFill()
highlightColor.setStroke()
circle2.fill()
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
circle.fill()
var pointLabel : UILabel = UILabel()
pointLabel.text = "Point = \(graphPoints[i])"
pointLabel.frame = CGRectMake(point2.x, point2.y, 100, 50)
self.addSubview(pointLabel)
} else {
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
circle.fill()
}
This looks like it should work, but the UILabel is added twice. What am I doing wrong?
This code is probably in drawRect. You're doing subview-adding in drawRect which is incorrect. drawRect gets called at least twice as the view appears, and perhaps many times after that.
You should be overriding some early lifecycle method, like awakeFromNib() or the constructor. In that, construct your label and add it as a child view. This way the addSubView() happens once as it should. The label will be invisible having no contents, so don't worry about that.
In drawRect, just update all the necessary attributes of the label including the frame.
If you find you're actually needing lots of text "labels" that come and go, different quantity for every graph, you don't really want UILabels as subviews after all. Consider directly drawing text on screen with someString.drawAtPoint(...)

Resources