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.
Related
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
}
}
When presenting a view controller using a custom animation, none of Apple's documentation or example code mentions or includes constraints, beyond the following:
// Always add the "to" view to the container.
// And it doesn't hurt to set its start frame.
[containerView addSubview:toView];
toView.frame = toViewStartFrame;
The problem is that the double-height status bar is not recognized by custom-presented view controllers (view controllers that use non-custom presentations don't have this problem). The presented view controller is owned by the transition's container view, which is a temporary view provided by UIKit that we have next to no dominion over. If we anchor the presented view to that transient container, it only works on certain OS versions; not to mention, Apple has never suggested doing this.
UPDATE 1: There is no way to consistently handle a double-height status bar with custom modal presentations. I think Apple botched it here and I suspect they will eventually phase it out.
UPDATE 2: The double-height status bar has been phased out and no longer exists on non-edge-to-edge devices.
My answer is: You should not use constraints in case of custom modal presentations
Therefore I know your pain, so I will try to help you to save time and effort by providing some hints which I suddenly revealed.
Example case:
Card UI animation like follows:
Terms for further use:
Parent - UIViewController with "Detail" bar button item
Child - UIViewController with "Another"
Troubles you mentioned began, when my animation involved size change along with the movement. It causes different kinds of effects including:
Parent's under-status-bar area appeared and disappeared
Parent's subviews were animated poorly - jumps, duplication and other glitches.
After few days of debugging and searching I came up with the following solution (sorry for some magic numbers ;)):
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0.4,
options: .curveEaseIn, animations: {
toVC.view.transform = CGAffineTransform(translationX: 0, y: self.finalFrame.minY)
toVC.view.frame = self.finalFrame
toVC.view.layer.cornerRadius = self.cornerRadius
fromVC.view.layer.cornerRadius = self.cornerRadius
var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, scale, scale, 1.0)
transform = CATransform3DTranslate(transform, 0, wdiff, 0)
fromVC.view.layer.transform = transform
fromVC.view.alpha = 0.6
}) { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
Main point here is, that You have to use CGAffineTransform3D to avoid animation problems and problems with subviews animation (2D Transforms are not working for unknown reasons).
This approach fixes, I hope, all your problems without using constraints.
Feel free to ask questions.
UPD: According to In-Call status bar
After hours of all possible experiments and examining similar projects like this and this and stackoverflow questions like this, this (it's actually fun, OPs answer is there) and similar I am totally confused. Seems like my solution handles Double status bar on UIKit level (it adjusts properly), but the same movement is ignoring previous transformations. The reason is unknown.
Code samples:
You can see the working solution here on Github
P.S. I'm not sure if it's ok to post a GitHub link in the answer. I'd appreciate for an advice how to post 100-300 lines code In the answer.
I've been struggling with double-height statusBar in my current project and I was able to solve almost every issue (the last remaining one is a very strange transformation issue when the presentingViewController is embedded inside a UITabBarController).
When the height of the status bar changes, a notification is posted.
Your UIPresentationController subclass should subscribe to that particular notification and adjust the frame of the containerView and its subviews:
UIApplication.willChangeStatusBarFrameNotification
Here is an example of code I'm using:
final class MyCustomPresentationController: UIPresentationController {
// MARK: - StatusBar
private func subscribeToStatusBarNotifications() {
let notificationName = UIApplication.willChangeStatusBarFrameNotification
NotificationCenter.default.addObserver(self, selector: #selector(statusBarWillChangeFrame(notification:)), name: notificationName, object: nil)
}
#objc private func statusBarWillChangeFrame(notification: Notification?) {
if let newFrame = notification?.userInfo?[UIApplication.statusBarFrameUserInfoKey] as? CGRect {
statusBarWillChangeFrame(to: newFrame)
} else {
statusBarWillChangeFrame(to: .zero)
}
}
func statusBarWillChangeFrame(to newFrame: CGRect) {
layoutContainerView(animated: true)
}
// MARK: - Object Lifecycle
deinit {
// Unsubscribe from all notifications
NotificationCenter.default.removeObserver(self)
}
// MARK: - Layout
/// Called when the status-bar is about to change its frame.
/// Relayout the containerView and its subviews
private func layoutContainerView(animated: Bool) {
guard let containerView = self.containerView else { return }
// Retrieve informations about status-bar
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let normalStatusBarHeight = Constants.Number.statusBarNormalHeight // 20
let isStatusBarNormal = statusBarHeight ==~ normalStatusBarHeight
if animated {
containerView.frame = …
updatePresentedViewFrame(animated: true)
} else {
// Update containerView frame
containerView.frame = …
updatePresentedViewFrame(animated: false)
}
}
func updatePresentedViewFrame(animated: Bool) {
self.presentedView?.frame = …
}
}
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.
I have a web view that display a table much larger than the screen. When I try to scroll it diagonally AFTER zooming - both in or out, it usually only scrolls in a single direction instead of diagonally. Is this behaviour due to web view's scrollview or could I have made mistake in my codes?
This is how I populate my web view:
webViewContent.scrollView.bounces = false
webViewContent.scrollView.bouncesZoom = false
webViewContent.scrollView.delegate = self
webViewContent.scalesPageToFit = true
var htmlString = "<html><head>... ... a really long string that creates a table"
webViewContent.loadHTMLString(htmlString, baseURL: nil)
Please do tell me if the htmlString might affect, I did not include it because it is really long.
I also tried to synchronise the view with a header row called webViewTitle which I populate using similar codes but only one row. The synchronise codes are like:
func scrollViewDidZoom(_ scrollView: UIScrollView) {
if(scrollView != webViewTitle.scrollView){
var zoomPadding:CGFloat = 0.0
if((currentZoomScale*webViewContent.scrollView.zoomScale) < 1){
zoomPadding = 0.5*(-acos(currentZoomScale*webViewContent.scrollView.zoomScale)*180.0/CGFloat.pi)
}else{
zoomPadding = 0.5*acos(2-(currentZoomScale*webViewContent.scrollView.zoomScale))*180.0/CGFloat.pi
}
webViewTitle.scrollView.zoom(to: CGRect(x: webViewContent.scrollView.contentOffset.x,
y: (355*currentZoomScale*webViewContent.scrollView.zoomScale) + zoomPadding,
width: webViewTitle.scrollView.bounds.width/currentZoomScale/webViewContent.scrollView.zoomScale,
height: webViewTitle.scrollView.bounds.height/currentZoomScale/webViewContent.scrollView.zoomScale),
animated: false)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var zoomPadding:CGFloat = 0.0
if((currentZoomScale*webViewContent.scrollView.zoomScale) < 1){
zoomPadding = 0.5*(-acos(currentZoomScale*webViewContent.scrollView.zoomScale)*180.0/CGFloat.pi)
}else{
zoomPadding = 0.5*acos(2-(currentZoomScale*webViewContent.scrollView.zoomScale))*180.0/CGFloat.pi
}
if(scrollView == webViewTitle.scrollView){
webViewTitle.scrollView.contentOffset.y = (355*currentZoomScale*webViewContent.scrollView.zoomScale) + zoomPadding
webViewContent.scrollView.contentOffset.x = webViewTitle.scrollView.contentOffset.x
}else{
webViewTitle.scrollView.contentOffset.y = (355*currentZoomScale*webViewContent.scrollView.zoomScale) + zoomPadding
webViewTitle.scrollView.contentOffset.x = webViewContent.scrollView.contentOffset.x
}
}
Could any of these caused the diagonal scrolling to become buggy?
Could any of these caused the diagonal scrolling to become buggy?
Yes
From the provided code, you are implementing the UIScrollViewDelegate for the UIWebView, but in both shown implementations, you are not calling super. This means that the scrollView is not going to have it's standard behavior, and instead strictly use your code for it's scrollViewDidScroll and scrollViewDidZoom behavior.
While this question doesn't necessarily provide a lot of clarity on exactly what is going on, I believe it to be the same issue you're facing.
In summary: At a minimum, call super for each delegate implementation of the UIScrollViewDelegate, or if you don't need any custom scroll behavior then remove the delegate altogether.
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.