ActivityViewController Popup not on Top of the Screen - swift

I have a share button to share a screenshot of my screen. Although I implemented a extension to get the rootViewController, the popup of my ActivityViewController is still behind the window from which I want to share the screenshot see image attached. I already tried to load my handleDismiss() function inside the handleShare(), but without success so far. Maybe someone can get me on the right track, what is going on here.
Thank you very much in advance.
Short gif of my Statistic View
import UIKit
class GameStatistics: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let blackView = UIView()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
let cellId = "cellId"
let sectionHeader = "sectionHeader"
let sectionFooter = "sectionFooter"
//
//
//
func showStatistics() {
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
// Dynamic Height of Collection View
let value: CGFloat = CGFloat(statistics.count)
let height: CGFloat = value * cellHeight + (value - 1) * cellSpacing + headerHeight + footerHeight
let y = window.frame.height - height
blackView.frame = window.frame
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
blackView.alpha = 0
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}, completion: nil)
}
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return statistics.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! GameStatisticCell
cell.statistic = statistics[indexPath.item]
cell.layoutIfNeeded()
//dump(statistics)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: cellHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return cellSpacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return cellSpacing
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionHeader, for: indexPath)
return supplementaryView
case UICollectionElementKindSectionFooter:
let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: sectionFooter, for: indexPath)
return supplementaryView
default:
fatalError("Unexpected element kind")
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: headerHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: footerHeight)
}
override init() {
super.init()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(GameStatisticCell.self, forCellWithReuseIdentifier: cellId)
collectionView.register(GameStatisticHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: sectionHeader)
collectionView.register(GameStatisticFooter.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: sectionFooter)
}
func handleShare() {
if let window = UIApplication.shared.keyWindow {
let size = CGSize(width: window.frame.width, height: window.frame.height)
UIGraphicsBeginImageContext(size)
window.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let activityViewController = UIActivityViewController(activityItems: [image!], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceRect = CGRect(x: 0, y: 0, width: 0, height: 0)
//UIApplication.shared.keyWindow?.rootViewController?.present(activityViewController, animated: true, completion: nil)
UIApplication.topViewController?.present(activityViewController, animated: true, completion: nil)
}
}
}
Extension
extension UIApplication {
static var topViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
}
}
extension UIViewController {
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else {
return self
}
}
}

Related

How to achieve a dynamic CollectionView Cell Height (Size for item at)?

I have decided start a project with no storyboard for the first time and at the moment I am stuck trying to figuring out how to achieve a proper dynamic cell in my CollectionViewController. Reading some of the solutions here in Stackoverflow I got the point in using a layout.estimatedItemSize but it somehow stops the bouncing effect from the collection view and also in my second cell which is a horizontal scroll view will not work after this implementation.
Here is my code(UICollectionViewController):
class InfoEmpaVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate let cell1 = "cell1"
fileprivate let cell2 = "cell2"
fileprivate let cellID = "cellID"
fileprivate let headerID = "headerID"
fileprivate let padding: CGFloat = 10
//
//
//GET THE DATA FROM:
var empanada: Empanadas!
struct Cells {
static let empanadaStepsCell = "EmpanadaStepsCell"
}
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionViewLayout()
setupCollectionView()
}
//CHANGE COLOR OF STATUS BAR
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
fileprivate func setupCollectionView() {
collectionView.backgroundColor = UIColor(named: "ColorBackground")
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(InfoHeaderVC.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID)
//FirstCELL
collectionView.register(EmpaIngredientsListCell.self, forCellWithReuseIdentifier: cell1)
//SecondCELL
collectionView.register(EmpaStepsCell.self, forCellWithReuseIdentifier: cellID)
}
fileprivate func setupCollectionViewLayout() {
if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = .init(top: padding, left: padding, bottom: padding, right: padding)
layout.estimatedItemSize = CGSize(width: view.frame.width, height: 50)
}
}
var headerView: InfoHeaderVC!
//HEADER COLLECTION VIEW
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? InfoHeaderVC
headerView.empaImageView.image = UIImage(named: empanada.image)
headerView.empaTitleLabel.text = empanada.name
headerView.empaDescriptionLabel.text = empanada.info
headerView.buttonX.addTarget(self, action: #selector(dismissVC), for: .touchUpInside)
headerView.buttonAddFavorite.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
return headerView!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 350)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cell1, for: indexPath)
guard let cellOne = cell as? EmpaIngredientsListCell else {
fatalError("Wrong cell type for section 0. Expected CellTypeOne")
}
//INGREDIENT LIST
cellOne.empaIngredientList.ingredientList.append(contentsOf: empanada.ingredients)
cellOne.empaIngredientList.configure()
return cellOne
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! EmpaStepsCell
cell.pasos.append(contentsOf: empanada.pasos)
cell.titleHeaderLabel.text = "Step by Step"
cell.configure()
print (cell.pasos.count)
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item == 0 {
return .init(width: view.frame.width - 2 * padding, height: 300)
} else {
return .init(width: view.frame.width - 2 * padding, height: 300)
}
}
//OBJC FUNC
#objc func dismissVC() {
dismiss(animated: true)
}
//SAVE DATA
#objc func addButtonTapped() {
configureSaveToFavorites(empanada: empanada!)
}
}
Cell 1:
import UIKit
import SnapKit
class EmpaIngredientsListCell: UICollectionViewCell {
let empaIngredientList = EmpaIngredientsContainerView()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
print(intrinsicContentSize)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
func setupUI() {
//contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(empaIngredientList)
empaIngredientList.snp.makeConstraints { (make) in
make.top.bottom.left.right.equalTo(self.contentView)
make.edges.equalTo(self.safeAreaLayoutGuide)
}
}
}

How do i present a controller from a custom slide up menu

Im trying to present a controller from a custom slide up menu in swift but at the moment when i select an option from the menu it doesn't present the controller.
at first i used apples alert controller which worked fine but i changed it to a custom created menu that slides in from the bottom of the screen that looks like this:
And i use the following class to build this:
class SettingsLauncher: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let blackView = UIView()
let cellHeight = CGFloat(50)
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero,collectionViewLayout: layout)
cv.backgroundColor = UIColor.rgb(red: 35, green: 35, blue: 35, alpha: 1)
return cv
}()
let settings: [Setting] = {
return [
Setting(name: "Profile Settings", imageName: "gear"),
Setting(name: "My Store", imageName: "bag"),
Setting(name: "Search User", imageName: "magnifyingglass"),
Setting(name: "Cancel", imageName: "xmark")
]
}()
override init() {
super.init()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(SettingsCell.self, forCellWithReuseIdentifier: reuseidentifier)
}
func showSettings() {
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor.clear
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
let height: CGFloat = CGFloat(settings.count) * cellHeight + 50
let y = window.frame.height - height
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
collectionView.layer.cornerRadius = 20
blackView.frame = window.frame
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut) {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
} completion: { (nil) in
}
}
}
#objc func handleDismiss(setting: Setting) {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
} completion: { (nil) in
let mainTab = MainTabVC()
mainTab.presentController(setting: setting)
}
}
//MARK: - Collection View
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return settings.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseidentifier, for: indexPath) as! SettingsCell
let setting = settings[indexPath.row]
cell.setting = setting
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width
print(width)
return CGSize(width: width, height: cellHeight)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let setting = self.settings[indexPath.item]
handleDismiss(setting: setting)
}
}
when i select one of the four items the menu is dismissed and a appropriate controller is to be presented but it doesn't work. this is how i have attempted to do it but i don't think it is correct:
So for example when a user selects the "Search User" it is supposed to execute the following:
this function is handled in another class called MainTabVC() which is where all the navbar items are configured and when they are pressed on it calls the above class to present the menu.
func presentController(setting: Setting) {
if setting.name == "Search User" {
let searchVC = SearchVC()
let navigationController = UINavigationController(rootViewController: searchVC)
navigationController.title = "Search User"
self.navigationController?.present(navigationController, animated: true, completion: nil)
} else {
print(setting.name)
}
}
when a user selects an option that is not "Select User" the code is executed and it prints out the name of each setting but why does it not present the controller.
Any help would be helpful thanks.
When you trigger the action with this way
let mainTab = MainTabVC()
mainTab.presentController(setting: setting)
you actually creating a new MainTabVC() which never included in any window and UI scheme.
You can start writing a protocol for communication.
protocol SettingsLauncherDelegate: class {
func settingDidSelected(setting: Setting)
}
Then in SettingsLauncher class
class SettingsLauncher: NSObject {
// add weak delegate variable
weak var delegate: SettingsLauncherDelegate?
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let setting = self.settings[indexPath.item]
delegate?.settingDidSelected(setting: setting)
}
Then go in your target UIViewController class
Conform your view controller to the protocol like this.
class ViewController: UIViewController, SettingsLauncherDelegate{
let settingsLauncher = SettingsLauncher()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
settingsLauncher.delegate = self
}
// this will be triggered when any action is selected
func settingDidSelected(setting: Setting) {
print("selected setting > ", setting)
}
}
Then set the delegate variable of your custom UIView class to self.
This will solve your problem.

UICollectionView Datasource methods do not get called

I am refactoring my ViewControllers and one of them contains a collectionView but now the DataSource is not getting called anymore.
My ViewController:
class CoinPageVC: UIViewController, DependencyInjectionVC, Storyboarded {
lazy var mainView: CoinPageV = {
let v = CoinPageV()
v.collectionView.delegate = self
return v
}()
var coin: Coin!
var selectedBase: String!
var viewContainer: [UIView]!
var collectionViewViewDataSource: CollectionViewCoinPageDatasource?
func injectDependencys(dependency: CoinPageDependency) {
self.coin = dependency.coin
self.selectedBase = dependency.base
self.viewContainer = dependency.views
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionViewViewDataSource = CollectionViewCoinPageDatasource(data: viewContainer)
self.mainView.collectionView.dataSource = self.collectionViewViewDataSource
self.mainView.collectionView.reloadData()
}
}
extension CoinPageVC: SetMainView {}
extension CoinPageVC: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width:CGFloat = collectionView.bounds.width
let height:CGFloat = collectionView.bounds.height
// - (tabBarHeight + menuBar.frame.height + heightNavigationBarTop)
let output = Utility.shared.CGSizeMake(width, height)
return output
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = Int(targetContentOffset.pointee.x / view.frame.width)
let indexPath = IndexPath(item: index, section: 0)
mainView.menuBar.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
}
}
My Datasource class:
class CollectionViewCoinPageDatasource: NSObject, UICollectionViewDataSource {
let data: [UIView]
init(data: [UIView]){
self.data = data
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//let outputCell: UICollectionViewCell
let row = indexPath.item
let outputCell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue, for:indexPath) as! CollectionViewCellView
outputCell.view = data[row]
return outputCell
}
}
My collectionView setup:
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.register(CollectionViewCellView.self, forCellWithReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue)
if let flowLayout = cv.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
cv.backgroundColor = .green
cv.isPagingEnabled = true
//cv.backgroundColor = .blue
return cv
}()
What did I miss?
I set up the datasource and also connect it to the datasource of the collectionView, but the methods do not get called.
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.register(CollectionViewCellView.self, forCellWithReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue)
if let flowLayout = cv.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
cv.backgroundColor = .green
cv.isPagingEnabled = true
//cv.backgroundColor = .blue
cv.delegate = self
return cv
}()

Swift: UICollectionViewCell - Dismiss UICollectionView

Trying to dismiss a slide-in UICollectionView by tapping a cell in the collection view. I Am able to dismiss by tapping outside of the collection view by using a dismiss function. Thanks
Update: Found the solution using a delegate. I have marked parts of the code with the solution steps.
Find my Codes below.
HomeController:
import UIKit
class HomeController: UIViewController, UICollectionViewDelegate , UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
let userInfoCell = "userInfoCell"
let dashboardCell = "dashboardCell"
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .cyan
setupViews()
}
func setupViews() {
view.addSubview(mainCollectionView)
mainCollectionView.dataSource = self
mainCollectionView.delegate = self
mainCollectionView.register(UserInfoCell.self, forCellWithReuseIdentifier: userInfoCell)
mainCollectionView.register(DashboardCell.self, forCellWithReuseIdentifier: dashboardCell)
}
let mainCollectionView: UICollectionView = {
let windowWidth = UIScreen.main.bounds.width
let windowHeight = UIScreen.main.bounds.height
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 16
layout.scrollDirection = .vertical
let mainCV = UICollectionView(frame: .zero, collectionViewLayout: layout)
mainCV.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width - 20, height: UIScreen.main.bounds.height - 20)
mainCV.center = CGPoint(x: windowWidth - (windowWidth / 2), y: windowHeight - (windowHeight / 2))
mainCV.backgroundColor = .red
mainCV.layer.cornerRadius = 25
return mainCV
}()
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 1 {
return 1
}
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: dashboardCell, for: indexPath) as! DashboardCell
cell.plusButton.addTarget(self, action: #selector(plusButtonPressed), for: .touchUpInside)
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: userInfoCell, for: indexPath) as! UserInfoCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.section == 1 {
return CGSize(width: (view.frame.width / 3) - 30 , height: 100)
}
return CGSize(width: (view.frame.width / 2) - 30 , height: 200)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if section == 1 {
return UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
}
return UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
}
#objc func plusButtonPressed() {
print("Plus button pressed")
taggObjectLauncher.showSlideInImages()
}
lazy var taggObjectLauncher: SlideInViewLauncher = {
let launcher = SlideInViewLauncher()
launcher.homeController = self
return launcher
}()
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
class UserInfoCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .blue
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class DashboardCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .green
setupDashboardViews()
}
func setupDashboardViews() {
addSubview(plusButton)
}
let plusButton: UIButton = {
let button = UIButton()
button.backgroundColor = .yellow
button.setTitle("Plus", for: .normal)
button.setTitleColor(.black, for: .normal)
button.setTitleColor(.gray, for: .highlighted)
button.titleLabel?.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
button.frame = CGRect(x: 5, y: 5, width: (UIScreen.main.bounds.width / 3) - 40, height: 90)
return button
}()
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
SlideInViewLauncher Controller:
import UIKit
class SlideInViewLauncher: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var imageCategories: [ImageCategory]?
var homeController = HomeController()
var imageCell = ImageCell()
private let cellIdentifier = "ImageCell"
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
let blackView = UIView()
#objc func showSlideInImages() {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
if let window = UIApplication.shared.keyWindow {
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
let height = CGFloat(450)
let y = window.frame.height - height
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
collectionView.register(SlideInViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
window.addSubview(blackView)
window.addSubview(collectionView)
blackView.frame = window.frame
blackView.alpha = 0
imageCategories = ImageCategory.sampleImageCategories()
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5, animations: {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
})
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selected")
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = imageCategories?.count {
return count
}
return 0
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! SlideInViewCell
cell.imageCategory = imageCategories?[indexPath.row]
///////////////
// Solution: Part 5
cell.cellDelegate = self
///////////////
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width , height: 200)
}
override init() {
super.init()
collectionView.dataSource = self
collectionView.delegate = self
}
}
////////////
// Solution: Part 4
extension SlideInViewLauncher: MyCustomCellDelegate {
func didPressButton() {
UIView.animate(withDuration: 0.5, animations: {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(X: 0, y: window.frame.height, width:
self.collectionView.frame.width, height:
self.collectionView.frame.height)
}
})
}
}
/////////////
SlideInViewCell Controller:
import UIKit
////////////////
//Solution: Part 1
protocol MyCustomCellDelegate: class {
func didPressButton()
}
/////////////
class SlideInViewCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
/////////////////
// Solution: Part 2
weak var cellDelegate: MyCustomDelegate?
////////////////
var homeViewController = HomeController()
var slideInViewLauncher = SlideInViewLauncher()
var imageCategory: ImageCategory? {
didSet {
if let name = imageCategory?.name {
mainCategoryLabel.text = name
}
}
}
var image: Image?
private let cellID = "objectCellID"
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let mainCategoryLabel: UILabel = {
let label = UILabel()
label.text = ""
label.font = UIFont.systemFont(ofSize: 16)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var slideInImagesCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.white
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
let dividerLineView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.4, alpha: 0.4)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
func setupViews() {
backgroundColor = UIColor.clear
addSubview(slideInImagesCollectionView)
addSubview(dividerLineView)
addSubview(mainCategoryLabel)
slideInImagesCollectionView.dataSource = self
slideInImagesCollectionView.delegate = self
slideInImagesCollectionView.register(ImageCell.self, forCellWithReuseIdentifier: cellID)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": mainCategoryLabel]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": dividerLineView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": slideInImagesCollectionView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[mainCategoryLabel(30)][v0][v1(0.5)]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": slideInImagesCollectionView, "v1": dividerLineView, "mainCategoryLabel": mainCategoryLabel]))
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = imageCategory?.images?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! ImageCell
cell.image = imageCategory?.images?[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: frame.height - 30)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(35, 10, 10, 10)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let imageSelected = imageCategory!.images?[indexPath.item]
print(imageSelected?.name ?? "Error: No item at index path")
//////////////////
// Solution: Part 3
cellDelegate?.didPressButton()
//////////////////
}
}
class ImageCell: UICollectionViewCell {
var image: Image? {
didSet {
if let name = image?.name {
nameLabel.text = name
}
if let imageName = image?.imageName {
imageView.image = UIImage(named: imageName)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.layer.masksToBounds = true
return iv
}()
let nameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.numberOfLines = 2
label.textAlignment = .center
return label
}()
func setupViews() {
addSubview(imageView)
addSubview(nameLabel)
imageView.frame = CGRect(x: 0, y: 0, width: frame.width, height: 100)
nameLabel.frame = CGRect(x: 0, y: frame.width + 2, width: frame.width, height: 40)
}
}
Models Controller:
import UIKit
class ImageCategory: NSObject {
var name: String?
var images: [Image]?
static func sampleImageCategories() -> [ImageCategory] {
var someImages = [Image]()
let someImageCategory = ImageCategory()
someImageCategory.name = "Some Category"
let heartImage = Image()
heartImage.name = "Heart"
heartImage.imageName = "heart"
heartImage.category = "Some Category"
someImages.append(heartImage)
someImageCategory.images = someImages
// Return the array of image categories:
return [someImageCategory]
}
}
class Image: NSObject {
var id: NSNumber?
var name: String?
var category: String?
var imageName: String?
}
Was able to solve this by adding a delegate protocol. See comments in code. Also, sorry if my post does not quite follow protocol. This is my first post.
Thanks

scrollToItem horizontal scrolling collectionView crash

I am having an issue with implementing a horizontal scrollToItem collectionView. I have a collectionView (menuBar) that on tap of a cell the page collectionView will horizontally scroll to the appropriate cell. On tap of the menuBar collectionViewCell the app crashes. Not sure where the error is. Thanks in advance!
// controller class
class CurrentUserPlaceDetailsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var titleText: String?
let cellId = "cellId"
// UI elements
lazy var contentCollectionView: UICollectionView = {
let cv = UICollectionView()
cv.backgroundColor = UIColor.red
cv.layer.zPosition = 0
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.masksToBounds = true
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
setupCollectionView()
}
func scrollToMenuIndex(_ menuIndex: Int) {
let indexPath = IndexPath(item: menuIndex, section: 0)
contentCollectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
}
lazy var menuBar: MenuBar = {
let mb = MenuBar()
mb.currentUserPlaceDetailsVC = self
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|[v0(114)]", views: menuBar)
}
func setupCollectionView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
// layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
let contentCollectionView:UICollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
contentCollectionView.dataSource = self
contentCollectionView.delegate = self
// register cells
contentCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
contentCollectionView.backgroundColor = .white
// contentCollectionView.scrollIndicatorInsets = UIEdgeInsets(top:114, left: 10, bottom: 10, right: 10)
self.view.addSubview(contentCollectionView)
_ = contentCollectionView.anchor(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 114, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
contentCollectionView.isPagingEnabled = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.x)
menuBar.horizontalBarLeftAnchorConstraint?.constant = scrollView.contentOffset.x / 4
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
let colors: [UIColor] = [.blue, .red, .yellow, .green]
myCell.backgroundColor = colors[indexPath.item]
return myCell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
// menu bar class
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.headerReferenceSize = .zero
layout.sectionInset = .zero
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .green
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
var currentUserPlaceDetailsVC: CurrentUserPlaceDetailsVC?
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
let selectedIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectedIndexPath, animated: false, scrollPosition: UICollectionViewScrollPosition())
setupHorizontalBar()
}
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = .white
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
horizontalBarView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1/4).isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 4).isActive = true
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.item)
let x = CGFloat(indexPath.item) * frame.width / 4
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
currentUserPlaceDetailsVC?.scrollToMenuIndex(indexPath.item)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.imageView.image = UIImage(named: imageNames[indexPath.item])?.withRenderingMode(.alwaysTemplate)
cell.tintColor = UIColor.rgb(91, green: 14, blue: 13)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width / 4, height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In your comment you said:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'. I believe there is something wrong in my func scrollToMenuIndex()... when I remove that func the app does not crash.
That happens, because you inside your View Controller your collectionView declared as lazy, that means it will be calculated on-demand.
The problem is in initializing your collectionView (reason: 'UICollectionView must be initialized with a non-nil layout parameter')
In your view controller replace UICollectionView initialization with this code:
lazy var contentCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.red
cv.layer.zPosition = 0
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.masksToBounds = true
return cv
}()
P.S. As I wrote in my comment to your question, you have reference cycle. class MenuBar should hold weak reference to your view controller to prevent this.