CenterXAnchor with positive offset does not work? - ios

I created a view within my ViewController and created an Outlet. The view is called "chatView".
I would like to hide the view directly after the main view has loaded (and swipe it in after some time, when the user clicks a button).
My approach was to manipulate the centerXAnchor-constraint:
override func viewDidLoad() {
super.viewDidLoad()
view.insertSubview(chatView, belowSubview: view)
chatView.translatesAutoresizingMaskIntoConstraints = false
chatCenterX = chatView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 1500)
chatCenterX.isActive = true
}
But for any reason (I have no idea why), the chatView is already displayed when loading the view, so the offset is not set.
I tried some other things and detected that the offset works if I set it to a negative value (I tried -1500 instead of 1500).
Do you have an idea what I did wrong?

Drag the constraint that you added in storyboard as an IBOutlet and change it's constant value to move the view forward or backward as you like
note : change it in viewDidLayoutSubviews i first launch with a boolean so not to be hidden when you want to show it , not in viewDidLoad
Apple says:
viewDidLayoutSubviews()
When the bounds change for a view controller's view, the view
adjusts the positions of its subviews and then the system calls this
method. However, this method being called does not indicate that the
individual layouts of the view's subviews have been adjusted. Each
subview is responsible for adjusting its own layout.
Your view controller can override this method to make changes after
the view lays out its subviews. The default implementation of this
method does nothing.
So viewDidLayoutSubviews is called every time, that's why we add boolean variable to the function.
override func viewDidLayoutSubviews
{
if(once)
{
once = false
self.chatViewCenterX.constant = 1500
self.view.layoutIfNeeded()
}
}
to show again anywhere
self.chatViewCenterX.constant = 0
self.view.layoutIfNeeded() // viewDidLayoutSubviews is called here.

Related

Adding An UITableViewController on to a SubView

An UITableViewController pretty much takes up the entire view. I need a way to limit its height, width and add some shadows etc. For a clear explanation, I won't show the UITableViewController's contents.
Without the use of a storyboard, I subviewed the UITableViewController:
// In another UIViewController
let otherController = OtherController() // A subclass of UITableViewController
let otherControllerView = otherController.view
someView.addSubView(otherControllerView)
[...] // bunch of constraints
Notes:
In AppDelegate, if I set the rootController as OtherController(), everything works as it should. If I change it back to SomeView(), I see my modified tableView. If I should click it, it disappears.
This was the only thing that came close to my issue but sadly, I could not understand the answers provided as nothing made any sense to me.
I need to understand, why it disappears when touched etc.
view.bringSubviewToFront(...) proved futile. I'm gessing that a tableView should be rendered in its own controller and not in another view?
So just to answer this question, indeed you got two options. One is the best way, as suggested by Rakesha. Just use UITableView. Add it as a subview. Done.
And in the future, if you really want any controller to be added onto any UIView, remember you need to add that controller as a child. For example, in your case:
The controller of the view that will hold your UITableViewController will add such UITableViewController as a child.
self.addChild(yourUITableViewController)
self.whatEverViewContainer.addSubview(yourUITableViewController.view)
// Take note of the view of your tableViewController above^.
// Then setup the constraints of your yourUITableViewController.view below.
I hope this helps!
You must add the instance of UITableViewController's subclass as child view controller of the other view controller. You need to ensure few points in order to make it work. The points are as listed below:
Create the instance of your TableViewController
Add it as a child view controller of the other view controller
Add its view as a subview of the desired view (you may do these steps in viewDidLaod since they need to be done only once)
Keeping in mind the view cycle of a view controller. You must keep a weak reference of the child view controller aka TableViewController to adjust its view frame after the parent view controller has laid its subviews.
Code here:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let vc = TableViewController()
addChildViewController(vc)
view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
childVC = vc
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
childVC?.view.frame = view.frame
}

topLayoutGuide applied after viewWillAppear

I have the issue that the topLayoutGuide.length in a UIViewController (from XIB) gets set after viewWillAppear and i don't know how to hook into the change of topLayoutGuide.length to initially set the contentOffset of a table view.
Code to modally present a UIViewController inside a UINavigationController:
let viewController = UIViewController(nibName: "ViewController", bundle: nil)
let navigationController = UINavigationController(rootViewController: viewController)
present(navigationController, animated: true, completion: nil)
My debugging output about the topLayoutGuide.length
Init view controller
-[UIViewController topLayoutGuide]: guide not available before the view controller's view is loaded
willMove toParentViewController - top layout guide nan
Init navigation controller and pass view controller as root vc
Present navigation controller modally
viewDidLoad - top layout guide 0.0
viewWillAppear - top layout guide 0.0
viewWillLayoutSubviews - top layout guide 64.0
viewDidLayoutSubviews - top layout guide 64.0
viewWillLayoutSubviews - top layout guide 64.0
viewDidLayoutSubviews - top layout guide 64.0
viewDidAppear - top layout guide 64.0
didMove toParentViewController - top layout guide 64.0
viewWillLayoutSubviews - top layout guide 64.0
viewDidLayoutSubviews - top layout guide 64.0
For now i use a bool flag in the view controller to set the contentoffset in the viewDidLayoutSubviews only once, even though the method is called multiple times.
Any more elegant solution in mind?
The documentation for the topLayoutGuide states explicitly:
Query this property within your implementation of the viewDidLayoutSubviews() method.
Judging from your own inspections the earliest point to obtain the topLayoutGuide's actual length is inside the viewWillLayoutSubviews() method. However, I would not rely on that and do it in viewDidLayoutSubviews() as the docs suggest.
The reason why you cannot access the property earlier...
... is that the layout guides are objects that depend on the layout of any container view controllers. The views are laid out lazily when they are needed on screen. So when you add the viewController to the navigationViewController as its root view controller it's not laid out yet.
The layout happens when you present the navigationController. At that point the views of both view controllers are loaded (→ viewDidLoad(), viewWillAppear()) and then a layout pass is triggered. First, the navigationViewController's view is laid out (layout flow: superview → subview). The navigation bar's frame is set to a height of 64 px. Now the viewController's topLayoutGuide can be set. And finally the viewController's view is laid out (→ viewWillLayoutSubviews(), viewDidLayoutSubviews()).
Conclusion:
The only way to do some initial layout tweaks that depend on the layout guide's length is the method you suggested yourself:
Have a boolean property in your view controller that you set to true initially:
var isInitialLayoutPass: Bool = true
Inside viewDidLayoutSubviews() check for that property and only perform your initial layout when it's true:
func viewDidLayoutSubviews() {
if isInitialLayoutPass {
tableView.contentOffset = CGPoint(x: 0, y: topLayoutGuide.length)
}
}
Inside viewDidAppear(), set the property to false to indicate that the initial layout is done:
override func viewDidAppear() {
super.viewDidAppear()
isInitialLayoutPass = false
}
I know it still feels a little hacky but I'm afraid it's the only way to go (that I can think of) unless you want to use key-value-observing (KVO) which doesn't make it much neater in my opinion.

adding a uipageviewcontroller to a uistackview

I'm trying to add a sliding photo gallery functionality to the top portion of a view.
To give context, a user taps on a button or row or something. Then i load a scrollview with a uistackview in it. organized vertically, i had an image, and then another stack view with some text in it. Now, i want that image to become part of a larger "gallery". My research told me to implement UIPageviewcontroller and add the other images to a childVC.
i used this as a tutorial (the first example): http://www.raywenderlich.com/76436/use-uiscrollview-scroll-zoom-content-swift
the only relevant deviation from the tutorial my app has is that it creates things programmatically.
With my proof of concept for the gallery functionality, i wanted to integrate it with the previously mentioned stack view. my plan was to first add the pageviewcontroller stuff into the overall stack view with the original image view right below it and then simply remove the original image view to leave me the final product.
i was able to add the pageviewcontroller.view to the stackview, but the gallery doesn't show. taking a look at the UI Inspector, i can see that the gallery is kinda loaded, but it's messed up.
it's as if the uiview has a frame of 0 height and so the other stack view items don't respect the images that the pageviewcontroller is trying to show.
I think it could be that stack views can only handle specific views, not stuff as complicated as pageviewcontrollers.
also note: my implementation is all programmatic, no storyboards, and so for no xibs. so maybe i missed something here.
here is some code, if it helps:
note the constrain functions you see are from the "cartography" pod
this adds the "gallery" to the stack view, it's a delegate function from my view
func addZoomStuff(sender: UIStackView) {
let zoomer = PageBaseViewController()
addChildViewController(zoomer)
zoomer.view.translatesAutoresizingMaskIntoConstraints = false
zoomer.view.tag = 5
sender.addArrangedSubview(zoomer.view)
zoomer.didMoveToParentViewController(self)
}
this is what creates the scrollview, image view, etc for the gallery items:
override func viewDidLoad() {
super.viewDidLoad()
//MARK: - Zoom View Elements
// prep
scrollView = UIScrollView(frame: self.view.frame)
scrollView.delegate = self
self.view.addSubview(scrollView)
constrain(scrollView, view) { view, view2 in
view.edges == view2.edges
}
self.view.setNeedsUpdateConstraints()
// 1
let image = UIImage(named: imageName)!
imageView = UIImageView(image: image)
// 2
scrollView.addSubview(imageView)
constrain(imageView){ view in
view.edges == view.superview!.edges
}
scrollView.contentSize = image.size
i tried adding the constraints like this but there was no effect
func addZoomStuff(sender: UIStackView) {
let zoomer = PageBaseViewController()
addChildViewController(zoomer)
view.addSubview(zoomer.view)
constrain(zoomer.view, view) { view, view2 in
view.width == view2.width
view.height == view2.height * 2 / 3
view.leading == view2.leading
view.top == view2.top
}
zoomer.view.setNeedsUpdateConstraints()
zoomer.view.removeFromSuperview()
zoomer.view.translatesAutoresizingMaskIntoConstraints = false
zoomer.view.tag = 5
print("sender.subviews: \(sender.subviews)")
sender.addArrangedSubview(zoomer.view)
zoomer.didMoveToParentViewController(self)
print("sender.subviews: \(sender.subviews)")
}
if this method isn't going to work, can i do a nested horizontal stack view instead of the pageviewcontroller and somehow get that same scrolling/snap effect to see on image view at a time?
TLDR;
Create a subclass of UIPageViewController, make it it's own delegate.
Initialize the subclass with a plain UIViewController, only set a backgroundcolor.
In the pageviewcontroller subclass, implement the two delegate callbacks for a next and previous viewcontroller: create a plain viewcontrolller, with some random backgroundcolor.
If this works, replace the plain viewcontroller by your actual contentviewcontroller.
Long version:
Have you seen this: Maybe this link will help: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/PageViewControllers.html
It might help, as it explains the details of UIPageViewController. Basically, you need to create a viewController (not a view!), that shows one page of the gallery. So this VC has a stackview, and manages the content of it. The pageviewcontroller is initialized with your first contentviewController. If you create a subclass of the uipageviewcontroller, you can set self of that subclass as the delegate of it. Implement the delegate callbacks that return the next or previous viewController and thats it. For this last part, it is convenient to have a property on the contentviewcontroller from which the subclasses of the pageviewcontroller can figure out what data to set on the next or previousviewcontroller.
Your title seeks to hint at some confusion: its not possible to add a viewcontroller to a view. You can only add other views to a (stack)view. A viewcontroller owns and manages a viewhierarchy. A pageviewcontroller has no content, but manages the insertion and removal of viewcontrollers. as the pageviewcontroller is a containerviewcontroller, it will als take the contentViewcontrollers' views and place them in the viewhierarchy. But this is not something your code has to do when you subclass UIPageViewControlller and implement it's delegates on itself (and don't forget to assign self to be the delegate).

ViewWillAppear not executing code

I've got this block of code in my ViewWillAppear method which simply moves a set of labels down off the screen:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//Hide 1,3,5 RM labels
oneRepMax.center.y += view.bounds.height
threeRepMax.center.y += view.bounds.height
fiveRepMax.center.y += view.bounds.height
}
All 3 labels have been properly linked to my storyboard file. When I run the project, nothing happens.
If I copy the exact same code into a method linked to a button, it works as expected.
There isn't anything else in the project so i'm puzzled as to why this doesn't work.
I'm using Xcode 7 and following a tutorial where this is working in the ViewWillAppearMethod.
as follows:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
heading.center.x -= view.bounds.width
username.center.x -= view.bounds.width
password.center.x -= view.bounds.width
loginButton.center.y += 30
loginButton.alpha = 0
}
You never know the bounds untill the all the labels appear on the screen.
Try to do the same in viewDidAppear method.
From Documentation:
This method is called after the completion of any drawing and animations involved in the initial appearance of the view. You can override this method to perform tasks appropriate for that time, such as work that should not interfere with the presentation animation, or starting an animation that you want to begin after the view appears.
This chunk of code is from Ray Wenderlich book iOS Animation by Tutorials, right?
I had the same problem. I found the cause of this problem - the view from the tutorial don't use the Auto Layout and Size Classes, that's why a label don't respond on setting its coordinates. Try to change the color of the label in viewWillAppear method like labelText.textColor = UIColor.greenColor() and everything goes ok, but if you try to set coordinates like labelText.center.x -= view.bounds.width nothing happens. So click your view and go to File Inspector, scroll a bit and you'll see the checkmarks Use Auto Layout and Use Size Classes uncheck it and you'll get what you need. But you'll get pain in the ass either because this is a different and long story to code without Auto Layout. good luck then.
There are several methods called when you load a View controller. Their order is something like this (on UIViewController):
loadView
viewDidLoad
viewWillAppear:
viewWillLayoutSubviews
layoutSubviews (on UIViews inside the view controller)
viewDidLayoutSubviews (here the view controller knows everything is set)
viewDidAppear:
From viewDidLayoutSubviews you're completely sure your changes will take effect, because elements are set. Before it, you can't be sure (unless you cached the view controller and it is appearing for a second time).
Try to change your code to viewDidAppear.
You can find more information in this old post.

Size a UIViewController's view to parent UIWindow's bounds

I want to give a UIViewController's view a size that is different from the device's screen size. I know I can usually achieve this by adding the view controller as a child view controller of another parent UIViewController that has defined a frame for the child, but I am in a situation that seems a little different.
I have a UIWindow that only takes up a portion of the screen (it's got a frame that's basically (0, 0, DEVICE_WIDTH, HEIGHT_LESS_THAN_DEVICE_HEIGHT). This window shows up with the proper sizing and positioning when presented. I am setting a view controller as the rootViewController of the window, and then presenting the window by setting its hidden value to false. When this happens, the view controller's view ends up sized to fill the device's screen (i.e. a frame of (0, 0, DEVICE_WIDTH, DEVICE_HEIGHT)).
What I would like is for the view controller to inherit its sizing from the UIWindow it is set as the root view controller of. Is there a way to do this?
I have also tried overriding loadView() and returning a custom-sized view there. Logging the view shows that the view controller's view object is correctly sized during viewDidLoad, but is overwritten with the default size by viewWillAppear:. I would be open to using loadView() to size the view controller if inheriting sizing from the window isn't possible, but I don't know how to make the custom size stick.
Note: The reason why I am trying to add a view controller to the window is because I want to take advantage of the view controller lifecycle methods such as viewDidAppear:, which is why I am not just creating a simple UIView and adding it as a subview of the window.
As counter intuitive as it may seem, if you set set self.view.frame on viewWillAppear (IOS 8) or viewDidAppear (IOS 7) you will be able to make it work.
Swift code (IOS 8):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Banner style size, for example
self.view.frame = CGRectMake(0, 0, 320, 50)
}
For IOS 7, I had to use viewDidAppear, which is obviously an unsatisfactory solution. So I had to start the view with alpha = 0.0 and set alpha = 1.0 on viewDidAppear, after modifying self.view.frame.
Hope it helps.

Resources