Similar to what the Spotify or Apple Music app does when a song is playing, it places a custom view on top of the UITabBar:
Solutions I've tried:
UITabBarController in a ViewController with a max-sized Container View, and the custom view on top of the Container View49pt above the Bottom Layout Guide:
Problem: Any content in ViewControllers embedded in the UITabBarController constrained to the bottom don't show because they're hidden behind the custom layout. I've tried overriding size forChildContentContainer in UITabBarController, tried updating the bottom layout guide, Nothing. I need to resize the frame of container view of the UITabBarController.
Tried #1 again, but tried solving the problem of content hiding behind it by increasing the size of UITabBar, and then using ImageInset on every TabBarItem to bring it down, and adding my custom view on top of the UITabBar. Hasn't worked really well. There are going to be times when I want to hide my custom view.
UITabBarController as root, with each children being a ViewController with a Container View + my custom view:
But now I have multiple instances of my custom view floating around. If I want to change a label on it, have to change it to all views. Or hide, etc.
Override the UITabBar property of UITabBarController and return my custom UITabBar (inflated it with a xib) that has a UITabBar + my custom view. Problem: Probably the most frustrating attempt of all. If you override that property with an instance of class MyCustomTabBar : UITabBar {}, no tab shows up! And yes, I set the delegate of myCustomTabBar to self.
Leaning towards #3, but looking for a better solution.
This is actually very easy if you subclass UITabBarController and add your view programmatically. Using this technique automatically supports rotation and size changes of the tab bar, regardless of which version you are on.
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
//...do some of your custom setup work
// add a container view above the tabBar
let containerView = UIView()
containerView.backgroundColor = .red
view.addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
containerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// anchor your view right above the tabBar
containerView.bottomAnchor.constraint(equalTo: tabBar.topAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
}
I got it!
In essence, I increased the size of the original UITabBar to accomodate a custom view (and to shrink the frame of the viewcontrollers above), and then adds a duplicate UITabBar + custom view right on top of it.
Here's the meat of what I had to do. I uploaded a functioning example of it and can be found in this repo:
class TabBarViewController: UITabBarController {
var currentlyPlaying: CurrentlyPlayingView!
static let maxHeight = 100
static let minHeight = 49
static var tabbarHeight = maxHeight
override func viewDidLoad() {
super.viewDidLoad()
currentlyPlaying = CurrentlyPlayingView(copyFrom: tabBar)
currentlyPlaying.tabBar.delegate = self
view.addSubview(currentlyPlaying)
tabBar.isHidden = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
currentlyPlaying.tabBar.items = tabBar.items
currentlyPlaying.tabBar.selectedItem = tabBar.selectedItem
}
func hideCurrentlyPlaying() {
TabBarViewController.tabbarHeight = TabBarViewController.minHeight
UIView.animate(withDuration: 0.5, animations: {
self.currentlyPlaying.hideCustomView()
self.updateSelectedViewControllerLayout()
})
}
func updateSelectedViewControllerLayout() {
tabBar.sizeToFit()
tabBar.sizeToFit()
currentlyPlaying.sizeToFit()
view.setNeedsLayout()
view.layoutIfNeeded()
viewControllers?[self.selectedIndex].view.setNeedsLayout()
viewControllers?[self.selectedIndex].view.layoutIfNeeded()
}
}
extension UITabBar {
open override func sizeThatFits(_ size: CGSize) -> CGSize {
var sizeThatFits = super.sizeThatFits(size)
sizeThatFits.height = CGFloat(TabBarViewController.tabbarHeight)
return sizeThatFits
}
}
Since iOS 11 this became a little easier. When you add your view, you can do the following:
viewControllers?.forEach {
$0.additionalSafeAreaInsets = UIEdgeInsets(
top: 0,
left: 0,
bottom: yourView.height,
right: 0
)
}
Your idea to put it in a wrapper viewcontroller is good, but it will only cause overhead (more viewcontrollers to load in memory), and issues when you want to change the code later on. If you want the bar to always show on your UITabBarController, then you should add it there.
You should subclass UITabBarController and load the custom bar from a nib. There you will have access to the tabbar (so you can place your bar correctly above it), and you will only load it in once (which solves your problem that you will face having a different bar on each tab).
As for your views not reacting to the size of the custom bar, I don't know how you can do that, but my best suggestion is to use a public variable and notifications that you listen to in your individual tabs.
You can then use that to change the bottom constraint.
Besides playing with UITabBar or container vc, you could also consider adding the view in the App Delegate to the main window like in following post:
View floating above all ViewControllers
Since your view is all around along with the Tab bar, it is totally ok to make it in the App Delegate.
You can always access the Floating view from App Delegate Singleton by making it a property of the App Delegate. It is easy then to control its visibility in anywhere of your code.
Changing constant of the Constraints between the Floating view and super view window can adjust the position of the view, thus handsomely respond to orientation changes.
Another(similar) approach is to make the floating view another window like the uid button.
Unless I've misunderstood, you could create a custom view from your UITabBarController class. You can then insert it above and constrain it to the tabBar object, which is the tabBar associated with the controller.
So from your UITabBarController class, create your custom view
class CustomTabBarController: UITabBarController {
var customView: UIView = {
let bar = UIView()
bar.backgroundColor = .white
bar.translatesAutoresizingMaskIntoConstraints = false
return bar
}()
In viewDidLoad() add your custom view to the UITabBarController's view object and place it above the tabBar object
override func viewDidLoad() {
super.viewDidLoad()
...
self.view.insertSubview(customView, aboveSubview: tabBar)
Then after your custom view is added as a subView, add constraints so it's positioned correctly. This should also be done in viewDidLoad() but only after your view is inserted.
self.view.addConstraints([
NSLayoutConstraint(item: customView, attribute: .leading, relatedBy: .equal, toItem: tabBar, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: customView, attribute: .trailing, relatedBy: .equal, toItem: tabBar, attribute: .trailing, multiplier: 1, constant: 0),
NSLayoutConstraint(item: customView, attribute: .top, relatedBy: .equal, toItem: tabBar, attribute: .top, multiplier: 1, constant: -50),
NSLayoutConstraint(item: customView, attribute: .bottom, relatedBy: .equal, toItem: tabBar, attribute: .top, multiplier: 1, constant: 0)
])
There's a bunch of creative ways you can setup constraints to do what you want, but the constraints above should attach a view above your tabBar with a height of 50.
Make the view's frame with the height of tab bar and brings it to top, 2. set tabBar hidden is true.
Related
I have two subviews that I created, one in Storyboard one with code, I want to anchor the second view (created with code) to the first view (in storyboard) with some constraints so that the second view sits below the first view:
class ViewController: UIViewController{
#IBOutlet weak var view1: UIView!
override func viewDidLoad(){
super.viewDidLoad()
setUp()
}
func setUp(){
var view2 = UIView()
view2.setTranslatesAutoresizingMaskIntoConstraints(false)
view2.frame = CGRectMake(10,10,10,10)
self.view.addSubview(view2)
self.view.addConstraint(NSLayoutConstraint(item: view1, attribute: NSLayoutAttribute.TopMargin, relatedBy: NSLayoutRelation.Equal, toItem: view2, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 10))
}
}
The problem is that I got an error saying When added to a view, the constraint's items must be descendants of that view. Is it bad practice to have some views in storyboard and others in code?
You have a few problems.
First, the error means that view1 and view2 are not both part of self.view's view hierarchy. This is probably because view1 hasn't been decoded from the storyboard yet. Try moving this code from viewDidLoad() to awakeFromNib(), which is when they're guaranteed to have been loaded.
Second, you're trying to set the view's frame:
view2.frame = CGRectMake(10,10,10,10)
This frame will get overwritten by the layout engine making it pointless. Delete this line.
If you're going to use auto-layout, you need to unambiguously specify both the size (height & width) and position (x & y) of the view. The constraint you added only specifies the y-origin, so you also need to add more constraints (probably 3 more) to specify the x-origin, the width, and the height, which are currently undefined.
Is it bad practice to have some views in storyboard and others in code?
No, it's common.
EDIT
Thanks to #LeoNatan I have now got a complete working solution. If anyone finds this and would like the solution, it's available on GitHub.
Original Question
I'm trying to get iAds (or any other view for that matter, although it may be specific to ADBannerView) to be displayed just above a UITabBar. I've gone about a few different ways of doing this, but haven't come up with a solution that satifies the following:
Works on iOS 7 and 8
Works with and without the iAd displayed
Works in landscape and portrait
Works on iPhone and iPad
UITableViews insets correctly update
The only solution I have so far that has worked has been to have my UITableView inside a UIViewController, and adding the UITableView and ADBannerView to the view property of the UIViewController. I moved away from this for 2 reasons:
The UITableView did not extend its edges below the bottom UITabBar
I need to subclass UITableViewController, not UIViewController
I have a bannerView property on my AppDelegate and a shouldShowBannerView property to decide whether or not to show the iAd, and share a single instance. The AppDelegate then sends out notifications when iAds should be displayed or hidden (i.e., when an iAd is loaded and when the user has paid to remove the iAds). The "base" of the code works as such:
func showiAds(animated: Bool) {
if !self.showingiAd {
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
if let bannerView = delegate.bannerView {
println("Showing iAd")
self.showingiAd = true
if (bannerView.superview != self.view) {
bannerView.removeFromSuperview()
}
// let bannersSuperview = self.view.superview! // Bottom inset incorrect
let bannersSuperview = self.view // Banner is shown at the top screen. Crashes on iOS 7 (at bannersSuperview.layoutIfNeeded())
// let bannersSuperview = self.tableView // The is the same as self.view (duh)
// let bannersSuperview = self.tabBarController!.view // Bottom inset incorrect
// Added the view and the left/right constraints allow for the proper height
// to be returned when bannerView.frame.size.height is called (iOS 7 fix mainly)
bannersSuperview.addSubview(bannerView)
bannersSuperview.addConstraints([
NSLayoutConstraint(item: bannerView, attribute: .Left, relatedBy: .Equal, toItem: bannersSuperview, attribute: .Left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: bannerView, attribute: .Right, relatedBy: .Equal, toItem: bannersSuperview, attribute: .Right, multiplier: 1, constant: 0),
])
bannersSuperview.layoutIfNeeded()
let bannerViewHeight = bannerView.frame.size.height
var offset: CGFloat = -self.bottomLayoutGuide.length
if (UIDevice.currentDevice().systemVersion as NSString).floatValue < 8 {
// Seems to be needed for some reason
offset -= bannerViewHeight
}
let bannerBottomConstraint = NSLayoutConstraint(item: bannerView, attribute: .Bottom, relatedBy: .Equal, toItem: bannersSuperview, attribute: .Bottom, multiplier: 1, constant: offset + bannerViewHeight)
// self.bannerBottomConstraint = bannerBottomConstraint
bannersSuperview.addConstraint(bannerBottomConstraint)
bannersSuperview.layoutSubviews()
// bannerSuperview.setNeedsLayout()
bannersSuperview.layoutIfNeeded()
// Previously, this values was the height of the banner view, so that it starts off screen.
// Setting this to 0 and then doing an animation makes it slide in from below
bannerBottomConstraint.constant = offset
bannersSuperview.setNeedsUpdateConstraints()
UIView.animateWithDuration(animated ? 10 : 0, animations: { () -> Void in
// Calling layoutIfNeeded here will animate the layout constraint cosntant change made above
bannersSuperview.layoutIfNeeded()
})
} else {
println("Cannot show iAd when bannerView is nil")
}
}
}
func hideiAds() {
if self.showingiAd {
self.showingiAd = false
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
if let bannerView = delegate.bannerView {
if bannerView.superview == self.view {
bannerView.removeFromSuperview()
}
}
}
}
I then check in my viewWillAppear: and viewDidDisappear: methods if an iAds is/should be displayed and calling showiAds(false) and hideiAds() as required.
No matter what I do, I don't seem to be able to get it to work. A couple of other things I've tried but scrapped the code for:
Adding the iAd in the UITabBarController, which then alerts the UITableViewControllers that the iAd was shown/hidden. Modifying the content/scroll indicator insets did not work well, and was ofter reset by the UITableViewController to fit above/below the navigation/tab bar.
(as above) setting the content/scroll indicator insets myself, but I could not get it consistent without attempting to emulate (using (top|bottom)LayoutGuide) in viewDidLayoutSubviews, but this seems very costly?
I did, at one point, have it working by adding the ADBannerView to some view from within the UITableViewController, but it would crash on iOS 7 (something about tableView must call super -layoutSubviews)
EDIT
I have created a UIViewController subclass with the intent of using it to house UITableViewControllers via a Container View. Here is what I have so far, followed by a couple of issues:
class AdvertContainerViewController: UIViewController {
var tableViewController: UITableViewController?
var showingiAd = false
var bannerBottomConstraint: NSLayoutConstraint?
private var bannerTopOffset: CGFloat {
get {
var offset: CGFloat = 0
if let tabBar = self.tabBarController?.tabBar {
offset -= CGRectGetHeight(tabBar.frame)
}
if let bannerView = AppDelegate.instance.bannerView {
let bannerViewHeight = bannerView.frame.size.height
offset -= bannerViewHeight
}
return offset
}
}
override func viewDidLoad() {
super.viewDidLoad()
if self.childViewControllers.count > 0 {
if let tableViewController = self.childViewControllers[0] as? UITableViewController {
self.tableViewController = tableViewController
tableViewController.automaticallyAdjustsScrollViewInsets = false
self.navigationItem.title = tableViewController.navigationItem.title
}
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if AppDelegate.instance.shouldShowBannerView {
self.showiAds(false)
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let delegate = AppDelegate.instance
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showiAds", name: "BannerViewDidLoadAd", object: delegate)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "hideiAds", name: "RemoveBannerAds", object: delegate)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
if self.showingiAd {
self.hideiAds()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
println("View did layout subviews")
if self.showingiAd {
if let bannerView = AppDelegate.instance.bannerView {
let bannerViewHeight = CGRectGetHeight(bannerView.frame)
if let bottomConstraint = self.bannerBottomConstraint {
let bannerTopOffset = self.bottomLayoutGuide.length + bannerViewHeight
if bottomConstraint.constant != bannerTopOffset {
println("Setting banner top offset to \(bannerTopOffset)")
bottomConstraint.constant = -bannerTopOffset
bannerView.superview?.setNeedsUpdateConstraints()
bannerView.superview?.updateConstraintsIfNeeded()
}
}
println("Bottom layout guide is \(self.bottomLayoutGuide.length)")
let insets = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length + bannerViewHeight, 0)
self.updateTableViewInsetsIfRequired(insets)
}
}
}
private func updateTableViewInsetsIfRequired(insets: UIEdgeInsets) {
if let tableView = self.tableViewController?.tableView {
if !UIEdgeInsetsEqualToEdgeInsets(tableView.contentInset, insets) {
println("Updating content insets to \(insets.top), \(insets.bottom)")
tableView.contentInset = insets
}
if !UIEdgeInsetsEqualToEdgeInsets(tableView.scrollIndicatorInsets, insets) {
println("Updating scroll insets to \(insets.top), \(insets.bottom)")
tableView.scrollIndicatorInsets = insets
}
}
}
func showiAds() {
self.showiAds(true)
// self.showiAds(false)
}
func showiAds(animated: Bool) {
if !self.showingiAd {
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
if let bannerView = delegate.bannerView {
println("Showing iAd")
self.showingiAd = true
if (bannerView.superview != self.view) {
bannerView.removeFromSuperview()
}
let bannersSuperview = self.view.superview!
// Added the view and the left/right constraints allow for the proper height
// to be returned when bannerView.frame.size.height is called (iOS 7 fix mainly)
bannersSuperview.addSubview(bannerView)
bannersSuperview.addConstraints([
NSLayoutConstraint(item: bannerView, attribute: .Left, relatedBy: .Equal, toItem: bannersSuperview, attribute: .Left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: bannerView, attribute: .Right, relatedBy: .Equal, toItem: bannersSuperview, attribute: .Right, multiplier: 1, constant: 0),
])
bannersSuperview.layoutIfNeeded()
let bannerBottomConstraint = NSLayoutConstraint(item: bannerView, attribute: .Top, relatedBy: .Equal, toItem: bannersSuperview, attribute: .Bottom, multiplier: 1, constant: 0)
self.bannerBottomConstraint = bannerBottomConstraint
bannersSuperview.addConstraint(bannerBottomConstraint)
bannersSuperview.layoutSubviews()
bannersSuperview.layoutIfNeeded()
let topInset = self.navigationController?.navigationBar.frame.size.height ?? 0
let insets = UIEdgeInsetsMake(topInset, 0, -self.bannerTopOffset, 0)
// Previously, this values was the height of the banner view, so that it starts off screen.
// Setting this to 0 and then doing an animation makes it slide in from below
bannerBottomConstraint.constant = self.bannerTopOffset
bannersSuperview.setNeedsUpdateConstraints()
UIView.animateWithDuration(animated ? 0.5 : 0, animations: { () -> Void in
// Calling layoutIfNeeded here will animate the layout constraint cosntant change made above
self.updateTableViewInsetsIfRequired(insets)
bannersSuperview.layoutIfNeeded()
})
} else {
println("Cannot show iAd when bannerView is nil")
}
}
}
func hideiAds() {
if self.showingiAd {
self.showingiAd = false
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
if let bannerView = delegate.bannerView {
if bannerView.superview == self.view {
bannerView.removeFromSuperview()
}
}
}
}
}
Issues so far:
Using self.view as the superview causes a crash on rotate Auto Layout still required after sending -viewDidLayoutSubviews to the view controller. Gathered.AdvertContainerViewController's implementation needs to send -layoutSubviews to the view to invoke auto layout.
I'm not calculating the content insets correctly; when the iAd is shown, the top jumps up slightly and the bottom in below the top of the banner
The table view doesn't show the scroll indicators. This seems to be a known issue but I cannot find a solution
At the request of Leo Natan I have create a repo on GitHub that I will update with any attempts I make, and explain issues here. Currently, the issues are as follows:
First Tab:
Top of table moves down when iAd is shown (iOS 8)
Table cannot be scrolled (iOS 7)
Top of table view jumps when iAd shows (iOS 7)
Rotation often breaks the offset of the iAd, hiding it behind the tab bar (iOS 7 and 8)
Second Tab:
There are no scroll bars (iOS 7 and 8)
Scroll inset it not set (iOS 7)
Rotation often breaks the offset of the iAd, hiding it behind the tab bar (iOS 7 and 8)
The best solution is to use view controller containment. Use a view controller subclass that will house both the ad view and the table view controller's view, and add the table view controller as a child of the container view controller. This should take care of content insets correctly. On each layout of the container controller's view, position the table controller view hierarchy correctly after positioning the ad view. If you wish to hide the ad view, simply hide or remove it from the container hierarchy, and extend the table controller's view hierarchy fully. When working with hierarchies, remember to always use the table controller's view and not the tableView directly.
My answer was adapted into the following GitHub repo:
https://github.com/JosephDuffy/iAdContainer
The best that is that you download the AD suite from Apple site, there are tabbar controller and navigation controller containment example.
Apple provides you an abstract view controller that can handle by itself the ADBanner flow without interrupting its presentation, maximizing the showing time.
You can use this https://developer.apple.com/library/ios/samplecode/iAdSuite/Introduction/Intro.html apple sample and modified it according to your needs. Such as bool variable to take care of when iAds is shown or not.
There in code you can see BannerViewController class that contains all the logic. You can also write ADmob code there to use.
I've been going through the documentation and still seem to be on a sticking point.
I have a view controller object C_SelectPhoto. This has a container view. Inside the container view I want the childed view controller, C_SelectPhotoControllerView, to fit inside it. It will just be an array of photos. However, setting the frame and adding the child view controller is not working. If I move the x value of the desired child view controller, no effect happens.
To figure out what is going on I color coded everything. The container, below, is orange. The view the container expects, according to the storyboard is yellow. The view I actually want to fit in there is red.
Here is the storyboard:
Here is my controller code for C_SelectPhoto
class C_SelectPhoto:Controller
{
#IBOutlet weak var selectPhotoControllerView: UIView!
var _collectionViewController:C_SelectPhotoControllerView!
//TODO PERMISSION IS NEEDED BEFORE FETCHING
func initController()
{
_collectionViewController = Controller.STORYBOARD.instantiateViewControllerWithIdentifier("selectPhotoControllerView") as C_SelectPhotoControllerView
displayControllerViewController()
}
//show the photo selection
private func displayControllerViewController()
{
addChildViewController(_collectionViewController)
_collectionViewController.view.frame = CGRectMake(100, 0, 500, 500)
self.view.addSubview(_collectionViewController.view)
_collectionViewController.didMoveToParentViewController(self)
}
}
However the result is produces is below:
First, the yellow class shouldn't be added at all, I wanted only the red (the UICollectionViewController class). Second, I can tell the red class is being added to the wrong spot because its x value hasn't moved it over at all.
So my question is:
How can I add a UIContainerViewController, as a child to the main view controller, C_SelectPhoto, but have the UIContainerViewController frame FIT the container I have in the main view controller?
Thank you!!!
NOTE: The views I am trying to add are UICollectionViewControllers. When I add a UIViewController, the framing works just fine, but as you can see when adding the UICollectionViewControllers, the framing does NOT work, and they are getting added to random offsets and are not respecting my attempts to size them with frame assignments.
use following Extension for adding childViewController On View
extension UIViewController {
func configureChildViewController(childController: UIViewController, onView: UIView?) {
var holderView = self.view
if let onView = onView {
holderView = onView
}
addChildViewController(childController)
holderView.addSubview(childController.view)
constrainViewEqual(holderView, view: childController.view)
childController.didMoveToParentViewController(self)
childController.willMoveToParentViewController(self)
}
func constrainViewEqual(holderView: UIView, view: UIView) {
view.translatesAutoresizingMaskIntoConstraints = false
//pin 100 points from the top of the super
let pinTop = NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal,
toItem: holderView, attribute: .Top, multiplier: 1.0, constant: 0)
let pinBottom = NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal,
toItem: holderView, attribute: .Bottom, multiplier: 1.0, constant: 0)
let pinLeft = NSLayoutConstraint(item: view, attribute: .Left, relatedBy: .Equal,
toItem: holderView, attribute: .Left, multiplier: 1.0, constant: 0)
let pinRight = NSLayoutConstraint(item: view, attribute: .Right, relatedBy: .Equal,
toItem: holderView, attribute: .Right, multiplier: 1.0, constant: 0)
holderView.addConstraints([pinTop, pinBottom, pinLeft, pinRight])
}}
Updated for Swift 5+
Just one line in your view controller to add child view controller.
Super scalable methods in the extension if you want to add it on any custom view.
public extension UIViewController {
/// Adds child view controller to the parent.
///
/// - Parameter child: Child view controller.
func add(_ child: UIViewController) {
addChild(child)
view.addSubview(child.view)
child.didMove(toParent: self)
}
/// It removes the child view controller from the parent.
func remove() {
guard parent != nil else {
return
}
willMove(toParent: nil)
removeFromParent()
view.removeFromSuperview()
}
}
How to use:
Adding: In the view controller where you want to add the child view controller.
// let yourChildViewController = Load fro the storyboard or XIB
add(yourChildViewController)
Removing:
yourChildViewController.remove()
If you want the red controller to be the child controller, delete the yellow one, and control-drag from the container to the red controller. There's no need to add it in code, or do any resizing. The red controller will be set to the same size as the container in the storyboard.
I have a question on Auto layout. I'm using xib files and I have a view controller like this.
Its embedded inside a UINavigationController so I have the button positioned with a Top Space to Superview constraint.
The problem is when I rotate the device, it looks like this.
As you can see that constraint still keeps its original value so there's a big gap between the navigation bar edge and the button in landscape mode.
How can I make the button position close to the navigation bar like when its in the portrait and have it that way in both portrait and landscape modes? I'm using Xcode 6 by the way.
Thank you.
If you want to define your auto layout top margin constraints in your Xib file, you can add the following code in the ViewController's class file relative to your Xib:
override func viewDidLoad() {
super.viewDidLoad()
if self.respondsToSelector("edgesForExtendedLayout") {
edgesForExtendedLayout = UIRectEdge.None
}
}
Simple. But the problem there is that you won't be able to have a translucent navigation bar.
Fortunately, there are several alternatives to this. You can define your auto layout top margin as relative to your Top Layout Guide (not to your view) with Storyboard or with code.
If you move to Storyboard, click on the Pin button, select your top margin constraint and choose The Top Layout Guide (see image below).
If you decide to define all your UIButton's constraints with code, you can use Visual Format Language as indicated in the following code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var button = UIButton()
button.backgroundColor = UIColor.blueColor()
button.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(button)
var viewsDict = ["button" : button, "topLayoutGuide" : topLayoutGuide]
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayoutGuide]-20-[button]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[button]-20-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
}
}
Finally, there is a fourth (kind of mixed) way to perform what you want to do. Set your constraints in your Xib, drag your top margin constraint and your UIButton to your view controller class (name them topConstraint and button) and set your code as the following:
import UIKit
class ViewControllerTwo: UIViewController {
#IBOutlet weak var topConstraint: NSLayoutConstraint!
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
view.addConstraint(NSLayoutConstraint(item: button, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.topLayoutGuide, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 10))
view.removeConstraint(topConstraint)
}
}
Ok so I'm struggling here and haven't been able to find a working solution. I've been self learning Swift without Objective C experience (I know, I know).
In my app, I have my main UIViewController, a subview that is transparent but slides in from the bottom of the screen, and then 4 subviews of the sliding subview that are all working UIScrollViews. I have paging enabled and it works great but I'd like to add a UIPageControl for each of them. I seriously can't grasp delegates and how to implement the using swift. Any help would be much appreciated!
Also, I'm doing this all programmatically, so no IB please. Happy to provide code if it'll help. Thanks
I think you and/or anyone else looking for how to do this will find this answer helpful. The code example enabled me to create a page control indicator on my scrollView, and it was the first time attempting to do this. I found it very clear.
The lines you probably need to add to your project are:
1: add UIScrollViewDelegate as a protocol when you first name your view controller class.
2: in the class declaration create a pageControl variable. You will need to play with the frame numbers to get it to appear where you want it. the current numbers made one in the middle of the window for me.
For reference the numbers mean (x position for top left corner of indicator, y coordinate for top left corner, width of page indicator, height of page indicator)
var pageControl : UIPageControl = UIPageControl(frame: CGRectMake(50, 300, 200, 20))
in viewDidLoad set the scrollView delegate and call `configurePageControl():
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
configurePageControl()
}
you need to add two methods after viewDidLoad. one is called in viewDidLoad
func configurePageControl() {
self.pageControl.numberOfPages = <some reference to the number of pages>
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.redColor()
self.pageControl.pageIndicatorTintColor = UIColor.blackColor()
self.pageControl.currentPageIndicatorTintColor = UIColor.greenColor()
self.view.addSubview(pageControl)
}
and
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
The scrollView delegate is actually very simple to set up. Add UIScollViewDelegate as a protocol that your ViewController class will implement by adding it after the class declaration: class YourClassName: UIScrollViewDelegate. And then in viewDidLoad(), you complete the delegate setup by assigning the scroll view's delegate property to your class with the line scrollView.delegate = self. (again see the example I linked for if you need further clarification of where these commands go)
Just setup it in code like this:
private var pageControl = UIPageControl(frame: .zero)
private func setupPageControl() {
pageControl.numberOfPages = controllers.count
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.currentPageIndicatorTintColor = UIColor.orange
pageControl.pageIndicatorTintColor = UIColor.lightGray.withAlphaComponent(0.8)
let leading = NSLayoutConstraint(item: pageControl, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0)
let trailing = NSLayoutConstraint(item: pageControl, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0)
let bottom = NSLayoutConstraint(item: pageControl, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.insertSubview(pageControl, at: 0)
view.bringSubview(toFront: pageControl)
view.addConstraints([leading, trailing, bottom])
}
UIPageControl Integration using Swift 4
func configurePageControl() {
self.pageview.numberOfPages = items.count
self.pageview.currentPage = 0
self.pageview.tintColor = UIColor.red
self.pageview.pageIndicatorTintColor = UIColor.black
self.pageview.currentPageIndicatorTintColor = UIColor.green
}
Delegates are just methods of a class, that can be palmed off to another class. These methods are usually callbacks.
e.g. A callback for a TextField, when the user hits return, can be implemented in a Delegate. The delegate can be implemented in the ViewController class.
Now when the user hits return the TextField Object will call the delegate method in the ViewController object. The great thing about this is, you can access all the variables and methods of the ViewController object from the delegated method. Otherwise you would need a handle to the ViewController object within the TextField object itself.
Delegates are implemented as protocols, which are just interfaces. So if the ViewController implements the TextFieldDelegate protocol, all your textfield callbacks can be called from within the ViewController object.
I hope this helps.