Gradient layer not in the right place - ios

I have the following code as follows:
playView.layer.cornerRadius = 16
let gradient1 = CAGradientLayer()
gradient1.frame = playView.frame
gradient1.cornerRadius = 16
if #available(iOS 10.0, *) {
// set P3 colour
} else {
// set sRGB colour
}
gradient1.startPoint = CGPoint(x: 0, y: 0)
gradient1.endPoint = CGPoint(x: 1, y: 1)
playView.layer.insertSublayer(gradient1, at: 0)
On the 3rd line of the block of code above, I set the frame of the gradient equal to the frame I want it to fill.
When I run the app on different devices, the gradient layer will only fill the correct area if the device the app is being run on is the one selected in the Interface Builder.
I currently have the code in viewDidLoad(), and so the issue can be solved by moving the code to viewDidAppear(), but then when the app is loaded, there will be a slight delay before the gradient appears, not giving a smooth look and feel.
Is there another method I can put the code in, so that the gradient shows in the correct place, whilst at the same time being there as soon as the user sees the screen? Or alternatively, a way to make the gradient fill the view, whilst still keeping the code in viewDidLoad()?
EDIT: viewWillAppear() does not work, nor does viewWillLayoutSubviews(). Surely there must be away to solve this?

You can put inside this block:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
}

viewDidAppear() works. This screen is the root controller - I don't know if that makes a difference or not, but there is no visible delay on applying the gradient backgrounds.
I would be interested if anyone could explain this? I have a bar chart in another part of the app and in viewDidAppear() there is code to complete the bar chart, however there is a delay in it being filled in.

Change the layer.frame property inside the viewDidLayoutSubviews
method. This is to make sure that the subview (playView) has already a proper frame.

Related

UIProgressView do not redraw his frame when app launch back from background

i have view with `UIProgressView` and 3 dot-view. It's like a page control. Each page - the video. `progressView` displays progress of video playback
ok. I do not use constraints for left and right anchors, because my progressView should swap places with dot-view. For example, when current video is ended and next start play, we should swap positions of `progressView` with next dot-view. For swap i just change frames
and the problem is: when i move app to background and returns back, my `progressView` loses his old frame. It attaches to the left side, because `.frame.minX` is 0
and the last one: this problem occurs only after first returns from background
what i tried to do:
save progressView frames before app is going to background and restore it when app comes to foreground: progressView.frame = progressViewOldFrames and call setNeedsDisplay()
add constraint to leftAnchor with constant (frame.minX) before background and remove it after foreground
combine these 2 tries
so now it looks like
func appWillMoveInBackground() {
progressBarXConstraint = progressBar.leftAnchor.constraint(equalTo: self.leftAnchor, constant: progressBar.frame.minX)
progressBarXConstraint?.isActive = true
progressBarFrame = progressBar.frame
}
func updateProgressWidth() {
progressBarXConstraint?.isActive = false
// here i use constraints because my width and height also disables
// and only constraints helps me
progressBar.widthAnchor.constraint(equalToConstant: 32).isActive = true
progressBar.heightAnchor.constraint(equalToConstant: 6).isActive = true
progressBar.frame = progressBarFrame
progressBar.setNeedsDisplay()
}
UPDATE
ok, i should explain more. I guess i cant use constraints because we have some animation while we are scrolling. When we scroll to right - we should move our progressView to some points at right. And in this moment we should move right dot-view to the left. I mean, we do not scroll page by page, we can scroll to a half on the right, then we can return to the initial position.
this code of block did change frames of progressView and dot-view. Formulas are not important. Please, just understand the behavior of this view
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// calc some math variables
// and call method that make changes in frames
pageControl.moveBetweenPages(atPercentValue: distInPercents - 100)
}
// its part of body moveBetweenPages() func
progressBar.frame = CGRect(x: progStartX + progAddDist, y: 0,
width: SizeConstants.progressBarWidth.value,
height: SizeConstants.height.value)
let dotStartX: CGFloat = SizeConstants.progressBarWidth.value + SizeConstants.itemsSpacing.value + (CGFloat(currentPageNum) * dotSectionSize)
dots[currentPageNum].view.frame = CGRect(x: dotStartX - dotAddDist, y: 0,
width: SizeConstants.dotWidth.value,
height: SizeConstants.height.value)
images shows how it looks before background and after
matt from comments suggested me use constraints instead of frames and yes, it helps and it works
only thing i can say is dont forget call setNeedsLayout() after constraints update

Slider - incorrect color at the beginning and in the end

I have a custom slider where i have to increase slider's height (thickness). The code looks like this:
class CustomSlider: UISlider
{
override open func trackRect(forBounds bounds: CGRect) -> CGRect {
var defaultBounds = super.trackRect(forBounds: bounds)
let newHeight: CGFloat = 20
return CGRect(x: defaultBounds.origin.x,
y: defaultBounds.origin.y + defaultBounds.size.height/2 - newHeight/2,
width: defaultBounds.size.width,
height: newHeight)
}
}
The height is increased, but the problem now is that slider is not colored properly at the beginning and in the end. For example, at the beginning it now looks like this:
wrong color at the beginning
After some point the color becomes correct and fills blue: correct color after some point
In the end there is the same problem, at first everything works as expected: normal behavior
But then after some point the color becomes updated to blue too soon: wrong color in the end
Has anyone experienced anything similar before? Is there any solution for this?
I have tried using setMinimumTrackImage and setMaximumTrackImage instead of minimumTrackTintColor and maximumTrackTintColor, it works, but i cannot use it because when i rotate screen - slider stretches and the image which i am using stretches as well, so slider's corner radius looks stretched and not the same as it has to be.
Also an interesting fact is that the more I increase slider height - the later the correct color appears at the beginning.
Since setting track images is working and the only issue is stretched corners, the latter can be solved by using resizableImage(withCapInsets:) on your track images, so only the middle part of the image will be stretched and the rest will remain untouched.
These articles cover the topic in great details:
https://www.natashatherobot.com/ios-stretchable-button-uiedgeinsetsmake/
https://www.hackingwithswift.com/example-code/media/how-to-make-resizable-images-using-resizableimagewithcapinsets

Swift button frame height issue (viewDidLayoutSubviews)

I've got some square buttons that I'd like to add rounded corners to that are proportional to the button's height. In past versions of my app, I had implemented this feature without issues using viewDidLayoutSubviews(). For some reason, after pushing a new version of my app with other features I had tweaked, this section of code no longer functions as expected. Here is the code:
override func viewDidLayoutSubviews() {
for button in buttons {
button!.layer.shadowColor = UIColor.black.cgColor
button!.layer.shadowOffset = CGSize(width: 0, height: 1.0)
button!.layer.shadowOpacity = 0.4
button!.layer.shadowRadius = button!.frame.height / 40
button!.layer.cornerRadius = button!.frame.height / 10
}
Again, this block of code used to work just fine but for some reason it no longer works. What I am experiencing is much larger relative radii on smaller buttons (iPhone SE) compared to bigger buttons (iPads).
To troubleshoot, in viewDidLayoutSubviews(), I'm printing the button!.frame.height and I'm noticing that no matter what device I use the frame height is 395.5, which I believe is the correct size only on the 12.9" iPad. Therefore, the buttons look correct on the 12.9" iPad but the radii end up being too large on all of the smaller devices.
Any idea what's going on here? Why is it that they're all returning the same frame height even though they're visually very different sizes on the different devices?
I copy and pasted the above code into the viewWillAppear() method and
the problem was resolved. I then deleted the code from
viewWillAppear(), leaving me with my original code during posting of
question, and it is continuing to run as expected (working). What
could possibly be the cause of this intermittent behavior
The reason when you initialized the buttons in viewWillAppear and remove them but it still work because your button's frame did not change in the viewDidLayoutSubview method. And the viewDidLayoutSubview is invoked only controller's view is updated, rotated, or changed, which in your case it does not.
If you try to rotate your device you will see your parent view's frame changed.
For more information about view hierarchy. See this article
Try like this:-
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for button in buttons {
button!.layer.shadowColor = UIColor.black.cgColor
button!.layer.shadowOffset = CGSize(width: 0, height: 1.0)
button!.layer.shadowOpacity = 0.4
button!.layer.shadowRadius = button!.frame.height / 40
button!.layer.cornerRadius = button!.frame.height / 10
}

Swift circular corners doesn't work properly on different screen sizes

I'm trying to achieve smooth round corners on imageviews. Although it's working well on non-plus devices, i can not reach my goal at plus screen devices. Incidentally, i recognize, it's not even work well on the devices that font size enlarged. I applied following code that can be found every topic.
mergedImage.image = lastImage
mergedImage.layer.masksToBounds = false
mergedImage.layer.cornerRadius = mergedImage.frame.size.height / 2
mergedImage.clipsToBounds = true
And it's results like pictures below.
It looks like the black shape changes size. This can happen when using AutoLayout for example. If that is the case, you need to calculate the corner radius each time the frame changes.
I think the best way to do this is by subclassing UIView to create a "black shape view", and then overriding its layoutSubviews method:
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height / 2
}
If you don't have a subclass, you can for example do this in UIViewController.viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
mergedImage.layer.cornerRadius = bounds.height / 2
}

Swift: Rounded corners appear different upon first appearance and reappearing

class ApplyCorners: UIButton {
override func didMoveToWindow() {
self.layer.cornerRadius = self.frame.height / 2
}
}
I apply this class to the buttons in my application and it is working great, but when I apply it to a button used in every cell in a table view the button corners are not round upon entering the view, but if I click one of the buttons I get segued to another view. If I then segue back the corners are "fixed" / round.
The green is the button when returning and the red is upon first entering the view.
Anyone know how to fix this?
I'd suggest layoutSubviews, which captures whenever the frame of the button changes:
class ApplyCorners: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.height / 2
}
}
This takes care of both the original appearance and any subsequent appearance. It also avoids all sorts of problems related to not only whether the frame was known when the view appeared, but also if you do anything that might change the size of the button (e.g. anything related to constraints, rotation events, etc.).
This sort of thing is likely to be a timing problem. Consider the phrase self.frame.height. At the time didMoveToWindow is called, we may not yet know our frame. If you are going to call a method that depends upon layout, do so when layout has actually occurred.
Gonna propose another alternative: listen to any bounds changes. This avoids the problem of wondering "is my frame set yet when this is called?"
class ApplyCorners: UIButton {
override var bounds: CGRect {
didSet {
layer.cornerRadius = bounds.height / 2
}
}
}
Edited frame to bounds because as #Rob points out, listening for frame changes will cause you to miss the initial load sometimes.
Putting your code in didMoveToWindow() does not make sense to me. I'd suggest implementing layoutSubviews() instead. That method gets called any time a view object's layout changes, so it should update if you resize your view.
(Changed my suggestion based on comments from TNguyen and and Rob.)

Resources