I've been trying to add constraints programmatically to a view that I'm also adding programmatically to my view controller. However, it seems like the constraints are not being followed.
The view has been added to the story board for the view controller, but isn't actually added to the view controller's view until later on (See screenshot below).
I've tried adding a variety of constraints but none of them have worked so far. I've simplified it now to the single constraint below and even this will not work. What am I doing wrong?
#IBOutlet var loadingView: LoadingView!
override func viewDidLoad() {
super.viewDidLoad()
displayLoadingView(true)
}
func displayLoadingView(display: Bool) {
if display {
view.addSubview(loadingView)
let widthConstraint = NSLayoutConstraint(item: loadingView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50.0)
view.addConstraint(widthConstraint)
}
}
set translatesAutoresizingMaskIntoConstraints = false to any view you are settings constraints programatically.
from the apple doc: translatesAutoresizingMaskIntoConstraints
If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false, and then provide a non ambiguous, nonconflicting set of constraints for the view.
You don't set all necessary constraints, that can be the reason. Consider following rough example. MyView interface is defined in standalone xib file. Hope it helps:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let myView = loadFromNib("MyView") else {
return
}
view.addSubview(myView)
myView.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-15-[myView]-15-|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: ["myView": myView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-15-[myView]-15-|", options: NSLayoutFormatOptions.DirectionLeadingToTrailing, metrics: nil, views: ["myView": myView]))
}
func loadFromNib(cls: String) -> UIView? {
return NSBundle.mainBundle().loadNibNamed(cls, owner: nil, options: nil)[0] as? UIView
}
}
Related
I have a custom view. I am trying to add this custom view and center it in my ViewController. I created the view in storyboard but am adding it to my ViewController programmatically. The init function requires that I give a frame. I don't want to specify a frame because I want the view to be autosized based on what content is in the view controller and then I just want to use my constraints to center the view.
This is the code within my viewcontroller that I use to add my custom view
let reportWindow = ReportWindow(user: self.uid!, frame: CGRect(x: 0, y: 0, width: 100, height: 100))
self.view.addSubview(reportWindow)
let centerX = NSLayoutConstraint(item: reportWindow, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0)
let centerY = NSLayoutConstraint(item: reportWindow, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([centerX, centerY])
This is the code for my custom view class
class ReportWindow: UIView{
var uid: String
#IBOutlet weak var title: UILabel?
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var contentView: UIView!
init(user uid: String, frame: CGRect) {
self.uid = uid
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#IBAction func cancel(_ sender: UIButton) {
self.removeFromSuperview()
}
#IBAction func confirm(_ sender: UIButton) {
self.removeFromSuperview()
}
}
extension ReportWindow{
func commonInit() {
let view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = UIView.AutoresizingMask(rawValue: UIView.AutoresizingMask.RawValue(UInt8(UIView.AutoresizingMask.flexibleWidth.rawValue) | UInt8(UIView.AutoresizingMask.flexibleHeight.rawValue)))
self.addSubview(view)
textView.delegate = self
textView.textAlignment = .center
self.translatesAutoresizingMaskIntoConstraints = false
}
func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "ReportWindow", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
}
This is what my custom view looks like in storyboard
And this is what my custom view looks like while the application is running, the background is missing, and I cannot interact with the buttons or the text view. I've read that this is because my content is outside of my frame. I want the frame to auto place itself around all my content though, I don't want to have to specify the size and position of the frame.
You're going to have a much easier time with this if you simply present the view inside a modal view controller programatically or using a segue.
Copy your view to a new view controller and present that view controller modally. If you do so using a segue, you will need to set the uid of the view controller inside your preformSegue function. Otherwise, you would set it where you present the view controller. You can handle the button tap inside that view controller.
If that doesn't achieve the look you're going for (a floating view) by default, you can set the new view controller's view to be clear and have a smaller view (your already defined view) anchored to its center.
As a sidenote, I would recommend renaming your class to ReportUserView as it is not a UIWindow. It is a UIView, which is what you want. Windows are a bit different in iOS. You usually only have one and just deal with views and controllers inside it.
I have a UIViewController with a UITableView that fills the screen for an ad-supported version. MyViewController is embedded in a UINavigationController and it has a UITabBarController at the bottom.
There will be 2 versions of this app:
1) Paid - I have this configured on a storyboard. It works as desired.
2) Ad Supported - Try as I may, I can't get the banner to draw in the right spot. I'm trying to do this:
topLayoutGuide
tableview
standard height padding
bannerView (50 height)
standard height padding
bottomLayoutGuide
Instead, the bannerView is being drawn on top of the tableView, rather than between the tableView and the bottomLayoutGuide
I call a method I created called configureBannerView from viewDidLoad. Here' the relevant portion of the code that lays out the view in Visual Format Language:
var allConstraints = [NSLayoutConstraint]()
let horizontalTableViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[tableView]|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: nil,
views: views)
allConstraints += horizontalTableViewConstraint
let horizontalBannerViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[leftBannerViewSpacer]-[bannerView(320)]-[rightBannerViewSpacer(==leftBannerViewSpacer)]|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: nil,
views: views)
allConstraints += horizontalBannerViewConstraint
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topLayoutGuide][tableView]-[leftBannerViewSpacer(50)]-[bottomLayoutGuide]|",
options: [],
metrics: metrics,
views: views)
allConstraints += verticalConstraints
I can't figure out why this isn't working. Below is the complete configureBannerView method.
func configureBannerView() {
if adSupported == false {
// Do nothing, leave it alone
} else {
// remove existing constraints
tableView.removeConstraints(tableView.constraints)
tableView.translatesAutoresizingMaskIntoConstraints = false
// create dictionary of views
var views: [String : AnyObject] = [
"tableView" : tableView,
"topLayoutGuide": topLayoutGuide,
"bottomLayoutGuide": bottomLayoutGuide]
// Create a frame for the banner
let bannerFrame = CGRect(x: 0, y: 0, width: kGADAdSizeBanner.size.width, height: kGADAdSizeBanner.size.height)
// Instnatiate the banner in the frame you just created
bannerView = GADBannerView.init(frame: bannerFrame)
bannerView?.translatesAutoresizingMaskIntoConstraints = false
// add the bannerView to the view
view.addSubview(bannerView!)
// add bannerView to the view dictionary
views["bannerView"] = bannerView
// Create spacers for left and right sides of bannerView
// 32.0 = leftSpacer left pad + leftSpacer right pad + rightSpacer left pad + rightSpacer right pad
// Calculate width of spacer
let spacerWidth = (screenSize.width - kGADAdSizeBanner.size.width - 32.0) / 2
// Instantiate left and right pads
// 50.0 = height of bannerView
let leftBannerViewSpacer = UIView(frame: CGRect(x: 0, y: 0, width: spacerWidth, height: 50.0))
let rightBannerViewSpacer = UIView(frame: CGRect(x: 0, y: 0, width: spacerWidth, height: 50.0))
leftBannerViewSpacer.translatesAutoresizingMaskIntoConstraints = false
rightBannerViewSpacer.translatesAutoresizingMaskIntoConstraints = false
// add the spacers to the subview
view.addSubview(leftBannerViewSpacer)
view.addSubview(rightBannerViewSpacer)
// add to the views dictionary
views["leftBannerViewSpacer"] = leftBannerViewSpacer
views["rightBannerViewSpacer"] = rightBannerViewSpacer
// Create metric for tabBarHeight
let tabBarHeight = tabBarController?.tabBar.frame.height
// Create a dictionary of metrics
let metrics: [String : CGFloat] = ["tabBarHeight": tabBarHeight!]
var allConstraints = [NSLayoutConstraint]()
let horizontalTableViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[tableView]|",
options: NSLayoutFormatOptions.AlignAllCenterY,
metrics: nil,
views: views)
allConstraints += horizontalTableViewConstraint
let horizontalBannerViewConstraint = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[leftBannerViewSpacer]-[bannerView(320)]-[rightBannerViewSpacer(==leftBannerViewSpacer)]|",
options: [NSLayoutFormatOptions.AlignAllCenterY],
metrics: nil,
views: views)
allConstraints += horizontalBannerViewConstraint
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topLayoutGuide][tableView]-[leftBannerViewSpacer(50)]-[bottomLayoutGuide]|",
options: [],
metrics: metrics,
views: views)
allConstraints += verticalConstraints
NSLayoutConstraint.activateConstraints(allConstraints)
Thank you for reading. I welcome suggestions to resolve my erroneous code.
Storyboard solution
Do not add constraints programmatically unless all other avenues have failed: there is no way to see what you are doing until you build, link, run.
A much simpler solution requiring much less code is to hold on to references of your views an constraints from Interface Builder, and:
constraintToTweak.constant = newValue
Unlike the other properties, the constant may be modified after constraint creation. Setting the constant on an existing constraint performs much better than removing the constraint and adding a new one that's just like the old but for having a new constant.
or
constraintToTweak.active = false
The receiver may be activated or deactivated by manipulating this property. Only active constraints affect the calculated layout. Attempting to activate a constraint whose items have no common ancestor will cause an exception to be thrown. Defaults to NO for newly created constraints.
With ishaq's and SwiftArchitect's assistance, I figured it out. It turns out, the key to getting the GADBannerView to display properly below a UITableView without adding it as a footer was super simple. I ended up chopping 100 lines of needless code by doing the following:
1) UIStackView: If you haven't used this before, stop what you're doing now and follow this tutorial.
I added my tableView: UITableView and the bannerView: GADBannerView in interface builder to a vertical UIStackView
2) I created IBOutlets (I had tableView already) for both of them on MyViewController.
3) My refactored configureBannerView looks like this.
// I added these properties at the top. I did not know you could drag
// constraints from Interface Builder onto the ViewController
#IBOutlet weak var bannerViewHeight: NSLayoutConstraint!
#IBOutlet weak var bannerViewWidth: NSLayoutConstraint!
func configureBannerView() {
if adSupported == true {
loadAd() // follow Google's documentation to configure your requests
} else {
bannerView.hidden = true // this hides the bannerView if it's not needed
// Removes constraint errors when device is rotated
bannerViewWidth.constant = 0.0
bannerViewHeight.constant = 0.0
}
}
Thanks to ishaq & SwiftArchitect for pointing me in the right direction.
Add a new adMob UIViewController as root controller and addSubview of the old root controller.
I had a programmatic very similar problem case having to add adMob to a UITabBarController root controller with tab Controllers that were UINavigationController. Them all making hard resistance in trying to resize them internally, the ads were just typed over the application views. I might have just not been lucky enough finding a working way that path. Read a lot Stackoverflow and Google hints.
I also believe Apples and Googles recommendations are like having the ads below and kind of separate from but tight to the app. The Android Admob banners appears the same way, and same behavior is wanted. (Did the same project for Android recently).
Make a new app root controller, a ViewController with Admob
To be reusable and smoothly implementable in future projects, about a new adUnitID is all needed to be put in (and the name of the old root controller). The banner will be below the applications screen.
Just add this class file to the project and make it the root controller in the AppDelegate.swift file.
import UIKit
import GoogleMobileAds
class AdsViewController: UIViewController, GADBannerViewDelegate {
let adsTest = true
let ads = true //false //
let adUnitIDTest = "ca-app-pub-3940256099942544/2934735716"
let adUnitID = "ca-app-pub-xxxxxxxxxxxxxxxx/xxxxxxxxxx"
let tabVc = MainTabBarController()
var bannerView: GADBannerView!
override func viewDidLoad() {
super.viewDidLoad()
// Add the adMob banner
addBanner(viewMaster: view)
// Add the old root controller as a sub-view
view.addSubview(tabVc.view)
// Make its constraints to fit the adMob
if(ads) {
tabVc.view.translatesAutoresizingMaskIntoConstraints = false
tabVc.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tabVc.view.bottomAnchor.constraint(equalTo: bannerView.topAnchor).isActive = true
tabVc.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tabVc.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// To be notified of rotation
NotificationCenter.default.addObserver(self, selector: #selector(AdsViewController.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}
}
/*******************************
Ads banner standard engine
*******************************/
func addBanner(viewMaster: UIView) {
if(ads) {
// We instantiate the banner with desired ad size.
bannerView = GADBannerView(adSize: kGADAdSizeSmartBannerPortrait)
addBannerViewToView(bannerView, viewMaster: viewMaster)
if(adsTest) {
bannerView.adUnitID = adUnitIDTest
} else {
bannerView.adUnitID = adUnitID
}
bannerView.rootViewController = self
bannerView.load(GADRequest())
bannerView.delegate = self
}
}
func addBannerViewToView(_ bannerView: GADBannerView, viewMaster: UIView) {
bannerView.translatesAutoresizingMaskIntoConstraints = false
viewMaster.addSubview(bannerView)
viewMaster.addConstraints(
[NSLayoutConstraint(item: bannerView,
attribute: .bottom,
relatedBy: .equal,
toItem: viewMaster.safeAreaLayoutGuide,
attribute: .bottom,
multiplier: 1,
constant: 0),
NSLayoutConstraint(item: bannerView,
attribute: .centerX,
relatedBy: .equal,
toItem: viewMaster,
attribute: .centerX,
multiplier: 1,
constant: 0)
])
}
/*******************************
Rotation (change SmartBanner)
*******************************/
#objc func rotated() {
if UIDevice.current.orientation.isPortrait {
bannerView.adSize = kGADAdSizeSmartBannerPortrait
}
if UIDevice.current.orientation.isLandscape {
bannerView.adSize = kGADAdSizeSmartBannerLandscape
}
}
}
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)
}
}