UIImage created with animatedImageWithImages have bugs? - ios

Recently I found sometimes my gif image not shown. So I write a test code for it:
import UIKit
import ImageIO
extension UIImage {
static func gifImageArray(data: NSData) -> [UIImage] {
let source = CGImageSourceCreateWithData(data, nil)!
let count = CGImageSourceGetCount(source)
if count <= 1 {
return [UIImage(data: data)!]
} else {
var images = [UIImage](); images.reserveCapacity(count)
for i in 0..<count {
let image = CGImageSourceCreateImageAtIndex(source, i, nil)!
images.append( UIImage(CGImage: image) )
}
return images;
}
}
static func gifImage(data: NSData) -> UIImage? {
let gif = gifImageArray(data)
if gif.count <= 1 {
return gif[0]
} else {
return UIImage.animatedImageWithImages(gif, duration: NSTimeInterval(gif.count) * 0.1)
}
}
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let _gifData : NSData = NSData(contentsOfURL: NSURL(string: "https://upload.wikimedia.org/wikipedia/commons/d/d3/Newtons_cradle_animation_book_2.gif")!)!
lazy var _gifImageArray : [UIImage] = UIImage.gifImageArray(self._gifData)
lazy var _gifImage : UIImage = UIImage.gifImage(self._gifData)!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let table = UITableView(frame: self.view.bounds, style: .Plain)
table.dataSource = self
table.delegate = self
self.view.addSubview(table)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1000;
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let identifier = "cell"
var cell : UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(identifier);
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: identifier)
}
if let imageView = cell.imageView {
/** 1. use the same UIImage object created by animatedImageWithImages
the image disappear when reuse, and when touching cell, it reappear. */
imageView.image = self._gifImage
/** 2. create different UIImage by using same image array. this still not work */
// imageView.image = UIImage.animatedImageWithImages(self._gifImageArray, duration: NSTimeInterval(self._gifImageArray.count) * 0.1)
/** 3. create different UIImage from data, this seems work
but use a lot of memories. and seems it has performance issue. */
// imageView.image = UIImage.gifImage(self._gifData)
/** 4. use same image array as imageView's animationImages.
this kind works, but have different api than just set image.
and when click on cell, the animation stops. */
// imageView.image = self._gifImageArray[0]
// imageView.animationImages = self._gifImageArray
// imageView.startAnimating()
}
return cell
}
}
Notice in the cellForRowAtIndexPath: method, I tried different way to set imageView.
when I po object in lldb, I notice the animated imageView have a CAKeyframeAnimation. does this makes a tip?
How can I make the gif shown perfectly? Any suggestion would be helpful.
Here is a preview: after scroll, gif disappear, and reappear when touch

After I tried many times, I have finally figured out that I need to change:
imageView.image = self._gifImage
to:
imageView.image = nil // this is the magical!
imageView.image = self._gifImage
just set image to nil before set to new image, and the animation work!! It's just like a magical!
This is definitely apple's bug.

try by replacing imageView with myImageview or other name because cell have default imageView property so when you call cell.imageView then it returns cell's default imageview i think. so change your cell's imageview's variable name. hope this will help :)

maybe image class need SDAnimatedImage instead of UIImage

Related

Image in collectionView cell causing Terminated due to memory issue

I looked at several answers for this problem but none helped.
vc1 is a regular vc, I grab 20 images from firebase, in vc2 I use ARKit to display those images upon user selection. I have a collectionView in vc2 that paginates 20 more images from firebase. The problem is when the next 20 images are loading and I start scrolling, the app crashes with Message from debugger: Terminated due to memory issue. When scrolling those new images, I look at the memory graph and it shoots up to 1 gig, so that's the reason for the crash. ARKit and the nodes I have floating around also contribute to the memory bump but they are not the reason for the crash as stated below.
1- Inside the cell I use SDWebImage to display the image inside the imageView. Once I comment out SDWebImage everything works, scrolling is smooth, and no more crashes but of course I can't see the image. I switched to URLSession.shared.dataTask and the same memory issue reoccurs.
2- The images were initially taken with the iPhone full screen camera and saved with jpegData(compressionQuality: 0.3). The cell size is 40x40. Inside the the SDWebImage completion block I tried to resize the image but the memory crash still persists.
3- I used Instruments > Leaks to look for memory leaks and a few Foundation leaks appeared but when I dismiss vc2 Deinit always runs. Inside vc2 there aren't any long running timers or loops and I use [weak self] inside all of the closures.
4- As I stated in the second point the problem is definitely the imageView/image because once I remove it from the process everything works fine. If I don't show any images everything works fine (40 red imageViews with no images inside of them will appear).
What can I do to fix this issue?
Paginating to pull more images
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let dict = child.value as? [String:Any] else { continue }
let post = Post(dict: dict)
datasource.append(post)
let lastIndex = datasource.count - 1
let indexPath = IndexPath(item: lastIndex, section: 0)
UIView.performWithoutAnimation {
collectionView.insertItems(at: [indexPath])
}
}
cellForItem:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellId, for: indexPath) as! PostCell
cell.resetAll()
cell.post = dataSource[indexPath.item]
return cell
}
PostCell
private lazy var imageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 15
iv.layer.masksToBounds = true
iv.backgroundColor = .red
iv.isHidden = true
return iv
}()
private lazy var spinner: UIActivityIndicatorView = {
let actIndi = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.medium) // ...
}()
var post: Post? {
didSet {
guard let urlSr = post?.urlStr, let url = URL(string: urlSr) else { return }
spinner.startAnimating()
// if I comment this out everything works fine
imageView.sd_setImage(with: url, placeholderImage: placeHolderImage, options: [], completed: {
[weak self] (image, error, cache, url) in
// in here I tried resizing the image but no diff
DispatchQueue.main.async { [weak self] in
self?.showAll()
}
})
setAnchors()
}
}
func resetAll() {
spinner.stopAnimating()
imageView.image = nil
imageView.removeFromSuperView()
imageView.isHidden = true
}
func showAll() {
spinner.stopAnimating()
imageView.isHidden = false
}
func setAnchors() {
contentView.addSubview(imageView)
imageView.addSubview(cellSpinner)
// imageView is pinned to all sides
}
In the comments #matt was 100% correct , the problem was the images were too large for the collectionView at size 40x40, I needed to resize the images. For some reason the resize link in my question didn't work so I used this to resize image instead. I also used URLSession and this answer
I did it in 10 steps, all commented out
// 0. set this anywhere outside any class
let imageCache = NSCache<AnyObject, AnyObject>()
PostCell:
private lazy var imageView: UIImageView = { ... }()
private lazy var spinner: UIActivityIndicatorView = { ... }()
// 1. override prepareForReuse, cancel the task and set it to nil, and set the imageView.image to nil so that the wrong images don't appear while scrolling
override func prepareForReuse() {
super.prepareForReuse()
task?.cancel()
task = nil
imageView.image = nil
}
// 2. add a var to start/stop a urlSession when the cell isn't on screen
private var task: URLSessionDataTask?
var post: Post? {
didSet {
guard let urlStr = post?.urlStr else { return }
spinner.startAnimating()
// 3. crate the new image from this function
createImageFrom(urlStr)
setAnchors()
}
}
func createImageFrom(_ urlStr: String) {
if let cachedImage = imageCache.object(forKey: urlStr as AnyObject) as? UIImage {
// 4. if the image is in cache call this function to show the image
showAllAndSetImageView(with: cachedImage)
} else {
guard let url = URL(string: urlStr) else { return }
// 5. if the image is not in cache start a urlSession and initialize it with the task variable from step 2
task = URLSession.shared.dataTask(with: url, completionHandler: {
[weak self](data, response, error) in
if let error = error { return }
if let response = response as? HTTPURLResponse {
print("response.statusCode: \(response.statusCode)")
guard 200 ..< 300 ~= response.statusCode else { return }
}
guard let data = data, let image = UIImage(data: data) else { return }
// 6. add the image to cache
imageCache.setObject(image, forKey: photoUrlStr as AnyObject)
DispatchQueue.main.async { [weak self] in
// 7. call this function to show the image
self?.showAllAndSetImageView(with: image)
}
})
task?.resume()
}
}
func showAllAndSetImageView(with image: UIImage) {
// 8. resize the image
let resizedImage = resizeImageToCenter(image: image, size: 40) // can also use self.frame.height
imageView.image = resizeImage
showAll()
}
// 9. func to resize the image
func resizeImageToCenter(image: UIImage, size: CGFloat) -> UIImage {
let size = CGSize(width: size, height: size)
// Define rect for thumbnail
let scale = max(size.width/image.size.width, size.height/image.size.height)
let width = image.size.width * scale
let height = image.size.height * scale
let x = (size.width - width) / CGFloat(2)
let y = (size.height - height) / CGFloat(2)
let thumbnailRect = CGRect.init(x: x, y: y, width: width, height: height)
// Generate thumbnail from image
UIGraphicsBeginImageContextWithOptions(size, false, 0)
image.draw(in: thumbnailRect)
let thumbnail = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return thumbnail!
}
func resetAll() { ... }
func showAll() { ... }
func setAnchors() { ... }

Initial scroll lag in UITableView cells with resized large images?

I'm basically having the same issue as this question:
Slow scroll on UITableView images
My UITableView contains a UIImageView that is 87x123 size.
When my UITableViewController is loaded, it first calls a function that loops through an array of images. These images are high resolutions stored from the photo library. In each iteration, it retrieves the image and resize each image down to 87x123, then stores it back into the original image in the array.
When all the images has been resized and stored, it calls self.tableView.reloadData to populate the data in the array into the cells.
However, like the mentioned question, my UITablView is choppy and lags if I scroll fast before all the images has been resized and stored in the array.
Here's the problematic code:
extension UIImage
{
func resizeImage(originalImage: UIImage, scaledTo size: CGSize) -> UIImage
{
// Avoid redundant drawing
if originalImage.size.equalTo(size)
{
return originalImage
}
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
originalImage.draw(in: CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(size.width), height: CGFloat(size.height)))
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
func loadImages()
{
DispatchQueue.global(qos: .background).async {
for index in 0..<self.myArray.count
{
if let image = self.myArray[index].image
{
self.myArray[index].image = image.resizeImage(originalImage: image, scaledTo: CGSize(width: 87, height: 123) )
}
if index == self.myArray.count - 1
{
print("FINISHED RESIZING ALL IMAGES")
}
}
}
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
...
// Size is 87x123
let thumbnailImage = cell.viewWithTag(1) as! UIImageView
DispatchQueue.main.async{
thumbnailImage.image = self.myArray[indexPath.row]
}
thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill
thumbnailImage.layer.borderColor = UIColor.black.cgColor
thumbnailImage.layer.borderWidth = 1.0
thumbnailImage.clipsToBounds = true
return cell
}
I know to do any Non-UI operations in the background thread, which is what I do. Then all I do in cellForRowAt is load the image into the cell using its indexPath.row.
The problem is, as previously mentioned, if I start to scroll the UITableView BEFORE FINISHED RESIZING ALL IMAGES is printed out, i.e. before all the images has been resized, there is noticeable lag and slowness.
However, if I wait UNTIL all the images has been resized and FINISHED RESIZING ALL IMAGES is called before scrolling the UITableView, the scrolling is smooth without any lags.
I can put a loading indicator and have the user wait until all images has been resized and loaded into the cells before having user interaction, but that would be an annoyance since it takes about 8 seconds to resize all the high-res images (18 images to resize).
Is there a better way I can fix this lag?
UPDATE: Following #iWheelBuy's second example, I've implemented the following:
final class ResizeOperation: Operation {
private(set) var image: UIImage
let index: Int
let size: CGSize
init(image: UIImage, index: Int, size: CGSize) {
self.image = image
self.index = index
self.size = size
super.init()
}
override func main() {
image = image.resizeImage(originalImage: image, scaledTo: size)
}
}
class MyTableViewController: UITableViewController
{
...
lazy var resizeQueue: OperationQueue = self.getQueue()
var myArray: [Information] = []
internal struct Information
{
var title: String?
var image: UIImage?
init()
{
}
}
override func viewDidLoad()
{
....
loadImages()
...
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
...
// Size is 87x123
let thumbnailImage = cell.viewWithTag(1) as! UIImageView
DispatchQueue.main.async{
thumbnailImage.image = self.myArray[indexPath.row].image
}
thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill
return cell
}
func loadImages()
{
let size = CGSize(width: 87, height: 123)
for item in myArray
{
let operations = self.myArray.enumerated().map({ ResizeOperation(image: item.image!, index: $0.offset, size: size) })
operations.forEach { [weak queue = resizeQueue, weak controller = self] (operation) in
operation.completionBlock = { [operation] in
DispatchQueue.main.async { [image = operation.image, index = operation.index] in
self.update(image: image, index: index)
}
}
queue?.addOperation(operation)
}
}
}
func update(image: UIImage, index: Int)
{
myArray[index].image = image
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: UITableViewRowAnimation.fade)
}
}
However, upon calling tableView.reloadRows, I receive a crash with the error:
attempt to delete row 0 from section 0, but there are only 0 sections
before the update
I'm a bit confused on what it means and how to resolve it.
It is hard to determine the reason why you have lags. But there are some thoughts that might help you to make you code more performant.
Try using some small images at start and see, if it is the size of original image that influences bad performance. Also try to hide these lines and see if anything changes:
// thumbnailImage.contentMode = UIViewContentMode.scaleAspectFill
// thumbnailImage.layer.borderColor = UIColor.black.cgColor
// thumbnailImage.layer.borderWidth = 1.0
// thumbnailImage.clipsToBounds = true
Your code is a little bit oldschool, for loop in loadImages can become more readable with just a few lines of code by applying map or forEach to your image array.
Also, about your array of images. You read it from main thread and modify it from background thread. And you do it simultaneously. I'd suggest to make only image resizing on background... unless you are sure there will be no bad consequences
Check the code example #1 below how you current code can look like.
On the other hand, you can go some other way. For example you can set some placeholder image at start and update cells when image for some specific cell is ready. Not all images at once! If you go with some serial queue, you will get image updates every 0.5 seconds and the UI updates will be fine.
Check the code example #2. It wasn't tested, just to show the way you can go.
Btw, have you tried changing QualityOfService from background to userInitiated? It might decrease resizing time... or not (:
DispatchQueue.global(qos: .userInitiated).async {
// code
}
Example #1
extension UIImage {
func resize(to size: CGSize) -> UIImage {
guard self.size.equalTo(size) else { return self }
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
draw(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
static func resize(images: [UIImage], size: CGSize, completion: #escaping ([UIImage]) -> Void) {
DispatchQueue.global(qos: .background).async {
let newArray = images.map({ $0.resize(to: size) })
DispatchQueue.main.async {
completion(newArray)
}
}
}
}
final class YourController: UITableViewController {
var myArray: [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
self.loadImages()
}
}
fileprivate extension YourController {
func loadImages() {
UIImage.resize(images: myArray, size: CGSize(width: 87, height: 123)) { [weak controller = self] (newArray) in
guard let controller = controller else { return }
controller.myArray = newArray
controller.tableView.reloadData()
}
}
}
Example #2
final class ResizeOperation: Operation {
private(set) var image: UIImage
let index: Int
let size: CGSize
init(image: UIImage, index: Int, size: CGSize) {
self.image = image
self.index = index
self.size = size
super.init()
}
override func main() {
image = image.resize(to: size)
}
}
final class YourController: UITableViewController {
var myArray: [UIImage] = []
lazy var resizeQueue: OperationQueue = self.getQueue()
override func viewDidLoad() {
super.viewDidLoad()
self.loadImages()
}
private func getQueue() -> OperationQueue {
let queue = OperationQueue()
queue.qualityOfService = .background
queue.maxConcurrentOperationCount = 1
return queue
}
}
fileprivate extension YourController {
func loadImages() {
let size = CGSize(width: 87, height: 123)
let operations = myArray.enumerated().map({ ResizeOperation(image: $0.element, index: $0.offset, size: size) })
operations.forEach { [weak queue = resizeQueue, weak controller = self] (operation) in
operation.completionBlock = { [operation] in
DispatchQueue.main.async { [image = operation.image, index = operation.index] in
controller?.update(image: image, index: index)
}
}
queue?.addOperation(operation)
}
}
func update(image: UIImage, index: Int) {
myArray[index] = image
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: UITableViewRowAnimation.fade)
}
}

How to "wait' for a PopoverPresentationController to be dismissed

I am writing a "notebook" style program. Notebooks have multiple pages and I am trying to put in a "go to page" popover to allow the user to go to any page. The popover presents a collectionView of thumbnails of each page. Obviously, it would be great to:
Click on desired page thumbnail in popover
Send the selectedPage via delegate
Have the recipient mainViewController display the selectedPage
The popover works great. The page selection works and passes the selectedPage back.
The problem is that I need to have the mainViewController "wait" for the user to select a page. Here the relevant sections of code in the mainViewController:
User selects barButton to show popover:
#IBAction func selectPage(sender: UIBarButtonItem) {
self.performSegueWithIdentifier("showPages", sender: self)
//need to wait here for the popover to be dismissed.
//the next line is executed before segue even appears
//"while" and delay don't work
imageView.image = currentNotebook.pages[goToPage! - 1] //displays selectedPage (goToPage is set by delegation)
goToPage = nil
}
//segue to popover
case "showPages" :
let navigationController = segue.destinationViewController as? PageCollectionViewController
if let vc = navigationController {
vc.delegate = self
vc.modalInPopover = false
vc.preferredContentSize = CGSizeMake(400,100)
vc.notebook = self.currentNotebook
print("got to show pages")
}
I suspect I need some sort of closure or handler in the selectPage function, but I can't figure it out. Hope that is clear enough. It is very early in the morning...
Here is the code for the popover:
import UIKit
protocol PageCollectionViewControllerDelegate {
func selectsPage(selectedPage:Int)
}
class PageCollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var notebook: Notebook!
var pageNum: Int!
var delegate: PageCollectionViewControllerDelegate?
#IBOutlet weak var pageCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.pencilCollectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInCollectionView(pageCollectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return notebook.pages.count
}
func collectionView(pageCollectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = pageCollectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! PageCollectionViewCell
let cellImage = notebook.pages[indexPath.row]
let tempThumb = imageWithImage(cellImage, scaledToFillSize: CGSizeMake(cell.bounds.width, cell.bounds.height)) //create thumbnail of each page
cell.pageThumb.image = tempThumb
cell.backgroundColor = UIColor.whiteColor()
return cell
}
func collectionView(pageCollectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
//return page number
if let delegate = self.delegate {
pageNum = indexPath.row + 1
print("page number",pageNum)
delegate.selectsPage(pageNum)
}
self.dismissViewControllerAnimated(true, completion: nil) //if you comment this out the popover is not dismissed when clicking on a cell
}
func imageWithImage(image: UIImage, scaledToFillSize size: CGSize) -> UIImage {
let scale: CGFloat = max(size.width / image.size.width, size.height / image.size.height)
let width: CGFloat = image.size.width * scale
let height: CGFloat = image.size.height * scale
let imageRect: CGRect = CGRectMake((size.width - width) / 2.0, (size.height - height) / 2.0, width, height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
image.drawInRect(imageRect)
let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
This is a typical example of async programming. In this case, the async event you're waiting for is a user response.
The almost universal answer is "don't wait. Send a message and have the event notify you when it's done."
What I would do is create a custom subclass of UIViewController as your popover, and either give the popover a completion block (closure) property, or set it up with a delegate, and define a protocol so that the popover can notify it's delegate when the user selects an option. (The two approaches are quite similar, but with a completion block you don't have to define a method that gets invoked - you just pass in your completion code when you make the call that invokes the popover.)

How Do I Initialize Two Instances of NSObject in the same ViewController - Swift

Please bear with me. I'm new to programming and new to StackOverflow. I hope that my question will grant me a warm response for entering the programming community. An acquaintance, whose opinion I trust, told me to post this email to him on StackOverflow.
What do I have to do to get two instances of NSObject to work in the same ViewController? I've initialized an NSObject subclass called SideBar and RightSideBar. They both draw from NSObject. The cells in the menu are called created by a TableViewController I created programatically. I followed a tutorial that did everything programmatically because I didn't know that Storyboard is better for building things.
Below is the code base.
EDITED: Sorry for being long winded. I don't know how to make this any shorter and as complete
===========
****Note the SideBar subclass is the left menu. The RightSideBar class has the same initializer setup and is the right menu. I want to be able to make them both appear on the same ViewController in the same instance of the same ViewController if possible.****
This is the left TableViewController:
import UIKit
//this protocol of the sidebar delegate manages the selection of the item in the row.
protocol SidebarTableViewControllerDelegate {
func sidebarControlDidSelectRow(indexPath: NSIndexPath)
}
class SidebarTableViewController: UITableViewController {
//setting up the delegate and array of menu items.
var delegate:SidebarTableViewControllerDelegate?
var tableData:Array <String> = []
var imageData:[UIImage] = []
// MARK: - Table view data source
//Setting up the number of sections in the menu
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
//Setting up the number of items in the menu
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
//Setting up the menu look for the main screen after login.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
//configure the cell...
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkTextColor()
let selectedView:UIView = UIView(frame: CGRect(x: 0, y: 0, width: cell!.frame.size.width, height: cell!.frame.size.height))
selectedView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.3)
cell!.selectedBackgroundView = selectedView
}
cell!.textLabel!.text = tableData[indexPath.row]
cell!.imageView!.image = imageData[indexPath.row]
return cell!
}
//Setting up the height for each cell of the table
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 45.0
}
//Setting up the selection of the item in the cell.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.sidebarControlDidSelectRow(indexPath)
}
override func viewDidLoad() {
}
override func didReceiveMemoryWarning() {
}
}
This is the right table view controller:
//setting up the RightSidebarControllerDelegate
protocol RightSidebarTableViewControllerDelegate {
func rightSidebarControlDidSelectRow(indexPath: NSIndexPath)
}
class RightSidebarTableViewController: UITableViewController {
//setting up the delegate and array of menu items.
var delegate:RightSidebarTableViewControllerDelegate?
var rightTableData:Array <String> = []
var rightImageData:[UIImage] = []
// MARK: - Table view data source
//Setting up the number of sections in the menu
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
//Setting up the number of items in the menu
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rightTableData.count
}
//Setting up the menu look for the main screen after login.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
//configure the cell...
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkTextColor()
let selectedView:UIView = UIView(frame: CGRect(x: 0, y: 0, width: cell!.frame.size.width, height: cell!.frame.size.height))
selectedView.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.3)
cell!.selectedBackgroundView = selectedView
}
cell!.textLabel!.text = rightTableData[indexPath.row]
cell!.imageView!.image = rightImageData[indexPath.row]
return cell!
}
//Setting up the height for each cell of the table
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 45.0
}
//Setting up the selection of the item in the cell.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.rightSidebarControlDidSelectRow(indexPath)
}
override func viewDidLoad() {
}
override func didReceiveMemoryWarning() {
}
}
Here is where my problems may start with SideBar:NSObject. This is the left SideBar to be initialized:
import UIKit
#objc protocol SideBarDelegate {
func sideBarDidSelectButtonAtIndex (index: Int)
optional func sideBarWillClose()
optional func sideBarWillOpen()
optional func sideBarWillDeinitialize()
}
//this class sets up the actual sidebar.
class SideBar: NSObject, SidebarTableViewControllerDelegate {
//width of the bar, tableview setup, and views for the sidebar
let barWidth:CGFloat = 175.0
let sideBarTableViewTopInset:CGFloat = 25.0
let sideBarContainerView:UIView = UIView()
let sideBarTableViewController:SidebarTableViewController = SidebarTableViewController()
var originView:UIView!
//var for dynamic effect and controlling the sidebar
var animator:UIDynamicAnimator!
var delegate:SideBarDelegate?
var isSideBarOpen:Bool = false
//initializer for the "SideBar" class.
override init() {
super.init()
}
//initializer for the tableView of menu items.
init(sourceView: UIView, menuItems: Array<String>, menuImages: [UIImage]){
super.init()
//initializing the views and animation for the menu.
originView = sourceView
sideBarTableViewController.tableData = menuItems
sideBarTableViewController.imageData = menuImages
setupSideBar()
animator = UIDynamicAnimator(referenceView: originView)
}
//function for setting up the sidebar.
func setupSideBar () {
//setting up the frame/outline of the side bar.
sideBarContainerView.frame = CGRectMake(-barWidth, originView.frame.origin.y + 45, barWidth, originView.frame.size.height)
//setting up the color of the sidebar.
sideBarContainerView.backgroundColor = UIColor.clearColor()
//disables subviews from being confined to the sidebar.
sideBarContainerView.clipsToBounds = false
//placing the sidebar in the UIView
originView.addSubview(sideBarContainerView)
//adding blur to the menu.
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = sideBarContainerView.bounds
sideBarContainerView.addSubview(blurView)
//setting up controls for the sidebar
sideBarTableViewController.delegate = self
sideBarTableViewController.tableView.frame = sideBarContainerView.bounds
sideBarTableViewController.tableView.clipsToBounds = false
//disabling the scroll feature. Delete to keep the scroll feature.
sideBarTableViewController.tableView.scrollsToTop = false
//This will remove separators in the UITableCell. Delete to keep separators.
sideBarTableViewController.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
//This sets the background color of the sidebar and creates the inset.
sideBarTableViewController.tableView.backgroundColor = UIColor.clearColor()
sideBarTableViewController.tableView.contentInset = UIEdgeInsets(top: sideBarTableViewTopInset, left: 0, bottom: 0, right: 0)
//reloads the sidebar and adds the container view to the sideBarTableViewController.
sideBarTableViewController.tableView.reloadData()
sideBarContainerView.addSubview(sideBarTableViewController.tableView)
}
func showSideBar(shouldOpen: Bool){
animator.removeAllBehaviors()
isSideBarOpen = shouldOpen
//simple if and else statements to define the direction of animation and intensity of animation
let gravityX:CGFloat = (shouldOpen) ? 0.5 : -0.5
let magnitude:CGFloat = (shouldOpen) ? 20 : -20
let boundaryX:CGFloat = (shouldOpen) ? barWidth : -barWidth
//controls the behavior of the animation.
let gravityBehavior: UIGravityBehavior = UIGravityBehavior(items: [sideBarContainerView])
gravityBehavior.gravityDirection = CGVectorMake(gravityX, 0)
animator.addBehavior(gravityBehavior)
let collisionBehavior: UICollisionBehavior = UICollisionBehavior(items: [sideBarContainerView])
collisionBehavior.addBoundaryWithIdentifier("sideBarBoundary", fromPoint: CGPointMake(boundaryX, 20), toPoint: CGPointMake(boundaryX, originView.frame.size.height))
animator.addBehavior(collisionBehavior)
let pushBehavior:UIPushBehavior = UIPushBehavior(items: [sideBarContainerView], mode: UIPushBehaviorMode.Instantaneous)
pushBehavior.magnitude = magnitude
animator.addBehavior(pushBehavior)
let sideBarBehavior:UIDynamicItemBehavior = UIDynamicItemBehavior(items: [sideBarContainerView])
sideBarBehavior.elasticity = 0.3
animator.addBehavior(sideBarBehavior)
}
func sidebarControlDidSelectRow(indexPath: NSIndexPath) {
delegate?.sideBarDidSelectButtonAtIndex(indexPath.row)
}
}
This is the right SideBar:NSObject that will eventually initialize the right menu.
import UIKit
#objc protocol RightSideBarDelegate {
func rightSideBarDidSelectButtonAtIndex (index: Int)
optional func sideBarWillClose()
optional func sideBarWillOpen()
}
class RightSideBar: NSObject, RightSidebarTableViewControllerDelegate {
//width of the bar, tableview setup, and views for the sidebar
let barWidth:CGFloat = 175.0
let rightSideBarTableViewTopInset:CGFloat = 25.0
let rightSideBarContainerView:UIView = UIView()
let rightSideBarTableViewController:RightSidebarTableViewController = RightSidebarTableViewController()
var rightOriginView:UIView!
//var for dynamic effect and controlling the sidebar
var animator:UIDynamicAnimator!
var delegate:RightSideBarDelegate?
var isSideBarOpen:Bool = false
//initializer for the "SideBar" class.
override init() {
super.init()
}
//initializer for the tableView of menu items.
init(rightSourceView: UIView, rightMenuItems: Array<String>, rightMenuImages: [UIImage]){
super.init()
//initializing the views and animation for the menu.
rightOriginView = rightSourceView
rightSideBarTableViewController.rightTableData = rightMenuItems
rightSideBarTableViewController.rightImageData = rightMenuImages
setupSideBar()
animator = UIDynamicAnimator(referenceView: rightOriginView)
}
//function for setting up the sidebar.
func setupSideBar () {
//setting up the frame/outline of the side bar.
rightSideBarContainerView.frame = CGRectMake(rightOriginView.frame.size.width + barWidth , rightOriginView.frame.origin.y + 45, barWidth, rightOriginView.frame.size.height)
//setting up the color of the sidebar.
rightSideBarContainerView.backgroundColor = UIColor.clearColor()
//disables subviews from being confined to the sidebar.
rightSideBarContainerView.clipsToBounds = false
//placing the sidebar in the UIView
rightOriginView.addSubview(rightSideBarContainerView)
//adding blur to the menu.
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = rightSideBarContainerView.bounds
rightSideBarContainerView.addSubview(blurView)
//setting up controls for the sidebar
rightSideBarTableViewController.delegate = self
rightSideBarTableViewController.tableView.frame = rightSideBarContainerView.bounds
rightSideBarTableViewController.tableView.clipsToBounds = false
//disabling the scroll feature. Delete to keep the scroll feature.
rightSideBarTableViewController.tableView.scrollsToTop = false
//This will remove separators in the UITableCell. Delete to keep separators.
rightSideBarTableViewController.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
//This sets the background color of the sidebar and creates the inset.
rightSideBarTableViewController.tableView.backgroundColor = UIColor.clearColor()
rightSideBarTableViewController.tableView.contentInset = UIEdgeInsets(top: rightSideBarTableViewTopInset, left: 0, bottom: 0, right: 0)
//reloads the sidebar and adds the container view to the rightSideBarTableViewController.
rightSideBarTableViewController.tableView.reloadData()
rightSideBarContainerView.addSubview(rightSideBarTableViewController.tableView)
}
func showSideBar(shouldOpen: Bool){
animator.removeAllBehaviors()
isSideBarOpen = shouldOpen
//simple if and else statements to define the direction of animation and intensity of animation
let gravityX:CGFloat = (shouldOpen) ? -0.5 : 0.5
let magnitude:CGFloat = (shouldOpen) ? -20 : 20
let boundaryX:CGFloat = (shouldOpen) ? -barWidth : barWidth
//controls the behavior of the animation.
let gravityBehavior: UIGravityBehavior = UIGravityBehavior(items: [rightSideBarContainerView])
gravityBehavior.gravityDirection = CGVectorMake(gravityX, 0)
animator.addBehavior(gravityBehavior)
let collisionBehavior: UICollisionBehavior = UICollisionBehavior(items: [rightSideBarContainerView])
collisionBehavior.addBoundaryWithIdentifier("sideBarBoundary", fromPoint: CGPointMake(boundaryX, 20), toPoint: CGPointMake(boundaryX, rightOriginView.frame.size.height))
animator.addBehavior(collisionBehavior)
let pushBehavior:UIPushBehavior = UIPushBehavior(items: [rightSideBarContainerView], mode: UIPushBehaviorMode.Instantaneous)
pushBehavior.magnitude = magnitude
animator.addBehavior(pushBehavior)
let sideBarBehavior:UIDynamicItemBehavior = UIDynamicItemBehavior(items: [rightSideBarContainerView])
sideBarBehavior.elasticity = 0.3
animator.addBehavior(sideBarBehavior)
}
func rightSidebarControlDidSelectRow(indexPath: NSIndexPath) {
delegate?.rightSideBarDidSelectButtonAtIndex(indexPath.row)
}
}
Finally this is my current code for the DoubleMenuViewController. Something happens when I segue to the DoubleMenuViewController to break the menus. The menus won't even load. However, if I'm in a SingleMenuViewController that only calls SideBar:NSObject then the code will work so long as I'm only calling one menu. In this DoubleMenuViewController, I have the initialization section commented out for the RightSideBar class because I'm working on a solution. I know this code for this ViewController is garbled. I'm trying everything I can think of. See my remarks after the code to see what I've tried:
import UIKit
class DoubleMenuViewController: UIViewController, SideBarDelegate, RightSideBarDelegate {
var sideBar:SideBar?
var ondemandSideBar:SideBar {
get {
if sideBar == nil {
//setting up the menu items for the sidebar.
sideBar = SideBar(sourceView: self.view, menuItems: ["Home", "Share", "About", "Help"], menuImages: [homeImage!, shareImage!, aboutImage!, helpImage!])
sideBar!.delegate = self
SideBar.new()
}
return sideBar!
}
}
//initializes the "RightSideBar"
var rightSideBar:RightSideBar?
var ondemandRightSideBar:RightSideBar {
get {
if rightSideBar == nil {
rightSideBar = RightSideBar(rightSourceView: self.view, rightMenuItems: [//Other items], rightMenuImages: [//Other Items])
rightSideBar!.delegate = self
RightSideBar.new()
}
return rightSideBar!
}
}
var homeImage = UIImage(named: "Home")
var shareImage = UIImage(named: "Share")
var aboutImage = UIImage(named: "About")
var helpImage = UIImage(named: "Help")
#IBOutlet weak var currentMenuControl: UIBarButtonItem!
#IBAction func currentMenuDisplay(sender: AnyObject) {
if currentMenuControl.tag == 1 {
ondemandSideBar.showSideBar(true)
currentMenuControl.tag = 0
} else {
ondemandSideBar.showSideBar(false)
currentMenuControl.tag = 1
}
}
#IBOutlet weak var progressionMenuControl: UIBarButtonItem!
#IBAction func progressionMenuDisplay(sender: AnyObject) {
if progressionMenuControl.tag == 1 {
ondemandRightSideBar.showSideBar(true)
progressionMenuControl.tag = 0
} else {
ondemandRightSideBar.showSideBar(false)
progressionMenuControl.tag = 1
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func sideBarDidSelectButtonAtIndex(index: Int) {
switch index {
//segues
}
}
func rightSideBarDidSelectButtonAtIndex(index: Int) {
switch index {
//segues
}
}
}
Here's what I've tried:
I've tried altering the positioning of CGFloats since SubViews seem
to come from the left.
I've renamed all the RightSideBar variables and class names
everything to overcome a runtime confusion in instance variables and
class names. This includes renaming the initializers that you saw in
the NSObject SubClass and the target view controller.
I've tried using control flow in the viewDidLoad method with a
button tag. I took away the swipe features to show the menu and
added buttons because I thought system was struggling to deal with
the Swipes.
I've tried deinitializing in the SideBar subclass file of NSObject.
All that got me was an infinite loop that crashed the application
after login.
Then I tried ondemand initialization in the
targetViewController.....DoubleMenuViewController and
SingleMenuViewController. I returned to a working menu with buttons
in the SingleMenuViewController but it still won't show the left and
right menu in the DoubleMenuViewController.
Last I tried deinitializing the SideBar (left SideBar) and the RightSideBar in the DoubleMenuViewController. However, when I add println() functions to all my sections the debugger doesn't run the print function for me to get values of objects or even show typed states like "This". I added the print functions because I wasn't sure if I would know when deinitialization and reinitialization occurred.
It seems that my menu is initialized from the SideBar: NSObject file and the RightSideBar:NSObject file. What I mean is that my menu is being created before I hit the target view controller. This isn't a problem for me so long as I can get the compiler to initialize the SideBar and the RightSideBar in the same View Controller, but it won't do that.
I just need to be able to control both menus with swipes or button taps.
I think I have a problem with my initializers overriding each other.
However, I don't know how to fix that problem. I've read through the Swift manual and read articles on the internet. I've also searched StackOverflow.
You ask:
How do I Initialize two instances of NSObject in the same view controller?
Setting aside why you're dealing with NSObject at all (in Objective-C all classes have to subclass from NSObject ultimately, in Swift that's no longer the case), if you want to instantiate two objects, you simply have to have one property for each.
If these are lazily instantiated, as your code snippet suggests, then you have to identify where that lazily instantiated property is referenced (e.g. you might trigger it from a "swipe from edge" gesture) or what have you. Set a breakpoint in the code that references that lazily instantiated property and make sure you're getting there at all.
--
I had some observations on one of your code snippets. You say that you're instantiating your side bar like so:
var sideBar : SideBar?
var ondemandSideBar : SideBar {
get {
if sideBar == nil {
sideBar = SideBar(sourceView, menuItems, menuImage etc.)
sideBar!.delegate
SideBar.new()
}
}
}
I don't think that's what you're really doing because you're not setting the delegate, you're both instantiating the SideBar as well as calling new (which you shouldn't be doing from Swift), you're not returning a value, etc.
Also, that pattern of having a stored property that is instantiated by some computed property has a certain Objective-C je ne sais quoi. I'm inferring that you want a lazily instantiated property. If that's the case, I'd be inclined to use a single lazy stored property. And I'd then set that property lazily using a closure:
I'd expect something like this pattern
protocol SideBarDelegate : class { // specify class so we can use `weak` reference
func didChooseMenuItem(sender: SideBar, item: Int)
}
class SideBar {
weak var delegate: SideBarDelegate? // make sure this is weak to avoid strong reference cycle
}
class ViewController: UIViewController, SideBarDelegate {
lazy var sideBar: SideBar = {
let _sideBar = SideBar(sourceView, menuItems, menuImage, etc.)
_sideBar.delegate = self
return _sideBar
}()
func didChooseMenuItem(sender: SideBar, item: Int) {
// handle it here
}
// etc.
}
This way, the sideBar won't be instantiated until you reference sideBar somewhere in your code, but when you do, it will be instantiated with the code inside that closure.

How to Stop UI thread until Network Thread Completes?

I am displaying some data from JSON into my AccordionView. The problem is that my View thread completes executing before my Network Thread. So I Can't populate the Data which I get from the Array to my AccordionView.
So it shows that Array index out of range exception. I also tried using the reloadData Method But it is of no use.
I researched this issue and found about GCD. I can't figure out how to use GCD for this issue.
This is the AccordionView Library.
import UIKit
import Alamofire
import SwiftyJSON
var CourseIdinController : String!
var CourseDescriptionC: String!
var ChapNameC : [String] = []
var titleLabel : UILabel?
class CoursePageController: UIViewController {
#IBOutlet weak var courseDesc: UITextView!
var CourseName : String!
var ChapName : [String] = []
var LessonName : [String] = []
var myAccordionView : MKAccordionView?
override func viewDidLoad() {
super.viewDidLoad()
self.title = CourseName
self.courseDesc.text = CourseDescriptionC
self.courseDesc.setContentOffset(CGPointZero, animated: true)
view.bounds.size.height = 450.0
myAccordionView = MKAccordionView(frame: CGRectMake(0, 111, CGRectGetWidth(view.bounds), CGRectGetHeight(view.bounds)));
println(ChapNameC.count)
myAccordionView!.delegate = self;
myAccordionView!.dataSource = self;
view.addSubview(myAccordionView!);
getData()
getlData()
}
func getData(){
Alamofire.request(.GET, "http://www.wgve.com/index.php/capp/get_chapter_by_course_id/\(CourseIdinController)")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
let catCount = json.count
for index in 0...catCount-1 {
let cname = json[index]["CHAPTER_NAME"].string
self.ChapName.append(cname!)
println(self.ChapName[index])
}
self.myAccordionView!.tableView?.reloadData()
}}
func getlData(){
Alamofire.request(.GET, "http://www.wgve.com/index.php/capp/get_lesson_by_course_id/\(CourseIdinController)")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
let catCount = json.count
for index in 0...catCount-1 {
let cname = json[index]["LESSON_NAME"].string
self.LessonName.append(cname!)
}
self.myAccordionView!.tableView?.reloadData()
}}
func accordionView(accordionView: MKAccordionView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell? = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: nil)
//cell?.imageView = UIImageView(image: UIImage(named: "lightGrayBarWithBluestripe"))
// Background view
var bgView : UIView? = UIView(frame: CGRectMake(0, 0, CGRectGetWidth(accordionView.bounds), 50))
var bgImageView : UIImageView! = UIImageView(image: UIImage(named: "lightGrayBarWithBluestripe"))
bgImageView.frame = (bgView?.bounds)!
bgImageView.contentMode = UIViewContentMode.ScaleToFill
bgView?.addSubview(bgImageView)
cell?.backgroundView = bgView
// You can assign cell.selectedBackgroundView also for selected mode
cell?.textLabel?.text = LessonName[0]
return cell!
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: - Implemention of MKAccordionViewDatasource method
extension CoursePageController : MKAccordionViewDatasource {
func numberOfSectionsInAccordionView(accordionView: MKAccordionView) -> Int {
println(ChapName.count)
return ChapName.count
}
func accordionView(accordionView: MKAccordionView, numberOfRowsInSection section: Int) -> Int {
println(LessonName.count)
return LessonName.count
}
}
// MARK: - Implemention of MKAccordionViewDatasource method
extension CoursePageController : MKAccordionViewDatasource {
func numberOfSectionsInAccordionView(accordionView: MKAccordionView) -> Int {
return ChapName.count
}
func accordionView(accordionView: MKAccordionView, numberOfRowsInSection section: Int) -> Int {
return LessonName.count
}
}
// MARK: - Implemention of MKAccordionViewDelegate method
extension CoursePageController : MKAccordionViewDelegate {
func accordionView(accordionView: MKAccordionView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 50
}
func accordionView(accordionView: MKAccordionView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func accordionView(accordionView: MKAccordionView, viewForHeaderInSection section: Int, isSectionOpen sectionOpen: Bool) -> UIView? {
var view : UIView! = UIView(frame: CGRectMake(0, 0, CGRectGetWidth(accordionView.bounds), 50))
// Background Image
var bgImageView : UIImageView = UIImageView(frame: view.bounds)
bgImageView.image = UIImage(named: ( sectionOpen ? "grayBarSelected" : "grayBar"))!
view.addSubview(bgImageView)
// Arrow Image
var arrowImageView : UIImageView = UIImageView(frame: CGRectMake(15, 15, 20, 20))
arrowImageView.image = UIImage(named: ( sectionOpen ? "close" : "open"))!
view.addSubview(arrowImageView)
// Title Label
titleLabel = UILabel(frame: CGRectMake(50, 0, CGRectGetWidth(view.bounds) - 120, CGRectGetHeight(view.bounds)))
if (ChapName.count != 0) {
let count = ChapName.count
for index in 0...count-1 {
titleLabel!.text = self.ChapName[index]
}
}
titleLabel!.textColor = UIColor.whiteColor()
view.addSubview(titleLabel!)
return view
}
}
Note:
It shows me error because the ChapName & lesson name array are Empty. If i Hardcode the values. It prints both the array's in my console.
May be the accordionView doesn't support the reload Method?
or Where am I Going Wrong ?
UPDATE
numberOfSectionsInAccordionView throws Error like this fatal error: Can't form Range with end < start which on line 233
Refer this link
2.The titleLabel returns only the last item in the Array. I don't know Why ?
You should make sure all your json data is successfully stored into an array "accordionDataArray" before you attempt to reload it.
ATTENTION : make sure that the datasource methods numberOfRowsInSection and numberOfSectionsInAccordionView rely on the accordionDataArray content (or whatever Array you have) to determine the number of sections and rows of the accordion.
P.S. Anyway Why would you reload the content of the accordion in a loop?
Do this instead:
for index in 0...catCount-1 {
let cname = json[index]["CHAPTER_NAME"].string
self.ChapName.append(cname!)
println(self.ChapName[index])
}
self.myAccordionView!.tableView?.reloadData()
UPDATE:
To get passed the fact that ChapName & lessonName array are asked for some value when they are Empty , you need to check that like this whenever they occur:
if (ChapName.count != 0) {
titleLabel!.text = self.ChapName[0]
}
if (LessonName.count != 0){
// do something
}
Make sure you update your code and test. In the method:
func accordionView(accordionView: MKAccordionView, viewForHeaderInSection section: Int, isSectionOpen sectionOpen: Bool) -> UIView? {
[...]
if (ChapName.count != 0) {
titleLabel!.text = self.ChapName[0]
}
[...]
}
UPDATE 2: The library you are using doesn't seem to allow the number of sections to be 0. I suggest you contact the library's owner if you can't make sure that the number of section is at least 1.
Regarding to your other issue with titleLabel being always the last item of the array you need to change this:
if (ChapName.count != 0) {
let count = ChapName.count
for index in 0...count-1 {
titleLabel!.text = self.ChapName[index]
}
}
into this:
titleLabel!.text = self.ChapName[section]
About why the code above does not work:
numberOfRowsInSection returns a hardcoded value, regardless of the JSON response.
About stopping the UI thread:
We are somewhat glad you can't do that. GCD helps you to share processor time efficiently, the exact opposite of holding the UI thread hostage. Image the consequence if somehow your network thread would hang. What would happen to the UI thread? Don't do it.
Solutions: Your make your UI thread clever enough to handle absence of datum. i.e. do not return hardcoded counts for sections or cells. When you receive data, update your records (the model), send a refresh message to the UI (such as reloadData in UITableView).

Resources