Segmented control to switch collection views - ios

Unable to load viewControllers.
There is no initial selection in segmented controller
when my view loads I want my firstCVC be by default the first viewController
Here is my parent viewController
class covidVC: UIViewController {
private let segmentedController: UISegmentedControl = {
let labelArr = ["Covid19","Symptoms","WHO","Vaccine"]
let seg = UISegmentedControl(items: labelArr)
seg.translatesAutoresizingMaskIntoConstraints = false
seg.tintColor = .gray
seg.addTarget(self, action:#selector(segAction(_:)), for: .valueChanged)
return seg
}()
func viewLoader(){
addChild(FirstCVC())
addChild(SecondCVC())
addChild(ThirdCVC())
addChild(FourthCVC())
self.view.addSubview(FirstCVC().view)
self.view.addSubview(SecondCVC().view)
self.view.addSubview(ThirdCVC().view)
self.view.addSubview(FourthCVC().view)
FirstCVC().didMove(toParent: self)
SecondCVC().didMove(toParent: self)
ThirdCVC().didMove(toParent: self)
FourthCVC().didMove(toParent: self)
FirstCVC().view.frame = self.view.bounds
SecondCVC().view.frame = self.view.bounds
ThirdCVC().view.frame = self.view.bounds
FourthCVC().view.frame = self.view.bounds
}
#objc func segAction(_ segmentedControll: UISegmentedControl){
FirstCVC().view.isHidden = true
SecondCVC().view.isHidden = true
ThirdCVC().view.isHidden = true
FourthCVC().view.isHidden = true
switch segmentedControll.selectedSegmentIndex {
case 0:
FirstCVC().view.isHidden = false
case 1:
SecondCVC().view.isHidden = false
case 2:
ThirdCVC().view.isHidden = false
case 3:
FourthCVC().view.isHidden = false
default:
FirstCVC().view.isHidden = false
}
}
override func viewDidLayoutSubviews() {
view.addSubview(segmentedController)
segmentedController.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
segmentedController.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
viewLoader()
}
}
All four Child view are collection VC. I want them to be independent VCs
I don't want to relaod collection views as they have different rows & cols
class FirstCVC: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate{
// primary horizantal scrollVIew
private let myCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 365 , height: 300)
layout.sectionInset = UIEdgeInsets(top: 10, left: 5, bottom: 10, right: 5)
let view = UICollectionView(frame: .zero, collectionViewLayout:layout)
view.showsVerticalScrollIndicator = false
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FirstCustomCell", for: indexPath) as! FirstCustomCell
cell.backgroundColor = .red
return cell
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.addSubview(myCollectionView)
myCollectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
myCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
myCollectionView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
myCollectionView.dataSource = self
myCollectionView.delegate = self
myCollectionView.register(FirstCustomCell.self, forCellWithReuseIdentifier: FirstCustomCell.identifier)
}
}
The Image output I'm getting

You create a different instance each line
addChild(FirstCVC())
addChild(SecondCVC())
addChild(ThirdCVC())
addChild(FourthCVC())
self.view.addSubview(FirstCVC().view)
self.view.addSubview(SecondCVC().view)
self.view.addSubview(ThirdCVC().view)
self.view.addSubview(FourthCVC().view)
FirstCVC().didMove(toParent: self)
SecondCVC().didMove(toParent: self)
ThirdCVC().didMove(toParent: self)
FourthCVC().didMove(toParent: self)
FirstCVC().view.frame = self.view.bounds
SecondCVC().view.frame = self.view.bounds
ThirdCVC().view.frame = self.view.bounds
FourthCVC().view.frame = self.view.bounds
while it should be
let vc1 = FirstCVC()
let vc2 = SecondCVC()
let vc3 = ThirdCVC()
let vc4 = FourthCVC()
addChild(vc1)
addChild(vc2)
addChild(vc3)
addChild(vc4)
self.view.addSubview(vc1.view)
self.view.addSubview(vc2.view)
self.view.addSubview(vc3.view)
self.view.addSubview(vc4.view)
vc1.didMove(toParent: self)
vc2.didMove(toParent: self)
vc3.didMove(toParent: self)
vc4.didMove(toParent: self)
vc1.view.frame = self.view.bounds
vc2.view.frame = self.view.bounds
vc3.view.frame = self.view.bounds
vc4.view.frame = self.view.bounds
Tip : Also you better create an extension instead of repeating the 4 lines for each vc

Related

tableView never shows up

I have been at this for some time now. I can not get my tableView to appear. I think it has something to do with the fact that it is being presented as didMove(toParent)
I am trying to create a page that allows you to add a new Card to the profile. Every time I write it programmatically or use storyboard it crashes as it Unexpectedly found nil while implicitly unwrapping an Optional value.
Here is the view Controller that is presenting the Side Menu
import Foundation
import SideMenu
import FirebaseAuth
import UIKit
import CoreLocation
import SwiftUI
class BeginViewController: UIViewController, MenuControllerDelegate, CLLocationManagerDelegate {
private var sideMenu: SideMenuNavigationController?
struct customData {
var title: String
var image: UIImage
}
let data = [
customData(title: "NottingHill", image: #imageLiteral(resourceName: "norali-nayla-SAhImiWmFaw-unsplash")),
customData(title: "Southall", image: #imageLiteral(resourceName: "alistair-macrobert-8wMflrTLm2g-unsplash")),
customData(title: "Tower Hill", image: #imageLiteral(resourceName: "peregrine-communications-0OLnnZWg860-unsplash")),
customData(title: "Mansion House", image: #imageLiteral(resourceName: "adam-birkett-cndNklOnHO4-unsplash")),
customData(title: "Westminster", image: #imageLiteral(resourceName: "simon-mumenthaler-NykjYbCW6Z0-unsplash")),
customData(title: "London Bridge", image: #imageLiteral(resourceName: "hert-niks-CjouXgWrTRk-unsplash"))
]
struct Constants {
static let cornerRadius: CGFloat = 15.0 }
let manager = CLLocationManager()
private let ProfileController = ProfileViewController()
private let MyBookingsController = MyBookingsViewController()
private let WalletController = WalletViewController()
private let FAQController = FAQViewController()
private let SettingsController = SettingsViewController()
#IBOutlet weak var StoreButton: UIButton!
#IBOutlet weak var DeliverButton: UIButton!
#IBOutlet weak var AirportButton: UIButton!
#IBOutlet weak var HotelsButton: UIButton!
fileprivate let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(customCell.self, forCellWithReuseIdentifier: "cell")
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
// buttons
StoreButton.layer.cornerRadius = Constants.cornerRadius
StoreButton.layer.shadowOffset = .zero
StoreButton.layer.shadowOpacity = 0.3
StoreButton.layer.shadowColor = UIColor.black.cgColor
StoreButton.layer.shadowRadius = 5
DeliverButton.layer.cornerRadius = Constants.cornerRadius
DeliverButton.layer.shadowOffset = .zero
DeliverButton.layer.shadowOpacity = 0.3
DeliverButton.layer.shadowColor = UIColor.black.cgColor
DeliverButton.layer.shadowRadius = 5
AirportButton.layer.cornerRadius = Constants.cornerRadius
AirportButton.layer.shadowOffset = .zero
AirportButton.layer.shadowOpacity = 0.3
AirportButton.layer.shadowColor = UIColor.black.cgColor
AirportButton.layer.shadowRadius = 5
HotelsButton.layer.cornerRadius = Constants.cornerRadius
HotelsButton.layer.shadowOffset = .zero
HotelsButton.layer.shadowOpacity = 0.3
HotelsButton.layer.shadowColor = UIColor.black.cgColor
HotelsButton.layer.shadowRadius = 5
//CollectionViewNearbyPlaces
view.addSubview(collectionView)
collectionView.backgroundColor = .clear
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 450).isActive = true
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 25).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
collectionView.heightAnchor.constraint(equalTo: collectionView.widthAnchor, multiplier: 0.5).isActive = true
collectionView.delegate = self
collectionView.dataSource = self
// title
title = "handl"
//background
let menu = MenuController(with: [ "Home", "Profile", "My Bookings",
"Wallet",
"FAQ","Settings"])
menu.delegate = self
sideMenu = SideMenuNavigationController(rootViewController: menu)
sideMenu?.leftSide = true
sideMenu?.setNavigationBarHidden(true, animated: false)
SideMenuManager.default.leftMenuNavigationController = sideMenu
SideMenuManager.default.addPanGestureToPresent(toView: view)
addChildControllers()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
handleNotAuthenticated()
}
private func addChildControllers() {
addChild(ProfileController)
addChild(MyBookingsController)
addChild(WalletController)
addChild(FAQController)
addChild(SettingsController)
view.addSubview(ProfileController.view)
view.addSubview(MyBookingsController.view)
view.addSubview(WalletController.view)
view.addSubview(FAQController.view)
view.addSubview(SettingsController.view)
ProfileController.view.frame = view.bounds
MyBookingsController.view.frame = view.bounds
WalletController.view.frame = view.bounds
FAQController.view.frame = view.bounds
SettingsController.view.frame = view.bounds
ProfileController.didMove(toParent: self)
MyBookingsController.didMove(toParent: self)
WalletController.didMove(toParent: self)
FAQController.didMove(toParent: self)
SettingsController.didMove(toParent: self)
ProfileController.view.isHidden = true
MyBookingsController.view.isHidden = true
WalletController.view.isHidden = true
FAQController.view.isHidden = true
SettingsController.view.isHidden = true
}
#IBAction func SideMenuButton(_ sender: Any) {
present(sideMenu!, animated: true)
}
func didSelectMenuItem(named: String) {
sideMenu?.dismiss(animated: true, completion: { [weak self] in
self?.title = named
if named == "Home" {
self?.ProfileController.view.isHidden = true
self?.MyBookingsController.view.isHidden = true
self?.WalletController.view.isHidden = true
self?.FAQController.view.isHidden = true
self?.SettingsController.view.isHidden = true
}
if named == "Profile" {
self?.ProfileController.view.isHidden = false
self?.MyBookingsController.view.isHidden = true
self?.WalletController.view.isHidden = true
self?.FAQController.view.isHidden = true
self?.SettingsController.view.isHidden = true
}
if named == "My Bookings" {
self?.ProfileController.view.isHidden = true
self?.MyBookingsController.view.isHidden = false
self?.WalletController.view.isHidden = true
self?.FAQController.view.isHidden = true
self?.SettingsController.view.isHidden = true
}
else if named == "Wallet" {
self?.ProfileController.view.isHidden = true
self?.MyBookingsController.view.isHidden = true
self?.WalletController.view.isHidden = false
self?.FAQController.view.isHidden = true
self?.SettingsController.view.isHidden = true
}
else if named == "FAQ" {
self?.ProfileController.view.isHidden = true
self?.MyBookingsController.view.isHidden = true
self?.WalletController.view.isHidden = true
self?.FAQController.view.isHidden = false
self?.SettingsController.view.isHidden = true
}
else if named == "Settings" {
self?.ProfileController.view.isHidden = true
self?.MyBookingsController.view.isHidden = true
self?.WalletController.view.isHidden = true
self?.FAQController.view.isHidden = true
self?.SettingsController.view.isHidden = false
}
})
}
}
extension BeginViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width/2.5, height: collectionView.frame.width/2)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! customCell
cell.data = self.data[indexPath.row]
return cell
}
class customCell: UICollectionViewCell {
var data: customData? {
didSet {
guard let data = data else { return }
bg.image = data.image
}
}
fileprivate let bg: UIImageView = {
let iv = UIImageView()
iv.image = #imageLiteral(resourceName: "adam-birkett-cndNklOnHO4-unsplash")
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.shadowColor = UIColor.black.cgColor
iv.layer.shadowOpacity = 1
iv.layer.shadowOffset = CGSize.zero
iv.layer.shadowRadius = 10
iv.layer.shadowPath = UIBezierPath(rect: iv.bounds).cgPath
iv.layer.shouldRasterize = false
iv.layer.cornerRadius = 10
iv.clipsToBounds = true
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(bg)
bg.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
bg.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
bg.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
bg.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//if user is not logged in show login
private func handleNotAuthenticated() {
//check auth status
if Auth.auth().currentUser == nil {
//show log in screen
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .fullScreen
present(loginVC, animated: false)
}
}
}
and Here is the viewController I am trying to present a tableView on. It comes up with a white screen, but no tableView. Nor are my navigation items showing. Even when written programmatically.
import UIKit
class WalletViewController: UIViewController {
var addNewCard = [String]()
let button = UIButton()
let tableView = UITableView()
// MARK: - Properties
override func viewDidLoad() {
super.viewDidLoad()
addTable()
view.backgroundColor = UIColor(named: "RED")
button.setTitle("Add New Card", for: .normal)
view.addSubview(button)
button.backgroundColor = UIColor(named: "yellow-2")
button.setTitleColor(UIColor(named: "RED"), for: .normal)
button.frame = CGRect(x: 25, y: 700, width: 350, height: 50)
button.layer.cornerRadius = 15
button.addTarget(self, action: #selector(didTapAddButton), for: .touchUpInside)
if !UserDefaults().bool(forKey: "setup") {
UserDefaults().set(true, forKey: "setup")
UserDefaults().set(0, forKey: "count")
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Add New Card", style: .plain, target: self,
action: #selector(didTapAdd))
}
}
func updateCard() {
guard let count = UserDefaults().value(forKey: "count") as? Int else {
return
}
for x in 0..<count {
if let addCard = UserDefaults().value(forKey: "addCard\(x+1)") as? String {
addNewCard.append(addCard)
}
}
}
func addTable() {
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .singleLine
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "addCard")
self.view.addSubview(tableView)
}
#IBAction func didTapAdd() {
let vc = storyboard?.instantiateViewController(withIdentifier: "addCard") as! addCardViewController
vc.update = {
DispatchQueue.main.async {
self.updateCard()
}
}
navigationController?.pushViewController(vc, animated: true)
}
#objc private func didTapAddButton() {
let rootVC = addCardViewController()
let navVC = UINavigationController(rootViewController: rootVC)
navVC.modalPresentationStyle = .fullScreen
present(navVC, animated: true)
}
}
extension WalletViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension WalletViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let add = tableView.dequeueReusableCell(withIdentifier: "addCard", for: indexPath)
add.textLabel?.text = addNewCard[indexPath.row]
return add
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return addNewCard.count
}
}

UIRefreshControl endRefresh jumps when used with Large Title enabled

I'm trying to use UIRefreshControl, but when I call endRefreshing() it jumps the UINavigationBar. The problem only happens when I use UIRefreshControl along with large titles.
Looking at some similar issues (UIRefreshControl glitching in combination with custom TableViewCell) reported here, I tried to refresh only after dragging ends, nevertheless, the bug still occurs. Also tried to use
self.navigationController?.navigationBar.isTranslucent = false and self.extendedLayoutIncludesOpaqueBars = true
But, none of the solutions found on other questions seems to resolve the problem, it still not smooth.
The video of what is happening:
https://www.youtube.com/watch?v=2BBRnZ444bE
The app delegate
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
window.makeKeyAndVisible()
let nav = UINavigationController()
nav.title = "My Nav"
nav.navigationBar.prefersLargeTitles = true
nav.viewControllers = [ViewController()]
window.rootViewController = nav
self.window = window
return true
}
}
Observe that I'm using large titles:
let nav = UINavigationController()
nav.title = "My Nav"
nav.navigationBar.prefersLargeTitles = true
The ViewController:
import UIKit
import Foundation
final class ViewController: UICollectionViewController {
let randomHeight = Int.random(in: 100..<300)
init() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.estimatedItemSize = CGSize(width: 20, height: 20)
super.init(collectionViewLayout: layout)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Try to refresh"
self.navigationController?.navigationBar.isTranslucent = false
self.extendedLayoutIncludesOpaqueBars = true
collectionView.backgroundColor = .white
registerCells()
setupRefreshControl()
}
private func registerCells() {
self.collectionView.register(
Cell.self,
forCellWithReuseIdentifier: "Cell"
)
}
private func setupRefreshControl() {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(
self,
action: #selector(refreshControlDidFire),
for: .valueChanged
)
self.collectionView.refreshControl = refreshControl
}
#objc private func refreshControlDidFire(_ sender: Any?) {
if let sender = sender as? UIRefreshControl, sender.isRefreshing {
refresh()
}
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if collectionView.refreshControl!.isRefreshing {
refresh()
}
}
private func refresh() {
if !collectionView.isDragging {
collectionView.refreshControl!.endRefreshing()
collectionView.perform(#selector(collectionView.reloadData), with: nil, afterDelay: 0.05)
}
}
}
extension ViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(
_ collectionView: UICollectionView,
numberOfItemsInSection section: Int
) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "Cell", for: indexPath
) as? Cell else {
return UICollectionViewCell()
}
cell.label.text = "Text number \(indexPath.row), with height \(randomHeight)"
cell.heightAnchorConstraint.constant = CGFloat(randomHeight)
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
}
}
final class Cell: UICollectionViewCell {
private let shadowView = UIView()
private let containerView = UIView()
private let content = UIView()
let label = UILabel()
var heightAnchorConstraint: NSLayoutConstraint!
override init(frame: CGRect = .zero) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
insertSubview(shadowView, at: 0)
addSubview(containerView)
containerView.addSubview(label)
containerView.addSubview(content)
activateConstraints()
}
private func activateConstraints() {
self.translatesAutoresizingMaskIntoConstraints = false
shadowView.translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false
label.translatesAutoresizingMaskIntoConstraints = false
content.translatesAutoresizingMaskIntoConstraints = false
shadowView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
shadowView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
shadowView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
shadowView.bottomAnchor
.constraint(equalTo: self.bottomAnchor).isActive = true
containerView.backgroundColor = .white
containerView.layer.cornerRadius = 14
containerView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
let widthAnchorConstraint = containerView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20)
widthAnchorConstraint.identifier = "Width ContainerView"
widthAnchorConstraint.priority = .defaultHigh
widthAnchorConstraint.isActive = true
label.numberOfLines = 0
label.textAlignment = .center
label.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
content.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20).isActive = true
content.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10).isActive = true
content.bottomAnchor.constraint(lessThanOrEqualTo: containerView.bottomAnchor, constant: -10).isActive = true
heightAnchorConstraint = content.heightAnchor.constraint(greaterThanOrEqualToConstant: 220)
heightAnchorConstraint.identifier = "Height Content"
heightAnchorConstraint.priority = .defaultHigh
heightAnchorConstraint.isActive = true
content.widthAnchor.constraint(equalToConstant: 40).isActive = true
content.backgroundColor = .red
}
override func layoutSubviews() {
super.layoutSubviews()
applyShadow(width: 0.20, height: -0.064)
}
private func applyShadow(width: CGFloat, height: CGFloat) {
let shadowPath = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: 14.0)
shadowView.layer.masksToBounds = false
shadowView.layer.shadowRadius = 8.0
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: width, height: height)
shadowView.layer.shadowOpacity = 0.3
shadowView.layer.shadowPath = shadowPath.cgPath
}
}
The problem is related to layout.estimatedItemSize = CGSize(width: 20, height: 20)
When we use AutoLayout to resize the cell, it creates a bug with UIRefreshControl and navigation bar large title. So, if you use layout.estimatedItemSize with an equal or greater size than we expected. So the bug will not happen and the glitch will not happen.
Basically, the problem is when we call updateData but the cell is bigger than we expect and each cell of the UICollectinView will resize to a bigger size then the UICollectionViewController will glitches.
So, I try your code and all works fine.
But I ran it on iPhone 7 - no large title there.
I think, it is largeTitle issue.
You can try use this code snippet:
self.navigationController?.navigationBar.prefersLargeTitles = false
self.navigationController?.navigationBar.prefersLargeTitles = true

UIButton does not function in collectionView cell

I am trying to create this collection view cells with kinda paging behavior. In every cell; I made a card flip animation. But flip function does not work. I know that function is OK because; before I create these UICollectionViewCell model, I tried everything in UICollectionView itself and it worked out perfect. But since I need paging behavior, I need multiple pages with card view inside and in every cell when user tabs the card it should flip. So, after I migrated all the code from CollectionView to CollectionViewCell; the card view stopped flipping. Even print(...) statement does not return in Xcode. So I guess Xcode doesn't sense user touch. Anyway, I am so junior so I ll be appreciated if someone solves this out. Here is the code for my collectionView:
import UIKit
class AK11ViewController: AltKategoriViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate {
var ak11cv: UICollectionView!
private let ak11CellId = "ak11CellId"
let image1Names = ["bear_first", "heart_second", "leaf_third"]
override func viewDidLoad() {
super.viewDidLoad()
background()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return image1Names.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = ak11cv.dequeueReusableCell(withReuseIdentifier: ak11CellId, for: indexPath) as! AK11PageCell
//cell.translatesAutoresizingMaskIntoConstraints = false
let image1Name = image1Names[indexPath.item]
cell.cardView1.image = UIImage(named: image1Name)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
private func background() {
tabBarController?.tabBar.tintColor = UIColor.white
tabBarController?.tabBar.isTranslucent = false
navigationController?.navigationBar.prefersLargeTitles = false
navigationController?.navigationBar.isTranslucent = true
let ak11Layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
ak11Layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
ak11Layout.scrollDirection = .horizontal
ak11cv = UICollectionView(frame: self.view.frame, collectionViewLayout: ak11Layout)
ak11cv.dataSource = self
ak11cv.delegate = self
ak11cv.isPagingEnabled = true
ak11cv.isUserInteractionEnabled = true
ak11cv.backgroundColor = UIColor.brown
ak11cv.register(AK11PageCell.self, forCellWithReuseIdentifier: ak11CellId)
view.addSubview(ak11cv)
}
}
And here is the code for my collectionViewCell:
import UIKit
class AK11PageCell: UICollectionViewCell {
let mainCardView: UIView = {
let mcv = UIView()
mcv.translatesAutoresizingMaskIntoConstraints = false
mcv.isUserInteractionEnabled = true
return mcv
}()
let frontContainerView: UIView = {
let fcv = UIView()
fcv.translatesAutoresizingMaskIntoConstraints = false
fcv.backgroundColor = .blue
fcv.isUserInteractionEnabled = true
return fcv
}()
let backContainerView: UIView = {
let bcv = UIView()
bcv.translatesAutoresizingMaskIntoConstraints = false
bcv.backgroundColor = .purple
bcv.isUserInteractionEnabled = true
//bcv.isHidden = true
return bcv
}()
let pageControlContainerView: UIView = {
let pcv = UIView()
pcv.translatesAutoresizingMaskIntoConstraints = false
pcv.backgroundColor = .green
pcv.isUserInteractionEnabled = false
return pcv
}()
var cardView1: UIImageView = {
let cv1 = UIImageView()
cv1.translatesAutoresizingMaskIntoConstraints = false
cv1.contentMode = .scaleAspectFit
cv1.isUserInteractionEnabled = true
cv1.image = UIImage(named: "bear_first")
return cv1
}()
var cardView2: UIImageView = {
let cv2 = UIImageView()
cv2.translatesAutoresizingMaskIntoConstraints = false
cv2.contentMode = .scaleAspectFit
cv2.isUserInteractionEnabled = true
cv2.image = UIImage(named: "heart_second")
return cv2
}()
let flipToBack: UIButton = {
let ftb = UIButton(type: .system)
ftb.isUserInteractionEnabled = true
ftb.translatesAutoresizingMaskIntoConstraints = false
ftb.addTarget(self, action: #selector(flip), for: .touchUpInside)
return ftb
}()
let flipToFront: UIButton = {
let ftf = UIButton(type: .system)
ftf.isUserInteractionEnabled = true
ftf.translatesAutoresizingMaskIntoConstraints = false
ftf.addTarget(self, action: #selector(flip), for: .touchUpInside)
return ftf
}()
var flippedCard = false
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.cyan
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setupViews() {
addSubview(mainCardView)
mainCardView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: frame.width * 0.1).isActive = true
mainCardView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: frame.width * -0.1).isActive = true
mainCardView.topAnchor.constraint(equalTo: topAnchor, constant: frame.height * 0.05).isActive = true
mainCardView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: frame.height * -0.25).isActive = true
mainCardView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
mainCardView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
mainCardView.addSubview(frontContainerView)
frontContainerView.leadingAnchor.constraint(equalTo: mainCardView.leadingAnchor).isActive = true
frontContainerView.trailingAnchor.constraint(equalTo: mainCardView.trailingAnchor).isActive = true
frontContainerView.topAnchor.constraint(equalTo: mainCardView.topAnchor).isActive = true
frontContainerView.bottomAnchor.constraint(equalTo: mainCardView.bottomAnchor).isActive = true
frontContainerView.centerXAnchor.constraint(equalTo: mainCardView.centerXAnchor).isActive = true
frontContainerView.centerYAnchor.constraint(equalTo: mainCardView.centerYAnchor).isActive = true
mainCardView.addSubview(backContainerView)
backContainerView.leadingAnchor.constraint(equalTo: mainCardView.leadingAnchor).isActive = true
backContainerView.trailingAnchor.constraint(equalTo: mainCardView.trailingAnchor).isActive = true
backContainerView.topAnchor.constraint(equalTo: mainCardView.topAnchor).isActive = true
backContainerView.bottomAnchor.constraint(equalTo: mainCardView.bottomAnchor).isActive = true
backContainerView.centerXAnchor.constraint(equalTo: mainCardView.centerXAnchor).isActive = true
backContainerView.centerYAnchor.constraint(equalTo: mainCardView.centerYAnchor).isActive = true
frontContainerView.addSubview(cardView1)
cardView1.centerXAnchor.constraint(equalTo: frontContainerView.centerXAnchor).isActive = true
cardView1.centerYAnchor.constraint(equalTo: frontContainerView.centerYAnchor).isActive = true
cardView1.leadingAnchor.constraint(equalTo: frontContainerView.leadingAnchor).isActive = true
cardView1.trailingAnchor.constraint(equalTo: frontContainerView.trailingAnchor).isActive = true
cardView1.topAnchor.constraint(equalTo: frontContainerView.topAnchor).isActive = true
cardView1.bottomAnchor.constraint(equalTo: frontContainerView.bottomAnchor).isActive = true
frontContainerView.addSubview(flipToBack)
flipToBack.centerXAnchor.constraint(equalTo: frontContainerView.centerXAnchor).isActive = true
flipToBack.centerYAnchor.constraint(equalTo: frontContainerView.centerYAnchor).isActive = true
flipToBack.leadingAnchor.constraint(equalTo: frontContainerView.leadingAnchor).isActive = true
flipToBack.trailingAnchor.constraint(equalTo: frontContainerView.trailingAnchor).isActive = true
flipToBack.topAnchor.constraint(equalTo: frontContainerView.topAnchor).isActive = true
flipToBack.bottomAnchor.constraint(equalTo: frontContainerView.bottomAnchor).isActive = true
backContainerView.addSubview(cardView2)
cardView2.centerXAnchor.constraint(equalTo: backContainerView.centerXAnchor).isActive = true
cardView2.centerYAnchor.constraint(equalTo: backContainerView.centerYAnchor).isActive = true
cardView2.leadingAnchor.constraint(equalTo: backContainerView.leadingAnchor).isActive = true
cardView2.trailingAnchor.constraint(equalTo: backContainerView.trailingAnchor).isActive = true
cardView2.topAnchor.constraint(equalTo: backContainerView.topAnchor).isActive = true
cardView2.bottomAnchor.constraint(equalTo: backContainerView.bottomAnchor).isActive = true
backContainerView.addSubview(flipToFront)
flipToFront.centerXAnchor.constraint(equalTo: backContainerView.centerXAnchor).isActive = true
flipToFront.centerYAnchor.constraint(equalTo: backContainerView.centerYAnchor).isActive = true
flipToFront.leadingAnchor.constraint(equalTo: backContainerView.leadingAnchor).isActive = true
flipToFront.trailingAnchor.constraint(equalTo: backContainerView.trailingAnchor).isActive = true
flipToFront.topAnchor.constraint(equalTo: backContainerView.topAnchor).isActive = true
flipToFront.bottomAnchor.constraint(equalTo: backContainerView.bottomAnchor).isActive = true
addSubview(pageControlContainerView)
pageControlContainerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
pageControlContainerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
pageControlContainerView.bottomAnchor.constraint(equalTo: mainCardView.bottomAnchor, constant: 20).isActive = true
pageControlContainerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.075).isActive = true
}
#objc func flip() {
print("tabbed")
flippedCard = !flippedCard
let fromView = flippedCard ? backContainerView : frontContainerView
let toView = flippedCard ? frontContainerView : backContainerView
UIView.transition(from: fromView, to: toView, duration: 0.5, options: [.transitionFlipFromRight, .showHideTransitionViews])
}
}
Please ignore my data model because I did not set it yet; I just want my mainCardView, which is super view for frontCardView and backCardView, to flip. But any suggestions for other stuff are also welcomed.
Thank you!
I believe, to get your button action / target to register correctly, you need to declare them as lazy:
lazy var flipToBack: UIButton = {
lazy var flipToFront: UIButton = {
That should solve the tap issue.

Delegate Function Not Being Called

So I am trying to use protocols and delegates to connect two functions so I can perform some operation on a variable a collectionView in this case in a different file.
import Foundation
import UIKit
protocol EventCollectionCellDelegate: NSObjectProtocol {
func setupCollectionView(for eventCollectionView: UICollectionView?)
}
class EventCollectionCell:UICollectionViewCell {
weak var delegate: EventCollectionCellDelegate?
var eventArray = [EventDetails](){
didSet{
self.eventCollectionView.reloadData()
}
}
var enentDetails:Friend?{
didSet{
var name = "N/A"
var total = 0
seperator.isHidden = true
if let value = enentDetails?.friendName{
name = value
}
if let value = enentDetails?.events{
total = value.count
self.eventArray = value
seperator.isHidden = false
}
if let value = enentDetails?.imageUrl{
profileImageView.loadImage(urlString: value)
}else{
profileImageView.image = #imageLiteral(resourceName: "Tokyo")
}
self.eventCollectionView.reloadData()
setLabel(name: name, totalEvents: total)
}
}
let container:UIView={
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 16
view.layer.borderColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.3
return view
}()
//profile image view for the user
var profileImageView:CustomImageView={
let iv = CustomImageView()
iv.layer.masksToBounds = true
iv.layer.borderColor = UIColor.lightGray.cgColor
iv.layer.borderWidth = 0.3
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
//will show the name of the user as well as the total number of events he is attending
let labelNameAndTotalEvents:UILabel={
let label = UILabel()
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
let seperator:UIView={
let view = UIView()
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
//collectionview that contains all of the events a specific user will be attensing
let flow = UICollectionViewFlowLayout()
lazy var eventCollectionView = UICollectionView(frame: .zero, collectionViewLayout: flow)
// var eventCollectionView:UICollectionView?
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpCell()
self.setupCollectionView(for: eventCollectionView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupCollectionView(for eventCollectionView: UICollectionView?){
delegate?.setupCollectionView(for: eventCollectionView)
}
}
This is the file that creates a collectionViewCell with a collectionView in it. I am trying to perform some operation on that collectionView using the delegate pattern. My problem is that the delegate function is never called in the accompanying viewController. I feel like I have done everything right but nothing happens in the accompanying vc. Anyone notice what could possibly be wrong.
I have shown the code for the VC below
class FriendsEventsView: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,EventCollectionCellDelegate {
var friends = [Friend]()
var followingUsers = [String]()
var height:CGFloat = 0
var notExpandedHeight : CGFloat = 50
var isExpanded = [Bool]()
//so this is the main collectonview that encompasses the entire view
lazy var mainCollectionView:UICollectionView={
// the flow layout which is needed when you create any collection view
let flow = UICollectionViewFlowLayout()
//setting the scroll direction
flow.scrollDirection = .vertical
//setting space between elements
let spacingbw:CGFloat = 5
flow.minimumLineSpacing = spacingbw
flow.minimumInteritemSpacing = 0
//actually creating collectionview
let cv = UICollectionView(frame: .zero, collectionViewLayout: flow)
//register a cell for that collectionview
cv.register(EventCollectionCell.self, forCellWithReuseIdentifier: "events")
cv.translatesAutoresizingMaskIntoConstraints = false
//changing background color
cv.backgroundColor = .white
//sets the delegate of the collectionView to self. By doing this all messages in regards to the collectionView will be sent to the collectionView or you.
//"Delegates send messages"
cv.delegate = self
//sets the datsource of the collectionView to you so you can control where the data gets pulled from
cv.dataSource = self
//sets positon of collectionview in regards to the regular view
cv.contentInset = UIEdgeInsetsMake(spacingbw, 0, spacingbw, 0)
return cv
}()
lazy var eventCollectionView:UICollectionView={
let flow = UICollectionViewFlowLayout()
flow.scrollDirection = .vertical
let spacingbw:CGFloat = 5
flow.minimumLineSpacing = 0
flow.minimumInteritemSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: flow)
//will register the eventdetailcell
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
cv.register(EventDetailsCell.self, forCellWithReuseIdentifier: "eventDetails")
cv.delegate = self
cv.dataSource = self
cv.contentInset = UIEdgeInsetsMake(spacingbw, 0, spacingbw, 0)
cv.showsVerticalScrollIndicator = false
cv.bounces = false
return cv
}()
func setupCollectionView(for eventCollectionView: UICollectionView?) {
print("Attempting to create collectonView")
eventCollectionView?.backgroundColor = .blue
}
//label that will be displayed if there are no events
let labelNotEvents:UILabel={
let label = UILabel()
label.textColor = .lightGray
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = UIFont.italicSystemFont(ofSize: 14)
label.text = "No events found"
label.isHidden = true
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
//will set up all the views in the screen
self.setUpViews()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
}
func setUpViews(){
//well set the navbar title to Friends Events
self.title = "Friends Events"
view.backgroundColor = .white
//adds the main collection view to the view and adds proper constraints for positioning
view.addSubview(mainCollectionView)
mainCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
mainCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
mainCollectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
mainCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
//adds the label to alert someone that there are no events to the collectionview and adds proper constrains for positioning
mainCollectionView.addSubview(labelNotEvents)
labelNotEvents.centerYAnchor.constraint(equalTo: mainCollectionView.centerYAnchor, constant: 0).isActive = true
labelNotEvents.centerXAnchor.constraint(equalTo: mainCollectionView.centerXAnchor, constant: 0).isActive = true
//will fetch events from server
self.fetchEventsFromServer()
}
// MARK: CollectionView Datasource for maincollection view
//woll let us know how many cells are being displayed
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(friends.count)
isExpanded = Array(repeating: false, count: friends.count)
return friends.count
}
//will control the size of the cell that is displayed in the containerview
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
height = 100
let event = friends[indexPath.item]
if let count = event.events?.count,count != 0{
height += (CGFloat(count*40)+10)
}
return CGSize(width: collectionView.frame.width, height: height)
}
//will do the job of effieicently creating cells for the eventcollectioncell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "events", for: indexPath) as! EventCollectionCell
cell.delegate = self
cell.enentDetails = friends[indexPath.item]
cell.eventCollectionView = eventCollectionView
return cell
}
}
I have cut the code down to what I believe is needed to answer the question for simplicity. Any help is appreciated
You set delegate after setupCollectionView. In your case, you can't call setupCollectionView before you set delegate, because setupCollectionView called in init

state restoration working but then nullified in viewDidLoad

Note code has been updated to incorporate the fixes detailed in the comments, but here is the original question text:
State restoration works on the code-based ViewController below, but then it is "undone" by a second call to viewDidLoad. My question is: how do I avoid that?
With a breakpoint at decodeRestorableState I can see that it does in fact restore the 2 parameters selectedGroup and selectedType but then it goes through viewDidLoad again and those parameters are reset to nil so the restoration is of no effect. There's no storyboard: if you associated this class with an empty ViewController it will work (I double checked this -- there are some button assets too, but they aren't needed for function). I've also included at the bottom the AppDelegate methods needed to enable state restoration.
import UIKit
class CodeStackVC2: UIViewController, FoodCellDel {
let fruit = ["Apple", "Orange", "Plum", "Qiwi", "Banana"]
let veg = ["Lettuce", "Carrot", "Celery", "Onion", "Brocolli"]
let meat = ["Beef", "Chicken", "Ham", "Lamb"]
let bread = ["Wheat", "Muffin", "Rye", "Pita"]
var foods = [[String]]()
let group = ["Fruit","Vegetable","Meat","Bread"]
var sView = UIStackView()
let cellId = "cellId"
var selectedGroup : Int?
var selectedType : Int?
override func viewDidLoad() {
super.viewDidLoad()
restorationIdentifier = "CodeStackVC2"
foods = [fruit, veg, meat, bread]
setupViews()
displaySelections()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let index = selectedGroup, let type = selectedType else { return }
pageControl.currentPage = index
let indexPath = IndexPath(item: index, section: 0)
cView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
cView.reloadItems(at: [indexPath])
guard let cell = cView.cellForItem(at: indexPath) as? FoodCell else { return }
cell.pickerView.selectRow(type, inComponent: 0, animated: true)
}
//State restoration encodes parameters in this func
override func encodeRestorableState(with coder: NSCoder) {
if let theGroup = selectedGroup,
let theType = selectedType {
coder.encode(theGroup, forKey: "theGroup")
coder.encode(theType, forKey: "theType")
}
super.encodeRestorableState(with: coder)
}
override func decodeRestorableState(with coder: NSCoder) {
selectedGroup = coder.decodeInteger(forKey: "theGroup")
selectedType = coder.decodeInteger(forKey: "theType")
super.decodeRestorableState(with: coder)
}
override func applicationFinishedRestoringState() {
//displaySelections()
}
//MARK: Views
lazy var cView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
layout.itemSize = CGSize(width: self.view.frame.width, height: 120)
let cRect = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 120)
let cv = UICollectionView(frame: cRect, collectionViewLayout: layout)
cv.backgroundColor = UIColor.lightGray
cv.isPagingEnabled = true
cv.dataSource = self
cv.delegate = self
cv.isUserInteractionEnabled = true
return cv
}()
lazy var pageControl: UIPageControl = {
let pageC = UIPageControl()
pageC.numberOfPages = self.foods.count
pageC.pageIndicatorTintColor = UIColor.darkGray
pageC.currentPageIndicatorTintColor = UIColor.white
pageC.backgroundColor = .black
pageC.addTarget(self, action: #selector(changePage(sender:)), for: UIControlEvents.valueChanged)
return pageC
}()
var textView: UITextView = {
let tView = UITextView()
tView.font = UIFont.systemFont(ofSize: 40)
tView.textColor = .white
tView.backgroundColor = UIColor.lightGray
return tView
}()
func makeButton(_ tag:Int) -> UIButton{
let newButton = UIButton(type: .system)
let img = UIImage(named: group[tag])?.withRenderingMode(.alwaysTemplate)
newButton.setImage(img, for: .normal)
newButton.tag = tag // used in handleButton()
newButton.contentMode = .scaleAspectFit
newButton.addTarget(self, action: #selector(handleButton(sender:)), for: .touchUpInside)
newButton.isUserInteractionEnabled = true
newButton.backgroundColor = .clear
return newButton
}
//Make a 4-item vertical stackView containing
//cView,pageView,subStackof 4-item horiz buttons, textView
func setupViews(){
view.backgroundColor = .lightGray
cView.register(FoodCell.self, forCellWithReuseIdentifier: cellId)
//generate an array of buttons
var buttons = [UIButton]()
for i in 0...foods.count-1 {
buttons += [makeButton(i)]
}
let subStackView = UIStackView(arrangedSubviews: buttons)
subStackView.axis = .horizontal
subStackView.distribution = .fillEqually
subStackView.alignment = .center
subStackView.spacing = 20
//set up the stackView
let stackView = UIStackView(arrangedSubviews: [cView,pageControl,subStackView,textView])
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 5
//Add the stackView using AutoLayout
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 5).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
cView.translatesAutoresizingMaskIntoConstraints = false
textView.translatesAutoresizingMaskIntoConstraints = false
cView.heightAnchor.constraint(equalTo: textView.heightAnchor, multiplier: 0.5).isActive = true
}
// selected item returned from pickerView
func pickerSelection(_ foodType: Int) {
selectedType = foodType
displaySelections()
}
func displaySelections() {
if let theGroup = selectedGroup,
let theType = selectedType {
textView.text = "\n \n Group: \(group[theGroup]) \n \n FoodType: \(foods[theGroup][theType])"
}
}
// 3 User Actions: Button, Page, Scroll
func handleButton(sender: UIButton) {
pageControl.currentPage = sender.tag
let x = CGFloat(sender.tag) * cView.frame.size.width
cView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * cView.frame.size.width
cView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let index = Int(cView.contentOffset.x / view.bounds.width)
pageControl.currentPage = Int(index) //change PageControl indicator
selectedGroup = Int(index)
let indexPath = IndexPath(item: index, section: 0)
guard let cell = cView.cellForItem(at: indexPath) as? FoodCell else { return }
selectedType = cell.pickerView.selectedRow(inComponent: 0)
displaySelections()
}
//this causes cView to be recalculated when device rotates
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
cView.collectionViewLayout.invalidateLayout()
}
}
//MARK: cView extension
extension CodeStackVC2: UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return foods.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! FoodCell
cell.foodType = foods[indexPath.item]
cell.delegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: textView.frame.height * 0.4)
}
}
// *********************
protocol FoodCellDel {
func pickerSelection(_ food:Int)
}
class FoodCell:UICollectionViewCell, UIPickerViewDelegate, UIPickerViewDataSource {
var delegate: FoodCellDel?
var foodType: [String]? {
didSet {
pickerView.reloadComponent(0)
//pickerView.selectRow(0, inComponent: 0, animated: true)
}
}
lazy var pickerView: UIPickerView = {
let pView = UIPickerView()
pView.frame = CGRect(x:0,y:0,width:Int(pView.bounds.width), height:Int(pView.bounds.height))
pView.delegate = self
pView.dataSource = self
pView.backgroundColor = .lightGray
return pView
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
backgroundColor = .clear
addSubview(pickerView)
addConstraintsWithFormat("H:|[v0]|", views: pickerView)
addConstraintsWithFormat("V:|[v0]|", views: pickerView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if let count = foodType?.count {
return count
} else {
return 0
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let pickerLabel = UILabel()
pickerLabel.font = UIFont.systemFont(ofSize: 15)
pickerLabel.textAlignment = .center
pickerLabel.adjustsFontSizeToFitWidth = true
if let foodItem = foodType?[row] {
pickerLabel.text = foodItem
pickerLabel.textColor = .white
return pickerLabel
} else {
print("chap = nil in viewForRow")
return UIView()
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if let actualDelegate = delegate {
actualDelegate.pickerSelection(row)
}
}
}
extension UIView {
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
Here are the functions in AppDelegate:
//====if set true, these 2 funcs enable state restoration
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
//replace the storyboard by making our own window
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
//this defines the entry point for our app
window?.rootViewController = CodeStackVC2()
return true
}
If viewDidLoad is being called twice it will because your view controller is being created twice.
You do not say how you are creating the view controller but I suspect your problem is that the view controller is being created first by a storyboard or in the app delegate and then a second time because you have set a restoration class.
You only need to set a restoration class if your view controller is not being created by the normal app load sequence (a restoration identifier is enough otherwise). Try removing the line in viewDidLoad where you set a restoration class and I think you will see viewDidLoad is called once followed by decodeRestorableState.
Update: Confirmed you are creating the view controller in the app delegate so you do not need to use a restoration class. That fixes the problem with viewDidLoad being called twice.
You want to do the initial root view controller creation in willFinishLaunchingWithOptions in the app delegate as that is called before state restoration takes place.
The final issue once you have the selectedGroup and selectedType values restored is to update the UI elements (page control, collection view), etc to use the restored values
In my six years of iOS programming, I don't remember ever seeing iOS calling viewDidLoad() twice on the same view controller. So it is most likely that you are instantiating CodeStackVC2 twice :)
As far as I can tell, you are creating the view hierarchy programmatically in didFinishLaunchingWithOptions. However, state restoration is invoked before this delegate method is called. So, iOS asks the view controller's restoration class for a new view controller instance, and after that your code setting up the base hierarchy is executed, creating a fresh view controller.
Try moving your code from didFinishLaunchingWithOptions to willFinishLaunchingWithOptions: (which is called before any state restoration). Then, since the view controller that iOS is trying to restore already exists, it won't call that method with the long name from the UIViewControllerRestoration protocol, and instead call decodeRestorableState(with coder:) on that view controller.
If you need a more in-depth explanation, try useyourloaf or of course the Apple docs - I have found both to be very useful in understanding the concepts behind Apple's implementation. Although I must admit, I took me several reads before I understood it myself.

Resources