I have created xib view(AdvertisementView) programatically and here i have added Delegate method for button
import UIKit
protocol SampleButtonViewDelegate: AnyObject {
func sampleButtonTapped()
}
class AdvertisementView: UIView {
// MARK: - MySubViews
//some other view...
lazy var closeButton: UIButton = {
let buttonClose = UIButton(type: .roundedRect)
buttonClose.translatesAutoresizingMaskIntoConstraints = false
buttonClose.backgroundColor = .red
buttonClose.setTitle("X", for: .normal)
buttonClose.addAction {
print("this is close button")
self.delegate?.sampleButtonTapped()
}
return buttonClose
}()
// MARK: - Properties
var website = ""
weak var delegate: SampleButtonViewDelegate?
// MARK: - Initializers
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = true
addCloseButton()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
clipsToBounds = true
addCloseButton()
}
private func addCloseButton() {
addSubview(closeButton)
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: titleLabel.topAnchor),
closeButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 15),
closeButton.widthAnchor.constraint(equalToConstant: 35),
closeButton.heightAnchor.constraint(equalToConstant: 25)
])
}
}
in global.swift file i have added AdvertisementView to containerview--- this is global file
let adView = AdvertisementView()
func addAdvertisementView(containerView: UIView) {
// let adView = AdvertisementView()
containerView.isHidden = false
containerView.clipsToBounds = true
containerView.addSubview(adView)
adView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
adView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5),
adView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -5),
adView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 5),
adView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -5)
])
}
and in parent class i have added view in storyboard and given its IBOutlet as adContainerView: with this code i can able to close adContainerView when i click close button
after closing if i move from messagelist view controller and come back then again i want to show whole adContainerView with adView but i am getting only adContainerView but on top adView design is not coming why?
how to show adContainerView with adView`... please guide me
class MessageList: UIViewController, SampleButtonViewDelegate {
#IBOutlet weak var adContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addAdvertisementView(containerView: adContainerView)
adView.delegate = self
}
func sampleButtonTapped() {
adContainerView.isHidden = true
}
}
When you move to messagelist view controller, the previous view was not deinit. It just only hide so all the property you set still remain there. Apple calls it View Controller Life Cycle.
In here is adContainerView still being hidden by the func sampleButtonTapped() just like you describe
So if you need to unhide it when you get back from another view. You show check it in viewWillAppear because the view will appear again not from initialize
Code will be like this - You can add more condition to check if you need to unhide it
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if (adContainerView.isHidden) {
adContainerView.isHidden = false
}
}
Related
I have a label that I cant add constraints to because it is in a Collection Reusable View, which doesn't have a viewDidLoad method. Is it possible to constrain the header so it is always 15 px from the left?
I tried adding constraints normally, but again, the Reusable View doesnt have a view for some reason. The other solutions I looked at all use a view of sorts.
override func layoutSubviews() {
super.layoutSubviews()
headerTitle.translatesAutoresizingMaskIntoConstraints = false
headerTitle.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15).isActive = true
headerTitle.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
headerTitle.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
headerTitle.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
headerTitle.frame = bounds
}
This code throws the error Cannot find 'view' in scope which im assuming is a Reusable View thing rather than a non existent view. Here is the rest of my code:
class HeaderCollectionReusableView: UICollectionReusableView {
static let identifier = "homeheader"
private let headerTitle = UILabel()
private let newDataSet = UIButton()
override init(frame: CGRect) {
super.init(frame:frame)
headerTitle.text = "AppTitle"
headerTitle.font = UIFont.systemFont(ofSize: 32.0, weight: .bold)
headerTitle.textAlignment = .left
headerTitle.numberOfLines = 0
addSubview(headerTitle)
}
required init?(coder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
headerTitle.frame = bounds //<-- want to replace this line with the constraints
}
}
I'm hoping the solution works with buttons too. This is my desired output:
desired output from constraints
You are trying to put constraints on a label but you don't have a parent view of that label. Constraints need some view to hold onto in order to be functional.
When I add a UIHostingController which contains a SwiftUI view as a childView, and then place that childView inside a UIScrollView, scrolling breaks.
Here I have my View
struct TestHeightView: View {
let color: UIColor
var body: some View {
VStack {
Text("THIS IS MY TEST")
.frame(height: 90)
}
.fixedSize(horizontal: false, vertical: true)
.background(Color(color))
.edgesIgnoringSafeArea(.all)
}
}
Then I have a UIViewController with a UIScrollView as the subView. Inside the UIScrollView there is a UIStackView that is correctly setup to allow loading UIViews and scrolling through them if the stack height becomes great enough. This works. If I were to load in 40 UILabels, it would scroll through them perfectly.
The problem arises when I add a plain old UIView, and then add a UIHostingController inside that container. I do so like this:
let container = UIView()
container.backgroundColor = color.0
stackView.insertArrangedSubview(container, at: 0)
let test = TestHeightView(color: color.1)
let vc = UIHostingController(rootView: test)
vc.view.backgroundColor = .clear
add(child: vc, in: container)
func add(child: UIViewController, in container: UIView) {
addChild(child)
container.addSubview(child.view)
child.view.translatesAutoresizingMaskIntoConstraints = false
child.view.topAnchor.constraint(equalTo: container.topAnchor, constant: 0).isActive = true
child.view.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 0).isActive = true
child.view.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true
child.view.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0).isActive = true
child.didMove(toParent: self)
}
In my example I added 3 of these containerViews/UIHostingController and then one UIView (green) to demonstrate what is happening.
You can see that as I scroll, all views are suspended as a gap is formed. What is happening is that the containing UIView (light color) is expanding its height. Once the height reaches a certain value, scrolling continues as normal until the next container/UIHostingController reaches the top and it begins again.
I have worked on several different solutions
.edgesIgnoringSafeArea(.all)
Does do something. I included it in my example because without it, the problem is exactly the same only more jarring and harder to explain using a video. Basically the same thing happens but without any animation, it just appears that the UIScrollView has stopped working, and then it works again
Edit:
I added another UIViewController just to make sure it wasn't children in general causing the issue. Nope. Only UIHostingControllers do this. Something in SwiftUI
Unbelievably this is the only answer I can come up with:
I found it on Twitter here https://twitter.com/b3ll/status/1193747288302075906?s=20 by Adam Bell
class EMHostingController<Content> : UIHostingController<Content> where Content : View {
func fixedSafeAreaInsets() {
guard let _class = view?.classForCoder else { return }
let safeAreaInsets: #convention(block) (AnyObject) -> UIEdgeInsets = { (sself : AnyObject!) -> UIEdgeInsets in
return .zero
}
guard let method = class_getInstanceMethod(_class.self, #selector(getter: UIView.safeAreaInsets)) else { return }
class_replaceMethod(_class, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets), method_getTypeEncoding(method))
let safeAreaLayoutGuide: #convention(block) (AnyObject) ->UILayoutGuide? = { (sself: AnyObject!) -> UILayoutGuide? in
return nil
}
guard let method2 = class_getInstanceMethod(_class.self, #selector(getter: UIView.safeAreaLayoutGuide)) else { return }
class_replaceMethod(_class, #selector(getter: UIView.safeAreaLayoutGuide), imp_implementationWithBlock(safeAreaLayoutGuide), method_getTypeEncoding(method2))
}
override var prefersStatusBarHidden: Bool {
return true
}
}
Had the same issue recently, also confirm that safe area insets are breaking the scrolling. My fix on iOS 14+ with the ignoresSafeArea modifier:
public var body: some View {
if #available(iOS 14.0, *) {
contentView
.ignoresSafeArea()
} else {
contentView
}
}
I had a very similar issue and found a fix by adding the following to my UIHostingController subclass:
override func viewDidLoad() {
super.viewDidLoad()
edgesForExtendedLayout = []
}
I have a search view controller, I'd like to show a loading indicator in the centre of the screen, but as I'm using large title navigation, it appears to be offset but the height of the large nav?
How can I offset this so it is in the true centre of the screen?
I am setting it currently using
tableView.addSubview(searchingView)
searchingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchingView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
searchingView.centerYAnchor.constraint(equalTo: tableView.centerYAnchor)
])
My view controller is below
final class SearchViewController: UITableViewController {
private var searchLoadingController: SearchLoadingController?
private var searchController: UISearchController?
convenience init(searchLoadingController: SearchLoadingController, searchController: UISearchController) {
self.init(nibName: nil, bundle: nil)
self.searchLoadingController = searchLoadingController
self.searchController = searchController
}
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
}
extension SearchViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text, !text.isEmpty else { return }
searchLoadingController?.search(query: text)
}
}
extension SearchViewController: SearchErrorView {
func display(_ viewModel: SearchErrorViewModel) { }
}
private extension SearchViewController {
func configureUI() {
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.searchController = searchController
tableView.tableFooterView = UIView(frame: .zero)
searchController?.searchBar.searchBarStyle = .minimal
searchController?.searchResultsUpdater = self
searchController?.obscuresBackgroundDuringPresentation = false
if let searchingView = searchLoadingController?.view {
tableView.addSubview(searchingView)
searchingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchingView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
searchingView.centerYAnchor.constraint(equalTo: tableView.centerYAnchor)
])
}
}
}
If you really want the activity indicator in the center of the screen you need to do some calculations:
func addActivityIndicatorToCenterOfScreen() {
let screenHeight = UIScreen.main.bounds.size.height
let activityIndicatorHeight = searchingView.bounds.size.height
let safeAreaBottomInset = tableView.safeAreaInsets.bottom
let yOffset = (screenHeight - activityIndicatorHeight) / 2 - safeAreaBottomInset
tableView.addSubview(searchingView)
searchingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchingView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
searchingView.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -yOffset),
])
}
Also ensure that you configure the constraints after your views are laid out. viewDidAppear is a good place for that. This is necessary because we need to know the size of the tableView and the safe area.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
addActivityIndicatorToCenterOfScreen()
}
Now for some explanation. tableView.safeAreaLayoutGuide includes the portion of the tableView between the navigation bar and the notch at the bottom of the screen. tableView.safeAreaInsets.bottom gives us the height of the notch at the bottom of the screen. The yOffset is the center of the screen minus half the height of the activity indicator minus the height of notch of the bottom of the screen. We will offset the bottom of the activity indicator from the bottom of the safe area. That will place the center of the activity indicator at the center of the screen.
If I were you, I would center the activity indicator between the navigation bar and the top of the notch. It's way cleaner and you can do this in viewDidLoad:
func addActivityIndicatorCenteredBetweenNavBarAndSafeAreaBottom() {
tableView.addSubview(searchingView)
searchingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchingView.centerXAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.centerXAnchor),
searchingView.centerYAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.centerYAnchor),
])
}
My bug:
If navigate from a view controller with large titles enabled to a view controller with large titles disabled i see same bug. Height navigation bar changes not smoothy.
I want animation change height navBar during segue on another viewController like this
Common propertyes for navBar set up in BaseNavigationController
class BaseNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
setNavBarTitlesPropertyes()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
private func setNavBarTitlesPropertyes() {
navigationBar.tintColor = .white
navigationBar.titleTextAttributes = [
.foregroundColor: UIColor.white
]
if #available(iOS 11.0, *) {
navigationBar.prefersLargeTitles = true
navigationBar.largeTitleTextAttributes = [
.foregroundColor: UIColor.white
]
}
}
And my setting navbar in the storyboard:
I found solution for this trouble. UINavigationBar
property translucent should be true, and also bottom and top constraint for tableView in UIViewController should be equal Superview.Top and Superview.Bottom accordingly.
I have been working on a mainly text-based application with an Iadbanner in the bottom.
However as we all know, Iads aren't always there. So I would like to be able to dynamically update the height of my textView, so when the banner is hidden, the textView takes up the wasted space. And resize it when a banner is loaded.
Here's what I currently have for the Viewcontroller in question
import UIKit
import iAd
class DetailVC: UIViewController, UITextViewDelegate, ADBannerViewDelegate {
//Our label for displaying var "Items/cellName"
#IBOutlet var imageViewOutlet: UIImageView!
//connect in IB connection inspector with your ADBannerView
#IBOutlet var adBannerView: ADBannerView!
//Receiving variable assigned to our mainVC var "items"
var cellName: String = ""
var imageView: UIImageView = UIImageView()
var image = UIImage(named: "handcuffs.png")
var textViewText: String = ""
var textView: UITextView = UITextView(frame: CGRect(x: 5.0, y: 238.0, width: 315.00, height: 283.00))
//height = 332 for full screen 283 for small
override func viewDidLoad() {
super.viewDidLoad()
println("+--------------------+")
println("| Detail view loaded |")
println("+--------------------+")
// Iad stuff
self.adBannerView.delegate = self
self.canDisplayBannerAds = true
self.adBannerView.hidden = true //hide until ad loaded
//Setting up the textView
textView.text = textViewText
textView.editable = false
textView.backgroundColor = UIColor.clearColor()
textView.font = UIFont(name: "Helvetica", size: 15)
//adding textview as subview
self.view.addSubview(textView)
//ImageViewOutlets
imageViewOutlet.image = image
//Assign your string var to your navbar title
self.title = cellName
func bannerViewWillLoadAd(banner: ADBannerView!) {
NSLog("bannerViewWillLoadAd")
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
NSLog("bannerViewDidLoadAd")
//self.textView.removeFromSuperview()
//textView = UITextView(frame: CGRect(x: 0.0, y: 238.0, width: 320.00, height: 283.00))
//self.view.addSubview(textView)
self.adBannerView.hidden = false //now show banner as ad is loaded
}
func bannerViewActionDidFinish(banner: ADBannerView!) {
NSLog("bannerViewActionDidFinish")
//optional resume paused app code
}
func bannerViewActionShouldBegin(banner: ADBannerView!, willLeaveApplication willLeave: Bool) -> Bool {
NSLog("bannerViewActionShouldBegin")
//optional pause app code
return true
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
NSLog("didFailToReceiveAdWithError")
}
//... your class implementation code
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The commented out code in the function "bannerViewDidLoadAd" is what I thought would have fixed my issue. Sadly that function seems to never run? I'm not very familiar with Iads so hopefully someone out there can give me a hint as to how to change the height of a textView when an ad loads.
CanDisplayBannerAds :
Set this to enable automatic management of banner ad display with the view controller. It's important to note that this will modify the view hierarchy of the view controller by inserting a new container view above the view controller's view. The impact is that the view controller's view property will no longer return the originally provided view, it will return the new container. To access the original view, use the originalContentView property.
So, remove this line :
self.canDisplayBannerAds = true
Everything should work.