I'm trying to add background image onto my screen and set some constraints, but unfortunately the image view doesn't appear and I don't know what the reason is. I've checked if the image file is in the project folder.
import UIKit
class ViewController: UIViewController {
let backgroundImageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
setBackground()
}
func setBackground() {
view.addSubview(backgroundImageView)
backgroundImageView.translatesAutoresizingMaskIntoConstraints = false
backgroundImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
backgroundImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
backgroundImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
backgroundImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
backgroundImageView.image = UIImage(named: "background-test-image")
}
}
Put your image in the Assets.xcassets folder
I want to present a fullscreen ViewController "A" to cover our loading process across ViewControllers "B" AND "C", or in other words, 1) I present ViewController A from ViewController B, 2) segue from ViewController B to ViewController C while ViewController A is showing, 3) dismiss the ViewController A into ViewController C that ViewController B segued into.
If I push from the presenter ViewController B, the presented ViewController A will disappear as well. So my question is, what's the best way to change the ViewControllers B and C in the background, while another one (ViewController A) is presented on top of them?
Thanks.
You can do this in two ways:
1.Using a navigation controller
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "YourVCName") as? JunctionDetailsVC {
if let navigator = navigationController {
navigator.pushViewController(viewController, animated: false)
}
}
2.Present modally from you initial VC
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "YourVCName") as? LoginVC
{
present(vc, animated: false, completion: nil)
}
Remember to not animate because you said that users shouldn't notice the transition.
Without knowing anything else about your app, I think you'd be better off redesigning the flow and User Experience, but here is one approach to do what you want.
We start with VC-B as the root VC of a UINavigationController
On button-tap, we add a "cover view" to the navigation controller's view hierarchy
We initially position that view below the bottom of the screen
Animate it up into view
Make desired changes to VC-B's view
Instantiate and Push VC-C
Do what's needed to setup the UI on VC-C
Animate the cover view down and off-screen
Remove the cover view from the hierarchy
And here's the code. Everything is done via code - even the initial Nav Controller setup - so No Storyboard needed (go to Project General Settings and delete anything in the Main Interface field).
AppDelegate.swift
//
// AppDelegate.swift
//
// Created by Don Mag on 8/30/19.
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
// instantiate a UINavigationController
let navigationController = UINavigationController();
// instantiate a NavBViewController
let vcB = NavBViewController();
// set the navigation controller's first controller
navigationController.viewControllers = [ vcB ];
self.window?.rootViewController = navigationController;
self.window?.makeKeyAndVisible()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
}
ViewControllers.swift - contains CoverView, NavBViewController and NavCViewController classes
//
// ViewControllers.swift
//
// Created by Don Mag on 8/30/19.
//
import UIKit
class CoverView: UIView {
let theSpinner: UIActivityIndicatorView = {
let v = UIActivityIndicatorView()
v.translatesAutoresizingMaskIntoConstraints = false
v.style = .whiteLarge
return v
}()
let theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
v.textColor = .white
v.text = "Please Wait"
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
backgroundColor = .blue
// add an Activity Spinner and a label
addSubview(theSpinner)
addSubview(theLabel)
NSLayoutConstraint.activate([
theLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
theLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
theSpinner.centerXAnchor.constraint(equalTo: theLabel.centerXAnchor),
theSpinner.bottomAnchor.constraint(equalTo: theLabel.topAnchor, constant: -100.0),
])
theSpinner.startAnimating()
}
}
class NavBViewController: UIViewController {
// this view will be added or removed while the "coverView" is up
let newViewToChange: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .red
v.textColor = .white
v.textAlignment = .center
v.text = "A New View"
return v
}()
let theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
v.text = "View Controller B"
return v
}()
let theButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Tap Me", for: .normal)
v.setTitleColor(.blue, for: .normal)
v.setTitleColor(.lightGray, for: .highlighted)
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
// add a button and a label
view.addSubview(theButton)
view.addSubview(theLabel)
NSLayoutConstraint.activate([
theButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
theLabel.topAnchor.constraint(equalTo: theButton.bottomAnchor, constant: 40.0),
theLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
theButton.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
}
#objc func didTap(_ sender: Any) {
// get
// the neavigation controller's view,
if let navView = navigationController?.view {
// create a "cover view"
let coverView = CoverView()
coverView.translatesAutoresizingMaskIntoConstraints = false
// add the coverView to the neavigation controller's view
navView.addSubview(coverView)
// give it a tag so we can find it from the next view controller
coverView.tag = 9999
// create a constraint with an .identifier so we can get access to it from the next view controller
let startConstraint = coverView.topAnchor.constraint(equalTo: navView.topAnchor, constant: navView.frame.height)
startConstraint.identifier = "CoverConstraint"
// position the coverView so its top is at the bottom (hidden off-screen)
NSLayoutConstraint.activate([
startConstraint,
coverView.heightAnchor.constraint(equalTo: navView.heightAnchor, multiplier: 1.0),
coverView.leadingAnchor.constraint(equalTo: navView.leadingAnchor),
coverView.trailingAnchor.constraint(equalTo: navView.trailingAnchor),
])
// we need to force auto-layout to put the coverView in the proper place
navView.setNeedsLayout()
navView.layoutIfNeeded()
// change the top constraint constant to 0 (top of the neavigation controller's view)
startConstraint.constant = 0
// animate it up
UIView.animate(withDuration: 0.3, animations: ({
navView.layoutIfNeeded()
}), completion: ({ b in
// after animation is complete, we'll change something in this VC's UI
self.doStuff()
}))
}
}
func doStuff() -> Void {
// if newView is already there, remove it
// else, add it to the view
// this will happen *while* the coverView is showing
if newViewToChange.superview != nil {
newViewToChange.removeFromSuperview()
} else {
view.addSubview(newViewToChange)
NSLayoutConstraint.activate([
newViewToChange.bottomAnchor.constraint(equalTo: view.bottomAnchor),
newViewToChange.leadingAnchor.constraint(equalTo: view.leadingAnchor),
newViewToChange.trailingAnchor.constraint(equalTo: view.trailingAnchor),
newViewToChange.heightAnchor.constraint(equalToConstant: 80.0),
])
}
// simulate it taking a full second
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// instantiate and push the next VC
// again, this will happen *while* the coverView is showing
let vc = NavCViewController()
self.navigationController?.pushViewController(vc, animated: false)
}
}
}
class NavCViewController: UIViewController {
let theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.textAlignment = .center
v.text = "View Controller C"
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
// add a label
view.addSubview(theLabel)
NSLayoutConstraint.activate([
theLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
theLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
// do whatever else needed to setup this VC
// simulate it taking 1 second to setup this view
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
// get
// the neavigation controller's view,
// the view with tag 9999 (the "coverView")
// the top constraint of the coverView
if let navView = self.navigationController?.view,
let v = navView.viewWithTag(9999),
let c = (navView.constraints.first { $0.identifier == "CoverConstraint" }) {
// change the top constant of the coverView to the height of the navView
c.constant = navView.frame.height
// animate it "down"
UIView.animate(withDuration: 0.3, animations: ({
navView.layoutIfNeeded()
}), completion: ({ b in
// after animation is complete, remove the coverView
v.removeFromSuperview()
}))
}
}
}
}
When you run it, it will look like this:
Tapping "Tap Me" will slide-up a "cover view" and a new red view will be added (but you won't see it):
The sample has 2-seconds worth of delay, to simulate whatever your app is doing to set up its UI. After 2-seconds, the cover view will slide down:
Revealing the pushed VC-C (confirmed by the Back button on the Nav Bar).
Tapping Back takes you back to VC-B, where you see the new red view that was added:
So, by animating the position of the cover view, we emulate the use of present() and dismiss(), and allow the push to take place behind it.
My FSCalendar's content shrinks when I switch its scope from week to month if there is a view that's constrained to its bottom anchor.
Here is a quick gif to show what exactly is happening
I have tried everything at this point. Using calendar.setScope() instead of calendar.scope =, constraining attachedToCalendarView.topAnchor to calendar.bottomAnchor calendar.contentView.bottomAnchor, and calendar.daysContainer.bottomAnchor, even turning attachedToCalendarView 's constraints on and off depending on whether it's week scope or scope month.
Not sure what else to try. Here is the code:
import UIKit
import FSCalendar
class TestController : UIViewController, FSCalendarDataSource, FSCalendarDelegate, FSCalendarDelegateAppearance {
fileprivate weak var calendar: FSCalendar!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setUp()
}
#objc func switchCalendarScope(){
if self.calendar.scope == FSCalendarScope.month {
self.calendar.scope = FSCalendarScope.week
} else {
self.calendar.scope = FSCalendarScope.month
}
}
func setUp(){
let calendar = FSCalendar()
calendar.dataSource = self
calendar.delegate = self
self.calendar = calendar
self.calendar.scope = .week
self.calendar.locale = Locale(identifier: "en_EN")
self.calendar.calendarHeaderView.calendar.locale = Locale(identifier: "en_EN")
self.calendar.adjustsBoundingRectWhenChangingMonths = true
let testingView = UIView()
testingView.backgroundColor = .red
let attachedToCalendarView = UIView()
attachedToCalendarView.backgroundColor = .blue
view.addSubview(calendar)
view.addSubview(testingView)
view.addSubview(attachedToCalendarView)
self.calendar.translatesAutoresizingMaskIntoConstraints = false
self.calendar.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
self.calendar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
self.calendar.widthAnchor.constraint(equalToConstant: view.bounds.size.width).isActive = true
self.calendar.heightAnchor.constraint(equalToConstant: 300).isActive = true
testingView.translatesAutoresizingMaskIntoConstraints = false
testingView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
testingView.widthAnchor.constraint(equalToConstant: view.bounds.size.width).isActive = true
testingView.heightAnchor.constraint(equalToConstant: 20).isActive = true
attachedToCalendarView.translatesAutoresizingMaskIntoConstraints = false
attachedToCalendarView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
// Attaching this view's topAnchor to the calendar's bottom anchor
attachedToCalendarView.topAnchor.constraint(equalTo: self.calendar.contentView.bottomAnchor).isActive = true
attachedToCalendarView.widthAnchor.constraint(equalToConstant: view.bounds.size.width).isActive = true
attachedToCalendarView.heightAnchor.constraint(equalToConstant: 20).isActive = true
// Title and button to toggle the calendar scope
self.navigationItem.title = "Test"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Toggle", style: .done, target: self, action: #selector(switchCalendarScope))
}
}
Well, I couldn't figure out how to fix the problem itself but I did find a workaround. I placed the calendar inside of an empty container view (just a simple UIView) and attached attachedToCalendarView to the container's bottomAnchor instead of the calendar's itself.
Do note however that using setScope, which animates the transition, still causes the same issue. For it to work you have to set it manually like calendar.scope = x
example:
#objc func switchCalendarScope(){
if self.calendar.scope == FSCalendarScope.month {
// self.calendar.setScope(FSCalendarScope.week, animated: true) // this will cause the calendar to be squished again
self.calendar.scope = .week
movingConstraint.constant = view.safeAreaLayoutGuide.layoutFrame.size.height * -0.20
} else {
// self.calendar.setScope(FSCalendarScope.month, animated: true)
self.calendar.scope = .month
movingConstraint.constant = 0
}
}
I'm not able to display an activity indicator on top a static table view though I wrote the below code in viewDidLoad.
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
tableView.backgroundView = activityIndicatorView
tableView.separatorStyle = UITableViewCellSeparatorStyle.none
self.activityIndicatorView = activityIndicatorView
self.activityIndicatorView.isHidden = false
self.tableView.addSubview(activityIndicatorView)
self.activityIndicatorView.startAnimating()
Instead of adding UIActivityIndicatorView in tableView, add it to inside your main view and call the bringSubview(toFront:) on the main view to to bring the indicatorView to front and you have't set the origin position for your indicatorView set that also.
self.activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
tableView.separatorStyle = UITableViewCellSeparatorStyle.none
self.activityIndicatorView.isHidden = false
//Add inside your view
self.view.addSubview(activityIndicatorView)
//Set activityIndicatorView origin in the screen
self.activityIndicatorView.center = self.view.center
//Try to bring activityIndicatorView to front
self.view.bringSubview(toFront: activityIndicatorView)
self.activityIndicatorView.startAnimating()
You cant add subview to tableview, Try adding it on Navigation controllers view instead.
like :
self.view.superviewaddSubview(activityIndicatorView);
If you use pull to refresh then implement this code:
var refreshControl: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
tableView.addSubview(refreshControl)
}
func refresh(sender:AnyObject) {
tableView.reloadData()
refreshControl.endRefreshing()
}
In the previous version of my view controller (based on storyboard) the search bar was working good.
In the 100% programmatically defined current version (I removed all storyboard references) the search bar has disappeared.
I am trying to understand where the problem lies, without success. Any idea?
Here is the faulty code snippet:
let resultSearchController = UISearchController()
override public func viewDidLoad() {
super.viewDidLoad()
// No nav bar
navigationController?.navigationBar.hidden = true
// — Add table view
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 0).active = true
tableView.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: 0).active = true
tableView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor, constant: 0).active = true
// Add search bar as a the table header
resultSearchController.dimsBackgroundDuringPresentation = false
resultSearchController.definesPresentationContext = true
resultSearchController.hidesNavigationBarDuringPresentation = false
resultSearchController.searchResultsUpdater = self
resultSearchController.searchBar.sizeToFit()
resultSearchController.searchBar.delegate = self
tableView.tableHeaderView = resultSearchController.searchBar
/// Add refresh control
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(BigHeadViewController.refreshTable), forControlEvents: UIControlEvents.ValueChanged)
tableView.addSubview(refreshControl)
}
I think the problem might be the first line. You have to specify the "searchResultsController" as part of the initialisation - even if it's nil (to search in place). Try
let resultSearchController = UISearchController(searchResultsController:nil)