Im trying to create multiple slide up menus in swift but i don't want to have to keep duplicating classes and only changing one thing so can i pass in an array of the options that need to be displayed on the menu through a constructor or do i have to use different classes?
For example to create the slide up menu i use the following class:
where the array of Settings is what i want to be displayed on the menu and that is the only difference in all my classes.
So is there a way that i can pass in the array when i call the class so that i can use one class for all menus or do i have to duplicate the class and have the array of settings in each class?
private var reuseidentifier = "cellId"
class SettingsLauncher: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
weak var delegate: SettingsLauncherDelegate?
let blackView = UIView()
let headerId = "headerId"
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: "Settings", imageName: "gear"),
Setting(name: "Notifications", imageName: "bell"),
Setting(name: "My Store", imageName: "bag"),
Setting(name: "Search User", imageName: "magnifyingglass"),
Setting(name: "Sign Out", imageName: "arrow.backward"),
Setting(name: "Cancel", imageName: "xmark")
]
}()
override init() {
super.init()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(SettingsCell.self, forCellWithReuseIdentifier: reuseidentifier)
self.collectionView.register(HeaderCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerId)
}
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 + 1) * 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 = 15
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() {
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
}
}
#objc func handleDismissAndPresenetController(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
self.delegate?.settingDidSelected(setting: setting)
}
}
//MARK: - Header
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 50)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath)
return header
}
//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]
if cell.setting?.name == "Notifications" {
let cell = SettingsCell()
cell.badge.isHidden = false
}
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
return CGSize(width: width, height: cellHeight)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let setting = self.settings[indexPath.item]
handleDismissAndPresenetController(setting: setting)
}
}
Im not sure how i would do this as i need to call the show method in another class to present the menu like this:
let settingsLauncher = SettingsLauncher()
settingsLauncher.showSettings()
There are many ways to do this. This version uses inheritance of a common class
I used SwiftUI but the structs that end with View can be easily substituted with you UIKit UIViews
//Common Class
class MyObject {
var id: UUID = UUID()
var name: String
var imageName: String
init(name: String, imageName: String) {
self.name = name
self.imageName = imageName
}
}
//Class that inherits
class Setting: MyObject{
var typeDescription: String = "I am a Setting"
}
//Class that inherits
class CRUD: MyObject{
var useDescription: String = "Use me to persist"
}
class DataSource{
let settings: [Setting] = [Setting(name: "Settings", imageName: "gear"), Setting(name: "Notification", imageName: "bell")]
let cruds: [CRUD] = [CRUD(name: "Edit", imageName: "pencil"), CRUD(name: "Add", imageName: "plus")]
var objects: [MyObject] = [Setting(name: "Settings", imageName: "gear"),CRUD(name: "Edit", imageName: "pencil"), Setting(name: "Notification", imageName: "bell"), CRUD(name: "Add", imageName: "plus")]
}
struct ParentView: View {
let dataSource: DataSource = DataSource()
var body: some View {
Form{
Section(header: Text("Settings"), content: {ReusableListView(objects: dataSource.settings)})
Section(header: Text("CRUDs"), content: {ReusableListView(objects: dataSource.cruds)})
Section(header: Text("Combo"), content: {ReusableListView(objects: dataSource.objects)})
}
}
}
//Shared/Reusable View
struct ReusableListView: View {
var objects: [MyObject]
init(objects: [MyObject]){
self.objects = objects
}
var body: some View {
List(objects, id: \.id){obj in
VStack{
Label(
title: { Text(obj.name) },
icon: { Image(systemName: obj.imageName) })
//Add Something unique
if obj is Setting{
SettingCellView(object: obj as! Setting)
}else if obj is CRUD{
CRUDCellView(object: obj as! CRUD)
}
}
}
}
}
//Unique View
struct SettingCellView: View {
var object: Setting
var body: some View {
Text(object.typeDescription)
}
}
//Unique View
struct CRUDCellView: View {
var object: CRUD
var body: some View {
Text(object.useDescription)
}
}
Related
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.
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
}
}
}
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
I have a simple UICollectionView that has 3 cells. The cell has it's own class and all 3 cells use this class. The cell class just has a UITextField that is the cells size. I want to know how to be able to get hold of whatever is entered in each of the 3 cells UITextFields and pass it into my SecondController. How do I achieve this? Thank you guys. Code is below:
import UIKit
class FirstCell: BaseCell {
var secondController: SecondController?
let textField: UITextField = {
let tv = UITextField()
tv.textColor = .white
return tv
}()
override func setupViews() {
super.setupViews()
backgroundColor = .lightGray
addSubview(textField)
textField.frame = CGRect(x: 12, y: 0, width: self.frame.width, height: self.frame.height)
}
}
private let reuseIdentifier = "Cell"
class SecondController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.backgroundColor = .white
self.collectionView?.alwaysBounceVertical = true
self.collectionView?.register(FirstCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Print", style: .plain, target: self, action: #selector(handlePrint))
}
func handlePrint() {
// print here...
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 50)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? FirstCell
cell?.secondController = self
if indexPath.item == 0 {
cell?.textField.placeholder = "First Textfield"
}
if indexPath.item == 1 {
cell?.textField.placeholder = "Second Textfield"
}
if indexPath.item == 2 {
cell?.textField.placeholder = "ThirdTextfield"
}
return cell!
}
}
create protocol
protocol MyCellTextFieldDelegate {
func didChangeTextTo(text: String)
}
then
extension SecondController: MyCellTextFieldDelegate {
func didChangeTextTo(text: String) {
//doStuff
}
}
inside your cell change
var secondController: SecondController?
to
weak var delegate: MyCellTextFieldDelegate?
and change your method to be like
override func setupViews() {
super.setupViews()
backgroundColor = .lightGray
addSubview(textField)
textField.delegate = self //this line
textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) //and this line
textField.frame = CGRect(x: 12, y: 0, width: self.frame.width, height: self.frame.height)
}
and your cell class
class FirstCell: BaseCell, UITextFieldDelegate {
//other code
private func textFieldDidChange() {
self.delegate?.didChangeTextTo(text: (self.textField.text ?? ""))
}
}
and inside cellForItemAt
cell?.delegate = self
Edit: if you would like to get UICollectionViewCell that calls this, you should add
var tag: Int = 0
inside your cell and inside cellForItemAt
cell?.tag = indexPath.row
and then change your MyCellTextFieldDelegate func to pass not only text but tag as well.
I am using this code: https://www.youtube.com/watch?v=bNtsekO51iQ , but when I implement my data and use collectionView.reloadData() it crashes with error code *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the collection view's data source did not return a valid cell from -collectionView:cellForItemAtIndexPath: for index path <NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0}'
class ChatLogController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var userId:Int?
var position:Int = 0
var otherAvatar:UIImage = UIImage(named: "defaultAvatar")!
var otherName:String = ""
var otherSex:String = ""
var otherBanned:Int = 0
var otherBlocked:Int = 0
var messagesDates:[String] = []
var messagesText:[String] = []
var messagesIds:[String] = []
var messagesPics:[UIImage?] = []
var messagesSeen:[Int] = []
var messagesWhoSendIt:[Int] = []
private let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.register(ChatLogMessageCell.self, forCellWithReuseIdentifier: cellId)
collectionView!.isPrefetchingEnabled = false
loadChatsFor(position: position)
} override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messagesIds.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath as IndexPath) as! ChatLogMessageCell
cell.messageTextView.text = messagesText[indexPath.row]
cell.profileImageView.image = UIImage(named: "defaultAvatar")!
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
cell.messageTextView.frame = CGRect(x:48 + 8, y:0, width:estimatedFrame.width + 16, height:estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:48, y:0, width:estimatedFrame.width + 16 + 8, height:estimatedFrame.height + 20)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
return CGSize(width:view.frame.width, height:estimatedFrame.height + 20)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(8, 0, 0, 0)
}
}
class ChatLogMessageCell: BaseCell {
let messageTextView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 18)
textView.text = "Sample message"
textView.backgroundColor = UIColor.clear
return textView
}()
let textBubbleView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(white: 0.95, alpha: 1)
view.layer.cornerRadius = 15
view.layer.masksToBounds = true
return view
}()
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 15
imageView.layer.masksToBounds = true
return imageView
}()
override func setupViews() {
super.setupViews()
addSubview(textBubbleView)
addSubview(messageTextView)
addSubview(profileImageView)
addConstraintsWithFormat(format:"H:|-8-[v0(30)]", views: profileImageView)
addConstraintsWithFormat(format:"V:[v0(30)]|", views: profileImageView)
profileImageView.backgroundColor = UIColor.red
}
}
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
class BaseCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
}
}
In loadChatsFor I get the array data from web and I use collectionView.reloadData(), but when this function is performed it crashes my app. I've searched for answer, but unsuccessfully. I've added IOS 10 function collectionView!.isPrefetchingEnabled = false in view did load from this answer UICollectionView exception in UICollectionViewLayoutAttributes from iOS7, but also not working. Even the method collectionViewLayout invalidateLayout before reloadData and after reloadData doesn't stop the crash. So what else I can do to make it work ?
I am coming in this CollectionViewController from UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let layout = UICollectionViewFlowLayout()
let controller = ChatLogController(collectionViewLayout: layout)
controller.userId = chatUserIds[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
}
I've added UICollectionViewDelegateFlowLayout in the class of the tableview
You are not implementing correct cellForItemAt method of UICollectionViewDataSource. Signature of this method is changed in Swift 3 like this.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath as IndexPath) as! ChatLogMessageCell
cell.messageTextView.text = messagesText[indexPath.row]
cell.profileImageView.image = UIImage(named: "defaultAvatar")!
let size = CGSize(width:250,height:1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messagesText[indexPath.row]).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
cell.messageTextView.frame = CGRect(x:48 + 8, y:0, width:estimatedFrame.width + 16, height:estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x:48, y:0, width:estimatedFrame.width + 16 + 8, height:estimatedFrame.height + 20)
return cell
}