【问题标题】:Programmatically adding a view between a tableView and bottomLayoutGuide以编程方式在 tableView 和 bottomLayoutGuide 之间添加视图
【发布时间】:2016-05-06 10:09:09
【问题描述】:

我有一个UIViewController 和一个UITableView,它填满了支持广告的版本的屏幕。 MyViewController 嵌入在 UINavigationController 中,底部有 UITabBarController

此应用将有 2 个版本:

1) 付费 - 我在情节提要上进行了配置。它可以按需要工作。

2) 广告支持 - 尽我所能,我无法在正确的位置绘制横幅。我正在尝试这样做:

topLayoutGuide
tableview
standard height padding
bannerView (50 height)
standard height padding
bottomLayoutGuide

相反,bannerView 被绘制在 tableView 的顶部,而不是在 tableViewbottomLayoutGuide 之间

我从viewDidLoad 调用我创建的名为configureBannerView 的方法。这是用可视格式语言布置视图的代码的相关部分:

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

我不明白为什么这不起作用。下面是完整的configureBannerView 方法。

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)

感谢您的阅读。欢迎提出解决我的错误代码的建议。

【问题讨论】:

  • (可能不适用,但值得关注)如果您的目标是 iOS 9.0 及更高版本,使用 Stack View 会容易得多。
  • @ishaq 我没有想到,但这看起来很简单。我想做的不是火箭科学,但这看起来更容易实现。 stackoverflow.com/a/31577312/4475605
  • 好吧,我希望它有所帮助:)
  • 再次感谢您推荐UIStackView。再加上 SwiftArchitect 的建议和他网站上的 AutoLayout 文章的一些灵感,我感到很满意。我完成了我的整个项目,并用 StackView 重新编写了我的大部分 VC。简单得多,代码少得多,布局更好看,并且记录的约束错误为零。

标签: ios swift uitableview uiviewcontroller admob


【解决方案1】:

故事板解决方案

除非所有其他途径都失败了,否则不要以编程方式添加约束:在构建、链接、运行之前,无法查看您在做什么。

一个需要更少代码的更简单的解决方案是保留视图的引用和来自 Interface Builder 的约束,并且:

constraintToTweak.constant = newValue

与其他属性不同,常量可以在约束创建后修改。在现有约束上设置常量比删除约束并添加一个与旧约束一样但具有新常量的新约束要好得多。

constraintToTweak.active = false

可以通过操作此属性来激活或停用接收器。只有活动约束影响计算的布局。尝试激活其项没有共同祖先的约束将导致抛出异常。对于新创建的约束,默认为 NO。

【讨论】:

  • 谢谢。所以听起来你建议我在故事板上布置两个视图,然后从故事板到 VC 的ctrl + drag 约束并以编程方式调整它们?那太酷了!我不知道您可以从情节提要中创建IBOutlets。这要简单得多!
  • IBOutlets 来自约束,即 ;)
  • 正确。您不能对它们做太多事情(尽管我已经看到一些工程师在运行时将它们删除,如果这是一次性操作),但您可以更改它们的值或激活/停用它们。一个额外的好处是这两个值可以动画化,可以创造出很好的视觉效果。
  • 嗯...这就像打地鼠一样。我最终为我的tableViewbannerView 使用了UIStackViewbannerView.hidden = true 它保持在界面构建器中的配置状态。虽然框架在预期的时间和地点绘制,但即使请求发送成功,广告也不会出现在框架中。这还没有“让我跳舞”,但我认为我现在正在处理 AdMob 问题。谢谢你帮我删掉了 200 行代码;)
【解决方案2】:

ishaqSwiftArchitect 的帮助下,我想通了。事实证明,让GADBannerView 正确显示在UITableView 下方而不将其添加为页脚的关键非常简单。通过执行以下操作,我最终砍掉了 100 行不必要的代码:

1) UIStackView:如果你以前没有使用过这个,请停止你现在正在做的事情并关注this tutorial

我在界面生成器中添加了我的tableView: UITableViewbannerView: GADBannerView 到垂直UIStackView

2) 我在MyViewController 上为他们俩创建了IBOutlets(我已经有tableView)。

3) 我重构的configureBannerView 看起来像这样。

// 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

    }
}

感谢 ishaqSwiftArchitect 为我指明了正确的方向。

【讨论】:

    【解决方案3】:

    添加新的 adMob UIViewController 作为根控制器并添加旧根控制器的子视图。

    我有一个非常相似的程序问题案例,必须将 adMob 添加到 UITabBarController 根控制器,其中的选项卡控制器是 UINavigationController。他们都在尝试在内部调整它们的大小时遇到​​了很大的阻力,广告只是在应用程序视图上输入的。 我可能只是没有足够幸运找到一条可行的路径。阅读大量 Stackoverflow 和 Google 提示。

    我也相信 Apple 和 Google 的推荐就像有下面的广告,与应用分开但又紧密联系在一起。 Android Admob 横幅的显示方式相同,并且需要相同的行为。 (最近为 Android 做了同样的项目)。

    制作一个新的应用根控制器,一个带有 Admob 的 ViewController

    为了在未来的项目中可重用和顺利实施,大约需要放入一个新的 adUnitID(以及旧根控制器的名称)。横幅将位于应用程序屏幕下方。

    只需将这个类文件添加到项目中,并使其成为 AppDelegate.swift 文件中的根控制器。

    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
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-24
      • 2012-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多