I have a class:
class CustomLayout: UICollectionViewLayout {
// Cant pass it directly into the init because it is an override init :/
override init() {
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
weak var delegate: layoutDelegate?
private var cache: [UICollectionViewLayoutAttributes] = []
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else {
print("Collectionview not found")
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
guard
cache.isEmpty,
let collectionView = collectionView
else { print("No Collectionview rip"); return }
let columnWidth = contentWidth
for item in 0..<collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
let postHeight = delegate?.collectionView(_collectionView: collectionView, heightForPostAtIndexPath: indexPath) ?? 200
let height = postHeight
let frame = CGRect(x: 0, y: 0, width: columnWidth, height: height)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
cache.append(attributes)
contentHeight = max(contentHeight, frame.maxY)
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []
// Loop through the cache and look for items in the rect
for attributes in cache {
if attributes.frame.intersects(rect) {
visibleLayoutAttributes.append(attributes)
}
}
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
}
What I am trying to do is pass a variable called CustomCollectionView into this class so that I can use this variable instead of the default collectionVIew. However I am unsure how I am supposed to do this because this class has an override init() which doesn't allow you to pass variables into.
How do I pass a CustomCollectionView variable into this class?
Related
New to Swift, I am trying to implement an NSTableView in my SwiftUI macOS (>= 12.2) app. The table view is being added as a subview of an NSView, but the table view is not displaying on the screen.
Update 1:
A minimal reproducible example was added at the end of the post
The code was updated to conform to advice from #jnpdx
Note: If uncommenting the line //addSubview(tableView) in configureTableView(), (so that there is a duplicate addSubview(tableView) call) you can see a slim table at the very bottom
Update 2: It appears that both addSubview(tableView) lines may be needed for the tableView to be a descendant view of CustomView in the view hierarchy, and displayed within the bounds of the CustomView instance. It apparently would not create a separate instance. If that's the case, then the current issue is just the table's positioning at the bottom of the app view (which should be at the top) and ensuring the text box's visibility
Here is the implementation for my FilesView class, which is a subview of the CustomView class:
class FilesView: NSView {
let files: Files
#Published var selectedFile: AudioFile? = nil
#Published var sortDescending = false
var tableView = NSTableView()
var nameColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "name"))
var statusColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "status"))
var dateColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "date"))
var groupingColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "grouping"))
var durationColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "duration"))
init(files:Files){
self.files = files
super.init(frame: .zero)
configureTableView()
}
func addFile(file: AudioFile) {
self.files.files.append(file)
tableView.reloadData()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureTableView() {
print("in configureTableView()")
tableView.delegate = self
tableView.dataSource = self
tableView.identifier = NSUserInterfaceItemIdentifier(rawValue: "cell")
tableView.addTableColumn(nameColumn)
tableView.addTableColumn(statusColumn)
tableView.addTableColumn(dateColumn)
tableView.addTableColumn(groupingColumn)
tableView.addTableColumn(durationColumn)
tableView.headerView = nil
tableView.backgroundColor = .black
tableView.usesAlternatingRowBackgroundColors = true
tableView.gridStyleMask = .solidVerticalGridLineMask
tableView.focusRingType = .none
tableView.selectionHighlightStyle = .none
tableView.rowSizeStyle = .default
tableView.reloadData()
//addSubview(tableView) // tableView is a subview of FilesView and FilesView is a subview of CustomView, tableView will be added as a subview of CustomView when CustomView is created and tableView is passed as a parameter.
}
}
And here is the implementation for the CustomView class:
class CustomView: NSView {
private let tableView: FilesView
private let textView: TextBox
init(tableView: FilesView, textView: TextBox) {
self.tableView = tableView
self.textView = textView
super.init(frame: .zero)
addSubview(tableView)
addSubview(textView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layout() {
super.layout()
let tableViewHeight = frame.height * 0.7
let textViewHeight = frame.height * 0.3
tableView.frame = NSRect(x: 0, y: 0, width: frame.width, height: tableViewHeight)
textView.frame = NSRect(x: 0, y: tableViewHeight, width: frame.width, height: textViewHeight)
}
}
The following invokes the CustomView in ContentView:
struct CustomViewRepresentable: NSViewRepresentable {
let filesView = FilesView(files: Files())
let textBox = TextBox()
func makeNSView(context: Context) -> NSView {
let customView = CustomView(tableView: filesView, textView: textBox)
return customView
}
func updateNSView(_ nsView: NSView, context: Context) {
// Update the NSView if necessary
}
typealias NSViewType = NSView
}
Here is the implementation in ContentView:
private var customViewRepresentable: CustomViewRepresentable
init(){
self.customViewRepresentable = CustomViewRepresentable()
}
In ContentView's body:
var body: some View {
VStack {
customViewRepresentable
}
}
Minimal reproducible example:
import SwiftUI
import Foundation
import Combine
class FilesView: NSView {
//let files: Files
#Published var sortDescending = false
var tableView = NSTableView()
var nameColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "name"))
var statusColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "status"))
var dateColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "date"))
var groupingColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "grouping"))
var durationColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "duration"))
init(){
super.init(frame: .zero)
configureTableView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureTableView() {
print("in configureTableView()")
tableView.delegate = self
tableView.dataSource = self
tableView.identifier = NSUserInterfaceItemIdentifier(rawValue: "cell")
tableView.addTableColumn(nameColumn)
tableView.addTableColumn(statusColumn)
tableView.addTableColumn(dateColumn)
tableView.addTableColumn(groupingColumn)
tableView.addTableColumn(durationColumn)
tableView.headerView = nil
tableView.backgroundColor = .black
tableView.usesAlternatingRowBackgroundColors = true
tableView.gridStyleMask = .solidVerticalGridLineMask
tableView.focusRingType = .none
tableView.selectionHighlightStyle = .none
tableView.rowSizeStyle = .default
tableView.reloadData()
//addSubview(tableView) // tableView is a subview of FilesView and FilesView is a subview of CustomView, tableView will be added as a subview of CustomView when CustomView is created and tableView is passed as a parameter.
}
}
extension FilesView: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return 0
}
}
extension FilesView: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cell"), owner: nil) as? NSTableCellView else {
return nil
}
if tableColumn == tableView.tableColumns[0] {
cell.textField?.stringValue = ""
} else if tableColumn == tableView.tableColumns[1] {
cell.textField?.stringValue = ""
}
else if tableColumn == tableView.tableColumns[2] {
cell.textField?.stringValue = ""
}
else if tableColumn == tableView.tableColumns[3] {
cell.textField?.stringValue = ""
}
else if tableColumn == tableView.tableColumns[4]{
cell.textField?.stringValue = ""
}
return cell
}
}
class TextBox: NSView {
#State private var selectedOption = 0 // 0 = strikeout, 1 = hide, 2 = underline
#State private var text = ""
#State private var isTextBoxHidden = false
var textEditor: some View {
TextEditor(text: $text)
.frame(minHeight: 200)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray, lineWidth: 2)
)
}
var exportButton: some View {
HStack {
Spacer()
Button(action: {
// code to export
}) {
Text("Export")
}
Spacer()
}
}
override func layout() {
super.layout()
// layout the views
}
}
class CustomView: NSView {
private let tableView: FilesView
private let textView: TextBox
init(tableView: FilesView, textView: TextBox) {
self.tableView = tableView
self.textView = textView
super.init(frame: .zero)
addSubview(tableView)
addSubview(textView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layout() {
super.layout()
let tableViewHeight = frame.height * 0.7
let textViewHeight = frame.height * 0.3
tableView.frame = NSRect(x: 0, y: 0, width: frame.width, height: tableViewHeight)
textView.frame = NSRect(x: 0, y: tableViewHeight, width: frame.width, height: textViewHeight)
}
}
struct CustomViewRepresentable: NSViewRepresentable {
let filesView = FilesView()
let textBox = TextBox()
func makeNSView(context: Context) -> NSView {
let customView = CustomView(tableView: filesView, textView: textBox)
return customView
}
func updateNSView(_ nsView: NSView, context: Context) {
// Update the NSView if necessary
}
typealias NSViewType = NSView
}
//let customViewRepresentable = CustomViewRepresentable()
// init(){
// self.customViewRepresentable = CustomViewRepresentable()
// }
struct ContentView: View {
private var customViewRepresentable: CustomViewRepresentable
init(){
self.customViewRepresentable = CustomViewRepresentable()
}
// Conform to the ObservableObject protocol
var didChange = PassthroughSubject<Void, Never>()
var body: some View {
VStack {
customViewRepresentable
}
}
}
I'm trying to add a new NSTextView to the last index in my collection view every time the attributed string exceeds a certain bounds. The code works perfectly until the 5th item then it starts its starts creating an item every time the enter button is pressed. I'm thinking its a bug but im not sure. if any one can show me a better way to do it or improve the current code I have I would appreciate it. Below is my code:
Here is the CollectionViewItem
class DocumentItem: NSCollectionViewItem {
var itemView: DocumentTextView?
override func viewDidLoad() {
super.viewDidLoad()
self.itemView?.wantsLayer = true
// Do view setup here.
}
override func loadView() {
self.itemView = DocumentTextView(frame: NSZeroRect)
self.view = self.itemView!
}
func getView() -> DocumentTextView {
return self.itemView!
}
}
Here is the collectionView datasource
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return DocList.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DocumentItem"), for: indexPath)
return item
}
Here is the NSTextView subclass
class DocumentTextView: NSTextView {
var theContainer = NSTextContainer()
var theStorage = NSTextStorage()
var theManager = NSLayoutManager()
var table = NSTextTable()
var pdfPage: PDFPage?
override init(frame frameRect: NSRect) {
super.init(frame: NSRect(origin: frameRect.origin, size: NSSize(width: 800, height: 1131 )), textContainer: theContainer)
theStorage.addLayoutManager(theManager)
theManager.addTextContainer(theContainer)
self.textContainerInset = CGSize(width: 50, height: 50)
self.textContainer?.widthTracksTextView = true
self.textContainer?.heightTracksTextView = true
self.textContainer?.lineBreakMode = .byWordWrapping
self.maxSize = NSSize(width: 800, height: 1131)
self.backgroundColor = NSColor.fromHexString("ffffff")!
self.isRichText = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is the function bringing the bug
func textDidChange(_ notification: Notification) {
var textView = notification.object as? DocumentTextView
let numberOfItems = theDocumentOutlineView.numberOfItems(inSection: 0)
let theLastTextView = theDocumentOutlineView.item(at: numberOfItems - 1) as! DocumentItem
if textView == theLastTextView.itemView {
print(textView?.attributedString().size())
if (textView?.attributedString().size().height)! >= 1106.0 {
self.DocList.append(2)
var set = Set<IndexPath>()
set.insert(NSIndexPath(forItem: self.DocList.count - 1 , inSection: 0) as IndexPath)
theDocumentOutlineView.insertItems(at: set)
theDocumentOutlineView.scrollToItems(at: set, scrollPosition: NSCollectionView.ScrollPosition.top)
var newFirstResponder = theDocumentOutlineView.item(at: self.DocList.count - 1) as! DocumentItem
newFirstResponder.itemView?.delegate = self
self.view.window?.makeFirstResponder(newFirstResponder.itemView)
}
}
}
Here's my test project, maybe it helps. The delegate of the text view is its view controller, the NSCollectionViewItem. The view controller of the collection view also receives NSText.didChangeNotification notifications to check the length of the text. heightTracksTextView of the text container is false.
ViewController:
class ViewController: NSViewController, NSCollectionViewDataSource, NSCollectionViewDelegate {
#IBOutlet weak var collectionView: NSCollectionView!
var docList: [DocumentObject] = [DocumentObject(index: 0, string: NSAttributedString(string: "New 0"))]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(DocumentItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier("DocumentItem"))
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange),
name: NSText.didChangeNotification, object: nil)
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return docList.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
if let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier("DocumentItem"),
for: indexPath) as? DocumentItem {
item.representedObject = docList[indexPath.item]
return item
}
return NSCollectionViewItem()
}
#objc func textDidChange(_ notification: Notification) {
if let textView = notification.object as? NSTextView {
if let theLastItem = self.collectionView.item(at: self.docList.count - 1) as? DocumentItem,
textView === theLastItem.itemView {
//if textView.attributedString().size().height >= 1106.0 {
print("\(textView.attributedString().size().height) \(textView.layoutManager!.usedRect(for: textView.textContainer!).size.height)")
if let textContainer = textView.textContainer,
let heigth = textView.layoutManager?.usedRect(for: textContainer).size.height,
heigth >= 1106.0 {
DispatchQueue.main.async {
if let window = self.view.window,
window.makeFirstResponder(nil) { // end editing of previous item
self.docList.append(DocumentObject(index: self.docList.count - 1, string: NSAttributedString(string: "New \(self.docList.count)")))
let set: Set = [IndexPath(item: self.docList.count - 1, section: 0)]
self.collectionView.insertItems(at: set)
self.collectionView.scrollToItems(at: set, scrollPosition: NSCollectionView.ScrollPosition.top)
if let newItem = self.collectionView.item(at: self.docList.count - 1) as? DocumentItem {
window.makeFirstResponder(newItem.itemView)
}
}
}
}
}
}
}
}
DocumentItem:
class DocumentItem: NSCollectionViewItem, NSTextViewDelegate {
var itemView: DocumentTextView?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
override func loadView() {
self.itemView = DocumentTextView(frame: NSZeroRect)
self.view = self.itemView!
self.itemView?.delegate = self
}
override var representedObject: Any? {
didSet {
if let item = representedObject as? DocumentObject {
itemView?.textStorage?.setAttributedString(item.string)
}
}
}
func textDidEndEditing(_ notification: Notification) {
if let item = representedObject as? DocumentObject {
item.string = itemView?.textStorage?.copy() as! NSAttributedString
}
}
}
DocumentObject:
class DocumentObject {
var index: Int
var string: NSAttributedString
init(index: Int, string: NSAttributedString) {
self.index = index
self.string = string
}
}
I have a TableView, with about 15 to 20 displayed cells. The cell has a Label with the following class:
import Foundation
import UIKit
class RoundedLabel: UILabel {
var inset: CGFloat = 16
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
super.drawText(in: rect.inset(by: insets))
}
override func layoutSubviews() {
super.layoutSubviews()
updateCornerRadius()
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(
width: size.width + inset,
height: size.height
)
}
#IBInspectable var rounded: Bool = false {
didSet {
updateCornerRadius()
}
}
func updateCornerRadius() {
layer.cornerRadius = rounded ? frame.size.height / 2 : 0
layer.masksToBounds = true
}
}
In my VC I have the following (I have to TableViews in one VC):
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == tableViewMainLatest {
let cell = tableViewMainLatest.dequeueReusableCell(withIdentifier: "cellMainLatest", for: indexPath) as! MainLatestTableViewCell
let postDate = posts[indexPath.row].postDate ?? 0
let formattedPostDate = Date(timeIntervalSince1970: postDate)
let timeAgo = formattedPostDate.timeAgo(numericDates: false)
let postCity = posts[indexPath.row].postFromCity ?? "?"
let postCountry = posts[indexPath.row].postFromCountry ?? "?"
let postComments = posts[indexPath.row].comments ?? 0
let maxPostComments: String?
// Only show the number if comment amount is less than 100
if postComments > 99 {
maxPostComments = "99+"
} else {
maxPostComments = String(postComments)
}
cell.labelPostDate.text = timeAgo
cell.labelPostFrom.text = postCity + ", " + postCountry
cell.labelAmountComments.text = maxPostComments
return cell
} else if tableView == tableViewCategories {
let cell = tableViewCategories.dequeueReusableCell(withIdentifier: "cellMainCategories", for: indexPath) as! MainApplicationCategoriesTableViewCell
cell.labelCategories.text = pseudo[indexPath.row] as? String
cell.alpha = 0.55
UIView.animate(withDuration: 0.5, animations: {
cell.alpha = 1
})
return cell
}
return UITableViewCell()
}
My CustomCell:
class MainApplicationCategoriesTableViewCell: UITableViewCell {
// Storyboard
#IBOutlet weak var labelCategories: UILabel! {
didSet {
labelCategories.layer.borderWidth = 1
labelCategories.layer.borderColor = UIColor(red:0.88, green:0.88, blue:0.88, alpha:1.0).cgColor
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.selectionStyle = .none
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
When I use this piece of code:
// Storyboard
#IBOutlet weak var labelCategories: UILabel! {
didSet {
labelCategories.layer.borderWidth = 1
labelCategories.layer.borderColor = UIColor(red:0.88, green:0.88, blue:0.88, alpha:1.0).cgColor
}
}
The scrolling of the TableView is lagging. Where do I have to apply the borderWidth / borderColor, to still have a good user experience without lagging?
i am desperately trying to add a custom View to a UITableViewCell. I have a UITableViewController in Storyboard wich is linked to a TestTableViewController Class and the prototype cell has the identifier "Cell2".
So this is the code in UITableViewController:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return dataArray.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 190
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell2", forIndexPath: indexPath)
let data = dataArray[indexPath.row]
let ticket = YellowTackleTicketComplete()
ticket.frame = CGRect(x: 20, y: 20, width: 335, height: 150)
cell.addSubview(ticket)
// cell.textLabel!.text = tackl!.valueForKey("title") as? String
cell.backgroundColor = UIColor.clearColor()
print(cell.subviews)
return cell
}
The dataArray is just for testing purposes and has one item in it. So what i get is a Table View with one empty cell. In the Xcode UI Debugger i can see the custom view. But its not shown in the simulator or on a device.
If anyone can help, thank you so much!
This is the code of the view:
class YellowTackleTicketComplete: UIView {
var containerView: UIView!
var ticket: TacklTicket!
var dropDownMenu: YellowDrawer!
var dropDownBackground: YellowDrawerBackground!
var ticketShadowLine: UIView!
var lowAlphaView: UIView!
var outbackButton: UIButton!
var arrowView: UIView!
var bounceHeight: CGFloat?
var dropdownHeight: CGFloat?
var topBorder: CGFloat?
var bottomBorder: CGFloat?
//let arrowLayer = CALayer()
//let layerDelegate = LayerDelegate()
var dropDownElements = [String]()
var dropped = false
var animating = false
var delegate: YellowTackleTicketCompleteDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addObserver(self, forKeyPath: "highlighted", options: NSKeyValueObservingOptions.New, context: nil)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addObserver(self, forKeyPath: "highlighted", options: NSKeyValueObservingOptions.New, context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
self.setNeedsDisplay()
if keyPath == "frame" {
// Set up DropdownMenu
self.dropDownBackground.frame.size.height = self.dropDownMenu.frame.maxY
}
}
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if(!self.clipsToBounds && !self.hidden && self.alpha > 0.0){
let subviews = self.subviews.reverse()
for member in subviews {
let subPoint = member.convertPoint(point, fromView: self)
if let result:UIView = member.hitTest(subPoint, withEvent:event) {
return result;
}
}
}
return nil
}
func ticketTapped() {
//if !animating {
if !dropped {
showDropdown()
NSLog("#")
// NSLog("Scrolling to \((dropDownMenu.indexPathForSelectedRow?.row)!)(the index of the tableview selection)")
// dropDownMenu.scrollToRowAtIndexPath((dropDownMenu.indexPathForSelectedRow)!, atScrollPosition: .Middle, animated: false)
} else {
hideDropdown()
}
// }
}
func showDropdown() {
dropDownMenu.hidden = false
self.ticket.drawShadowBeneathTicket()
print("showing")
if !animating {
animating = true
UIView.animateWithDuration(
1.0,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.5,
options: [],
animations: {
self.dropDownMenu.frame.origin.y = self.bottomBorder!
}, completion: { _ in
//self.delegate?.menuOpen?()
self.animating = false
print(self.dropDownBackground.frame.size.height)
print(self.dropDownMenu.frame.maxY)
}
)
}
self.setNeedsDisplay()
self.dropDownMenu!.setNeedsDisplay()
dropped = true
}
func hideDropdown() {
if !animating {
animating = true
UIView.animateWithDuration(
0.7,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.5,
options: [],
animations: {
self.dropDownMenu.frame.origin.y = self.topBorder! + self.bounceHeight!
}, completion: nil
)
}
UIView.animateWithDuration(
0.5,
delay: 0,
options: UIViewAnimationOptions.TransitionNone,
animations: {
self.dropDownMenu.frame.origin.y = self.topBorder! - self.dropdownHeight!
}, completion: { _ in
//self.delegate?.menuClosed?()
self.animating = false
self.dropDownMenu.hidden = true
self.ticket.setNeedsDisplay()
self.setNeedsDisplay()
}
)
dropped = false
}
func initSubViews() {
self.topBorder = self.frame.height*6/150
self.bottomBorder = self.frame.height - self.topBorder!
self.bounceHeight = 250
self.dropdownHeight = 350
containerView = UIView()
containerView.frame = CGRect(x: 0, y: topBorder!, width: self.bounds.width, height: dropdownHeight!+bounceHeight!)
containerView.clipsToBounds = true
ticket = TacklTicket()
ticket.frame = self.bounds
ticket.addTarget(self, action: #selector(YellowTackleTicketComplete.ticketTapped), forControlEvents: .TouchDown)
dropDownMenu = YellowDrawer()
dropDownMenu.frame = CGRect(x: 0, y: topBorder! - dropdownHeight!, width: containerView.bounds.width, height: dropdownHeight!)
dropDownMenu.hidden = true
dropDownMenu.backgroundColor = UIColor.clearColor()
dropDownMenu.addObserver(self, forKeyPath: "frame", options: .New, context: nil)
dropDownBackground = YellowDrawerBackground()
dropDownBackground.frame = CGRectMake(0,0,self.bounds.width,self.dropDownMenu.frame.maxY)
self.addSubview(containerView)
containerView.addSubview(dropDownBackground)
containerView.addSubview(dropDownMenu)
self.addSubview(ticket)
}
}
You should use a designated initializer for your UIView subview. That is either init(frame:CGRect) or in add it in Interface Builder. You are also not passing let data = dataArray[indexPath.row] to your cell, not sure if you're aware of that. You also never call initSubViews() which seems to be a big part of your custom view.
instead of
cell.addSubview(ticket)
try
cell.contentView.addSubview(ticket)
and the best place to customize cell's content is its init function.
Couple of things you should try, set translatesAutoresizingMaskIntoConstraints to false on the view you created programmatically since you are inserting it into an auto layout controller.
View that should also try just setting,
dropDownMenu, dropDownBackground, containerView and ticket
or
In your cellForRowAtIndexPath also try setting the property
ticket.translatesAutoresizingMaskIntoConstraints = false
When I load the first time my UICollectionView does not have any problem, Im using custom layouts and a serachbar, when I search some text it crashes throwing the exception that the cell with an index path that does not exist, Im using the search bar like the next code:
import UIKit
import AVFoundation
class CategoryViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UISearchBarDelegate {
var ListArray : JSON! = []
var SelectedIds: [Int] = []
var SearchActive : Bool = false
var SearchArray = [Int]()
#IBOutlet weak var SearchCategories: UISearchBar!
#IBOutlet weak var CategoryCollection: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
if let layout = self.CategoryCollection.collectionViewLayout as? InterestsLayout {
layout.delegate = self
}
self.CategoryCollection.backgroundColor = UIColor.clearColor()
self.CategoryCollection.contentInset = UIEdgeInsets(top: 18, left: 3, bottom: 10, right: 3)
// Search Delegate
self.SearchCategories.delegate = self
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.LoadData()
}
func LoadData() {
MUBService.categoriesList(self) { (categories_list) -> () in
self.ListArray = categories_list
self.CategoryCollection.reloadData()
self.view.hideLoading()
}
}
func dismissKeyboard() {
view.endEditing(true)
self.SearchCategories.showsCancelButton = false
}
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
self.SearchCategories.showsCancelButton = true
self.SearchActive = true
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
self.SearchActive = false
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
self.SearchActive = false
self.dismissKeyboard()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
self.SearchActive = false
}
func searchBar(searchBar: UISearchBar, let textDidChange searchText: String) {
self.SearchArray = []
for (index, object) in self.ListArray["categories"] {
let name = object["name"].string!
if name.localizedStandardContainsString(searchText) == true {
self.SearchArray.append(Int(index)!)
}
}
if(self.SearchArray.count == 0){
self.SearchActive = false;
} else {
self.SearchActive = true;
}
self.CategoryCollection.reloadData()
}
}
extension CategoryViewController {
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
self.CategoryCollection?.collectionViewLayout.invalidateLayout()
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(self.SearchActive && self.SearchArray.count > 0) {
return self.SearchArray.count
}
return self.ListArray["categories"].count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CategoryCell", forIndexPath: indexPath) as! CategoryCell
let row = indexPath.row
if(self.SearchActive && self.SearchArray.count > 0) {
let category = self.ListArray["categories"][self.SearchArray[row]]
cell.configureWithPhoto(category, selected: self.ListArray["selected"])
}else{
let category = self.ListArray["categories"][row]
cell.configureWithPhoto(category, selected: self.ListArray["selected"])
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = self.CategoryCollection.cellForItemAtIndexPath(indexPath) as! CategoryCell
cell.changeBackGroundColor()
if (cell.is_active == true){
self.SelectedIds.append(cell.id)
}else{
self.SelectedIds.removeObject(cell.id)
}
}
#IBAction func RegisterDidTouch(sender: AnyObject) {
MUBService.setMyCategories(self.SelectedIds, view_controller: self) { (categories_selected) -> () in
self.performSegueWithIdentifier("HomeTabBarFromCategoriesSegue", sender: self)
}
}
}
extension CategoryViewController : InterestsLayoutDelegate {
// 1. Returns the photo height
func collectionView(collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:NSIndexPath , withWidth width:CGFloat) -> CGFloat {
var row = indexPath.row
if(self.SearchActive && self.SearchArray.count > 0) {
row = self.SearchArray[row]
}
let category = self.ListArray["categories"][row]
let url = NSURL(string:category["image"].string!)
let data = NSData(contentsOfURL:url!)
let image = UIImage(data:data!)!
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let rect = AVMakeRectWithAspectRatioInsideRect((image.size), boundingRect)
return rect.size.height
}
// 2. Returns the annotation size based on the text
func collectionView(collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: NSIndexPath, withWidth width: CGFloat) -> CGFloat {
let annotationPadding = CGFloat(4)
let annotationHeaderHeight = CGFloat(17)
var row = indexPath.row
if(self.SearchActive && self.SearchArray.count > 0) {
row = self.SearchArray[row]
}
let category = self.ListArray["categories"][row]
let font = UIFont(name: "AvenirNext-Regular", size: 10)!
let rect = NSString(string: category["name"].string!).boundingRectWithSize(CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
let commentHeight = ceil(rect.height)
var height = annotationPadding + annotationHeaderHeight + commentHeight + annotationPadding
if (height != 70){
height = 70
}
return 70
}
}
I don't understand what is happening, thanks a lot for your help
I've faced the same problem. Here an explanation: if you use a custom collectionViewLayout and you have a cache for layout attributes (best practice so you don't have to calculate every time attributes), you have to override invalidateLayout method in your custom layout class and purge your cache.
Here's my layout attributes array
private var cache = [UICollectionViewLayoutAttributes]()
Here the overrided method
override func invalidateLayout() {
super.invalidateLayout()
cache.removeAll()
}
I call layout invalidation in my textDidChange delegate
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count > 0 {
// search and reload data source
self.searchBarActive = true
self.filterContentForSearchText(searchText)
self.collectionView?.collectionViewLayout.invalidateLayout()
self.collectionView?.reloadData()
}else{
self.searchBarActive = false
self.collectionView?.collectionViewLayout.invalidateLayout()
self.collectionView?.reloadData()
}
}