i'm trying to add a sublayer behind the imageView however the issue is that since it is using constraints it can't seem to figure out the position and just places sublayer in left corner? i've tried to add the LFTPulseAnimation to viewDidLayoutSubViews but then everytime i reopen the app it will add one on top.
viewDidLoad
//GroupProfile ImageView
imageGroupProfile = UIImageView(frame: CGRect.zero)
imageGroupProfile.backgroundColor = UIColor.white
imageGroupProfile.clipsToBounds = true
imageGroupProfile.layer.cornerRadius = 50
self.view.addSubview(imageGroupProfile)
imageGroupProfile.snp.makeConstraints { (make) -> Void in
make.height.equalTo(100)
make.width.equalTo(100)
make.centerX.equalTo(self.view.snp.centerX)
make.centerY.equalTo(self.view.snp.centerY).offset(-40)
}
let pulseEffect = LFTPulseAnimation(repeatCount: Float.infinity, radius:160, position:imageGroupProfile.center)
self.view.layer.insertSublayer(pulseEffect, below: imageGroupProfile.layer)
i've tried to add the LFTPulseAnimation to viewDidLayoutSubViews but then everytime i reopen the app it will add one on top.
Nevertheless that is the way to do it. Just add a Bool property so that your implementation of viewDidLayoutSubViews inserts the layer only once:
var didLayout = false
override func viewDidLayoutSubviews() {
if !didLayout {
didLayout = true
// lay out that layer here
}
}
The reason is that you don't have the needed dimensions until after viewDidLayoutSubviews tells you that (wait for it) your view has been laid out! But, as you rightly say, it can be called many times subsequently, so you also add the condition so that your code runs just once, namely the first time viewDidLayoutSubviews is called.
Related
The bounds of a subview of a subview of a custom UIView seem to be 0 in layoutSubviews(), hence using the bounds in layoutSubviews() is a problem.
To show the problem I put a demo on GitHub: SOBoundsAreZero
Here's a direct link to the custom view's implementation: DemoView.swift
The structure of the custom view "DemoView" is like this:
DemoView
firstLevelSubview
secondLevelSubview
This structure is being created programmatically using Auto Layout.
When layoutSubviews() is called, the view and the firstLevelSubview have the expected bounds, but the secondLevelSubview's bounds are 0.
I expected all subviews using Auto Layout to have the correct bounds, at least in the last call to layoutSubviews.
The structure is an abstraction of a real case. To avoid the problem, secondLevelSubview could be added as a first level subview to DemoView. Though, this is something, that is not feasible in the real case.
I feel like I'm missing something simple here, even if it's expected behaviour.
I was able to fix this with a call to secondLevelSubview.layoutIfNeeded() in layoutSubviews().
override func layoutSubviews() {
super.layoutSubviews()
secondLevelSubview.layoutIfNeeded()
firstLevelSubview.layer.cornerRadius = bounds.width / 2
secondLevelSubview.layer.cornerRadius = secondLevelSubview.bounds.width / 2
print("bounds.width: \(bounds.width), contentView.bounds.width: \(firstLevelSubview.bounds.width), backgroundView.bounds.width: \(secondLevelSubview.bounds.width)")
}
The description of layoutIfNeeded() is:
Use this method to force the view to update its layout immediately.
When using Auto Layout, the layout engine updates the position of
views as needed to satisfy changes in constraints. Using the view that
receives the message as the root view, this method lays out the view
subtree starting at the root. If no layout updates are pending, this
method exits without modifying the layout or calling any
layout-related callbacks.
So, essentially you have an ordering issue here. The subview is scheduled for layout, and Auto Layout will get to it, but it hasn't done it yet. By calling layoutIfNeeded(), you tell Auto Layout to perform the pending layout immediately so that you can get the updated frame information.
Note: You can also just call self.layoutIfNeeded() and that will layout DemoView and all of its subviews. This would be useful if you had many such subviews and didn't want to have to call layoutIfNeeded() on each of them.
First, Your view is being construed without problem cause you have an initializer based on ‘CGRect’ , so it gets its dimension!
Second,
Your first subview get initialized after its parents view has got its dimensions. First subview dimensions depends on the parent view dimensions which are ready. So no problem here as you mentioned.
But meanwhile your first subview is trying to get its dimensions, your second subview also starts to get its dimensions based on first subview which is not ready yet , so it got nothing.
I suggest make different functions for each subview.
Make your object from the view class with its frame initializer in Your view controller. In ‘viewDidLoad’ call first subview function and in ’viewDidLoadSubView’ call second subview function.
Hope it helps.
You can get the desired result, if you try to access UIView.bounds under main queue.
override func layoutSubviews() {
super.layoutSubviews()
DispatchQueue.main.async {
self.firstLevelSubview.layer.cornerRadius = self.bounds.width / 2
self.secondLevelSubview.layer.cornerRadius = self.secondLevelSubview.bounds.width / 2
print("bounds.width: \(self.bounds.width), contentView.bounds.width: \(self.firstLevelSubview.bounds.width), backgroundView.bounds.width: \(self.secondLevelSubview.bounds.width)")
}
}
//print:
//bounds.width: 64.0, contentView.bounds.width: 64.0, backgroundView.bounds.width: 54.0
But why? So here I dig down into your problem. Here is the calling view hierarchy of your DemoView layoutSubviews method
DemoView
|
|___layoutSubviews
|
|___firstLevelSubview
|
|___layoutSubviews //Here you are trying to access bounds of second view which is still need to be resize or achieve it's bound
|
|___ secondLevelSubview
|
|___layoutSubviews
And so in this case, main queue will help you to get the actual bounds of any UIView.subView.
Another case I did it, I try to add secondLevelSubview to the DemoView itself with the firstLevelSubview constraints like below:
fileprivate func setupViews() {
backgroundColor = UIColor.clear
firstLevelSubview.translatesAutoresizingMaskIntoConstraints = false
firstLevelSubview.layer.masksToBounds = true
firstLevelSubview.backgroundColor = UIColor.green
addSubview(firstLevelSubview)
firstLevelSubview.topAnchor.constraint(equalTo: topAnchor).isActive = true
firstLevelSubview.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
firstLevelSubview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
firstLevelSubview.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
secondLevelSubview.translatesAutoresizingMaskIntoConstraints = false
secondLevelSubview.layer.masksToBounds = true
secondLevelSubview.backgroundColor = UIColor.magenta
// firstLevelSubview.addSubview(secondLevelSubview)
addSubview(secondLevelSubview) //Here
secondLevelSubview.widthAnchor.constraint(equalTo: firstLevelSubview.widthAnchor, multiplier: 0.84).isActive = true
secondLevelSubview.heightAnchor.constraint(equalTo: firstLevelSubview.heightAnchor, multiplier: 0.84).isActive = true
secondLevelSubview.centerXAnchor.constraint(equalTo: firstLevelSubview.centerXAnchor).isActive = true
secondLevelSubview.centerYAnchor.constraint(equalTo: firstLevelSubview.centerYAnchor).isActive = true
}
And the hierarchy it follows like below:
DemoView
|
|___layoutSubviews
|
|___firstLevelSubview
|
|___layoutSubviews
|
|___secondLevelSubview
|
|___layoutSubviews
So, in the above case if you try to print the UIView.bounds without any main queue you will get the desired result.
Let me know, this helps you to understand the hierarchy.
I'm having an issue with some container views.
The main view has 2-container views. One of them is just a container view, straight up from the drop-off menu with nothing more than it's child VC being modified via Storyboard. (the embedded one).
The second Container View has a Scroll-view inside and a second view inside the container view with a custom size. (I saw a tutorial on youtube).
Now on my viewdidload on the HomeVC I call the following code:
func setupViews() {
containerTop.layer.cornerRadius = 15
containerTop.clipToBounds = true
containerBot.layer.cornerRadius = 15
containerBot.clipToBounds = true
}
The above code results in THIS RESULTS. As you can see all but 2 corners are rounded (the bottom 2 corners of the top view). Why is this happening and how can I fix this?
I'm not 100% sure about why you are having this odd behaviour, I also experienced something similar and fixed like following: (Your project should be supporting iOS11++ in order for this solution to work:
func setupViews() {
containerTop.layer.cornerRadius = 15
containerTop.clipToBounds = true
containerBot.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
containerBot.layer.cornerRadius = 15
containerBot.clipToBounds = true
}
Thank you for the suggestion. Unfortunately it didnt work.
I was able to fix it by doing the following:
On the storyboard tree On the ViewController there is a View on the element tree. I linked that as an outlet in my code and made it's corners round. Basically I called the round corners twice:
- Home View controller had:
func setupViews() {
containerTop.layer.cornerRadius = 15
containerTop.clipToBounds = true
containerBot.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
containerBot.layer.cornerRadius = 15
containerBot.clipToBounds = true
}
And then the ContainerTopViewController had inside a View (per storyboard) and the code is:
class ContainerTopViewController: UIViewController {
<LINKED THROUGH STORYBOARD> myView: UIViewController
func loadView() {
myView.layer.cornerRadius = 15
myView.layer.clipToBounds = true
}
}
I have a strange problem. I have a custom UIView that is supposed to fill the screen. Here is a picture of the GUI along with the constraints:
Now the main problem is, on the iPad Pro 12.9" simulator, at first the custom view only fills a portion of the screen- like it was following the Air 2 size constraints. However, if I go away from the screen and come back to it such that the screen isn't recreated but just redisplayed, the gui looks almost perfect. On the other hand, the gui looks almost perfect on the iPad Mini device that I have, without having to go and come back. It isn't quite there because the image in the middle section gets clipped slightly at the top and bottom, but I haven't tried hard to figure out why that is happening. I have spent a fair amount of time trying to debug the problem I am asking about. If you need more information to help me solve this problem, I'm happy to provide it- just specify what you need. On the view controllers that actually hold this custom view, I use autoresizing masks to have it fill the screen, which apparently isn't working, but constraints have been tried and they didn't help either.
Any ideas on how to fix this?
UPDATE: I changed the constraints to something I liked better, as I had used "Reset to Suggested Constraints" and that created some weird constraints. Problem still exists, however.
Here is some of the code involving the view:
class SessionDisplayViewController: SessionViewDisplayViewControllerBase
{
//some code omitted for succinctness
#IBOutlet weak var mySessionView: SessionDisplayView!
override func getSessionView() -> SessionDisplayView
{
return mySessionView
}
...
}
class SessionViewDisplayViewControllerBase: UIViewController, SessionDisplayViewDelegate{
...
override func viewDidLoad() {
super.viewDidLoad()
...
if !ShareData.sharedInstance.sessionDataObjectContainer.keys.contains(curSessName) || ShareData.sharedInstance.sessionDataObjectContainer[curSessName] == nil
{
setupMySession(isLive: false, isFinalized: false)
}
else if (ShareData.sharedInstance.sessionDataObjectContainer[curSessName]?.isFinalized)!
{
setupMySession(isLive: false, isFinalized: true)
}
else
{
setupMySession(isLive: true, isFinalized: false)
var fromTempChoose = ShareData.sharedInstance.startingSessionFromTempChoose && !(ShareData.sharedInstance.globalsVar?.hasStartedSession)!
if fromTempChoose || (ShareData.sharedInstance.resumingSessionFromSessDet && !(ShareData.sharedInstance.globalsVar?.hasResumedSession)!)
{
let mySessionView = getSessionView()
mySessionView.curScene.pauseSession(isStartingNow: true)
blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
//}
blurEffectView = UIVisualEffectView(effect: blurEffect)
//always fill the view
blurEffectView?.frame = self.view.bounds
blurEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(blurEffectView!)
}
...
}
var mySessObj = getSessionView() //these three lines of code were added to try to fix the problem. They weren't in the original code
mySessObj.frame = self.view.bounds
setNeedsDisplay()
}
...
func setupMySession(isLive: Bool, isFinalized: Bool)
{
let mySessionView = getSessionView()
//mySessionView.translatesAutoresizingMaskIntoConstraints = false
mySessionView.delegate = self
sessionNameIndex = self.getSessionNumber() - 1
let myName = ShareData.sharedInstance.currentAccount.name
var curSessName = generateCurrentAccountName(name: myName!, value: self.getSessionNumber())
//var names = generateAllPossibleSessionNames(name: myName!)
let curSession = ShareData.sharedInstance.sessionDataObjectContainer[curSessName]
mySessionView.onView(index: getSessionNumber(), sessionName: curSessName, isLive: isLive, isFinalized: isFinalized)
if isLive
{
let val = curSession?.currentValue()
mySessionView.curScene.setStartPosition(newValue: val!)
}
}
It took me awhile to figure this one out. The software was adding an auto resizing mask constraint to the view, which was causing it to look funny. In order to fix this problem, in the initialization of the custom UIView, I had to add the following lines of code:
self.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
self.view.frame = self.bounds
I guess the software treats the class that is the custom UIView and the view inside it that holds everything as two separate views. I wish the tutorials I found online about creating a custom UIView had included that we need to set the auto resizing mask and the view's frame, but oh well. Lesson learned.
Evening, I have a calendar collection.
The cells have some rounded views shown incorrectly at the first time, but when they are reloaded they are shown correctly.
I know that the issue is that at the first time the cell doesn't know the right size of the frame.
What I've tried:
1- call the round function inside layoutSubviews(): only the right side is rounded correctly
2 - call the round function inside the cellWillLayout: nothing changes
This is the rounding function:
func makeRound() {
print("rounding")
currentDayView.layer.cornerRadius = currentDayView.frame.height/2
currentDayView.layer.masksToBounds = true
currentDayView.clipsToBounds = true
selectedDayView.layer.cornerRadius = selectedDayView.frame.height/2
selectedDayView.layer.masksToBounds = true
selectedDayView.clipsToBounds = true
}
Any suggestion?
The best place to do corner rounding is either in each view's layoutSubviews or (for example, if you haven't sub-classed them) put it in your view controllers viewDidLayoutSubviews.
Each view in your case is the layoutSubviews of currentDayView and selectedDayView.
You need to override layoutSubviews method, then call your method inside it:
override func layoutSubviews() {
super.layoutSubviews()
self.makeRound()
}
I've created a subclass of UIControl called 'TestButton' with a label and an imageview subview. That object is created with a frame, and as part of the init process I create the subview elements.
These 'TestButtons' are created programmatically, I never use them in the StoryBoard.
Code snippet:
class TestButton: UIControl {
var iconImageView: UIImageView?
var labelView: UILabel?
required init(size: CGSize, icon: UIImage? text: String?) {
super.init(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height) )
if ( (icon != nil) && (text != nil) ) {
self.iconImageView = UIImageView()
self.iconImageView?.translatesAutoresizingMaskIntoConstraints = false
self.iconImageView?.contentMode = .center
self.iconImageView?.image = icon
self.iconImageView?.backgroundColor = UIColor.yellow // test: show bounds
self.labelView = UILabel()
self.labelView?.translatesAutoresizingMaskIntoConstraints = false
self.labelView?.text = "Test"
self.labelView?.backgroundColor = UIColor.blue
self.addSubview(self.iconImageView!)
//self.addSubview(self.labelView!)
// Setup constraints on created subview(s)
self.iconImageView?.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
self.iconImageView?.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
self.iconImageView?.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
self.iconImageView?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
}
override func layoutSubviews() {
super.layoutSubviews()
print("iconframe: \(self.iconImageView!.frame)")
}
In the sample above, I've removed the label from the mix. I'm only trying to get the imageView constraints to work and effectively size the imageView to the view. This does not work, the image appears full size and the constraints appear to have been ignored. I've tried moving the constraints code into updateConstraints and calling that - all appears to work but again the constraints are not applied.
layoutSubviews does get called when you would expect it to be but the imageView frame is unmodified. There are no messages in the output window, it just silently doesn't work.
My question is; have I somehow disabled autoLayout by specifying the parent's frame? I would have expected autoLayout to still work within the bounds of the parent's frame?
Sorry if this has been answered once or many times before. I'm not actually sure what I'm searching for or the correct question to ask, only posted after a day of trawling SO. Thanks
The behaviour of TestButton view will depend on how it is constrained within its superview.
In NSLayoutConstraints both participating attributes (or anchors) are equal "partners": with just those four constraints you have, imageView will take full frame of it's parent (TestButton), but at the same time TestButton will be expanded to be big enough for a full-size image.
You can apply other constraints to TestButton view to prevent the latter.
To understand why standard views behave like that, look at intrinsicContentSize property (docs). It is implemented by standard controls, and tells the auto layout system how big the view should be, purely based on it's content (UIButton or UISwitch are auto-sized like that, for example). UIImageView's intrinsicContentSize is the size of its image, that's why it expands full-size if nothing is preventing it.