Swift map change points if change SegmentedControl - ios

I'm trying to work with Mapbox.
I have created a point on my map and show from file
I want that when SegmentedControl is changed, item.type is changed accordingly and get correct data
For example if press 0 case -> make 0 == item.type
I create custom points
func addItemsToMap(features: [MGLPointFeature]) {
guard let style = mapView.style else { return }
let source = MGLShapeSource(identifier: "mapPoints", features: features, options: nil)
style.addSource(source)
let colors = [
"black": MGLStyleValue(rawValue: UIColor.black)
]
let circles = MGLCircleStyleLayer(identifier: "mapPoints-circles", source: source)
circles.circleColor = MGLSourceStyleFunction(interpolationMode: .identity,
stops: colors,
attributeName: "color",
options: nil)
circles.circleRadius = MGLStyleValue(interpolationMode: .exponential,
cameraStops: [2: MGLStyleValue(rawValue: 5),
7: MGLStyleValue(rawValue: 8)],
options: nil)
circles.circleStrokeWidth = MGLStyleValue(rawValue: 2)
circles.circleStrokeColor = MGLStyleValue(rawValue: UIColor.white)
style.addLayer(circles)
}
Put data to mapbox
func test(number: Int) {
guard let documentsDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let urlBar = documentsDirectoryUrl.appendingPathComponent("Persons.json")
do {
let jsonData = try Data(contentsOf: urlBar)
//Переводим их в модель
let result = try JSONDecoder().decode(CollectionTest.self, from: jsonData)
let coll: CollectionTest = result
let features = parseJSONItems(collection: coll, number: number)
addItemsToMap(features: features)
print(features)
} catch { print("Error while parsing: \(error)") }
}
Get data from file
func parseJSONItems(collection: CollectionTest, number: Int) -> [MGLPointFeature] {
var features = [MGLPointFeature]()
for item in collection.prices {
if item.type == number {
...get data to annotation and location
let feature = MGLPointFeature()
feature.coordinate = coordinate
feature.title = "\(name)"
feature.attributes = [
"color" : color,
"name" : "\(name)"
]
features.append(feature)
}
}
}}}
}
return features
}
I need to change number from 0 to 4, because in data I have type from 0 to 4 and points need to change from 0-4 if SegmentedControl is changed
in SegmentedControl I have
#objc func change(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
test(number: 0)
case 1:
test(number: 1)
case 2:
ttest(number: 2)
case 3:
test(number: 3)
case 4:
test(number: 4)
default:
test(number: 2)
}
}
In viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
styleToggle.addTarget(self, action: #selector(change(sender:)), for: .valueChanged)
test(number: 2)
}
When app run - works good and show all data == type 2
But when I change button in SegmentedControl to case 0 or others - I get a crash and in console prints
Terminating app due to uncaught exception
'MGLRedundantSourceIdentifierException', reason: 'Source mapPoints already exists'
What am I doing wrong? How to fix this?

It looks like you are trying to add a source to your map that already exists. In order to avoid this message, you will want to either:
Check for the existence of a source before adding it to your style. In your use case, maybe name your sources mapPoints-circles-\(number). If the source already exists, reuse it.
Add the source to the map one time (preferably within the initial -mapView:didFinishLoadingStyle:). In order to create new layers from it layer, you can access the source from the map's style.
If the points come from a source with an identifier my-source, you would try:
guard let source = style.source(withIdentifier: "my-source") else { return }

when clicking a segment, remove all markers which are already present in the map
then add new markers
You can use map.removeLayer(marker); to remove the marker (an ILayer object).

Related

How to Convert GMUFeature to GMSPath

I have drawn slots on google maps using Geojson. Now when the user long presses on the slot should ask the user whether to save the location or not. But I don't know how to enable long press only for the slots. Could anybody suggest me how to do this. Below is the code I have used.
`// the below is the code to render the slots on the google maps
let geoJsonParser = GMUGeoJSONParser(url: url!)
geoJsonParser.parse()
let renderer = GMUGeometryRenderer(map: self.mapview, geometries: geoJsonParser.features)
let style = GMUStyle(styleID: "random", stroke: UIColor.black, fill: UIColor.green, width: 2, scale: 1, heading: 0, anchor: CGPoint(x: 0, y: 0), iconUrl: nil, title: nil, hasFill: true, hasStroke: true)
for feature in geoJsonParser.features {
feature.style = style
}
renderer.render()
// The below code is to check whether the user has long pressed on the slot or not(written this code inside the didLongPressAt function).
for feature in geoJsonParser.features {
if GMSGeometryContainsLocation(coordinate, feature as! GMSPath, true) {// the app is crashing with an error in the console "Could not cast value of type 'GMUFeature' (0x1033573d0) to 'GMSPath' (0x1033585a0)"
print("YES: you are in this polygon.")
marker.title = "\(coordinate)"
marker.map = mapview
} else {
print("You do not appear to be in this polygon.")
}
}`
Finally, after lot of efforts I found the solution.
Note: If anyone is integrating Google-Maps-iOS-Utils library, then don't add header files in Bridging-Header file as suggested in Google developer document.
Wherever you are using the below code, just write import GoogleMapsUtils at top.
let path = Bundle.main.path(forResource: "dld_areas", ofType: "json")
let url = URL(fileURLWithPath: path!)
let data: Data = try! Data.init(contentsOf: url)
let json = try JSON(data: data)
//our json is having properties containing area name and area id, bacause the properties field inside the GMUFeature is retuning us nil, we have to pass it indiviually
let jsonFeatures: [JSON] = json["features"].array ?? []
geoJsonParser = GMUGeoJSONParser.init(data: data)
geoJsonParser.parse()
var index = 0
for feature in geoJsonParser.features {
var innerFeature = feature
while(innerFeature.geometry.type != "GeometryCollection"){
print("type is" + innerFeature.geometry.type)
innerFeature = innerFeature.geometry as! GMUGeometryContainer
}
let collection = innerFeature.geometry as! GMUGeometryCollection
let polygon:GMUPolygon = collection.geometries.first as! GMUPolygon
var isAreaObtained = false
for path in polygon.paths{
//check if the coordinates lie within the polygon
if (GMSGeometryContainsLocation(coordinates, path, true)) {
isAreaObtained = true
print(jsonFeatures[index]["properties"]["NAME_EN"])
print(jsonFeatures[index]["properties"]["AREA_ID"])
print("location inside polygon")
break;
}
}
if(isAreaObtained){
break
}
index = index + 1
}
I found the above code in the following link:
https://github.com/googlemaps/google-maps-ios-utils/issues/205
I have used this code and modified as per my requirement.

Swift considerable jump in tableview with just one query in cellforrow

I have the following code in my willDisplay:
func tableView(_: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard case let cell as L_LocCell = cell else {
return
}
let subC = subContractors[indexPath.row]
cell.backgroundColor = .clear
cell.locName = subC.companyname
cell.locCode = subC.region
/*if let sType = Helper_Types.getType(subC.type)
{
cell.add = sType.name
}
else {
cell.add = ""
}*/
switch subC.status {
case 3:
cell.catColor = UIColor(rgba: Palette.class_bad)
cell.catName = "Declined"
break
case 2:
cell.catColor = UIColor(rgba: Palette.class_good)
cell.catName = "Approved"
break
case 1:
cell.catColor = UIColor(rgba: Palette.class_warn)
cell.catName = "Pending"
break
case 4:
cell.catColor = UIColor(rgba: Palette.ok)
cell.catName = "Approved with Exception"
break
default:
cell.catColor = companyMed
cell.catName = "Unknown"
break
}
if subConPicDir != nil {
let filename = subConPicDir!.appendingPathComponent(subC.subcontractorId.description+".jpg")
cell.thumbImg.kf.setImage(
with: filename,
placeholder: UIImage(named: "ic_supplier")!,
options: [.transition(.fade(1)), .cacheOriginalImage],
progressBlock: { receivedSize, totalSize in
},
completionHandler: { result in
print(result)
})
}
else{
cell.thumbImg.image = UIImage(named: "ic_supplier")
}
}
There is a considerable difference in the smoothness of the scrolling when i put back in the commented out portion.
This is just a query to retrieve some info, i didn't have directly in my tableview's data source. How can I optimise this?
public static func getType(_ tid: Int) -> Types?
{
do {
var realm : Realm? = try Realm()
let predicate = NSPredicate(format: "_id = %d", tid);
let types = realm!.objects(STD_type.self).filter(predicate);
realm = nil
if types.count > 0
{
return Types(st : types.first!)
} else {
return nil;
}
} catch let error as NSError {
print("error realm \(error.localizedDescription)")
return nil
}
}
A Realm lookup is usually quite fast but it is still an async task which takes time and may even run on another thread.
Rather than doing this every time you attempt to render a cell I would suggest that you fetch all Types (maybe in viewDidLoad) and then just filter in cellForRow
Then you can just do something like
cell.add = types.first { $0.id == subC.type } ?? ""
Which is not aysnc and will be much faster and more responsive
If for some reason you can't fetch all types, I would at least cache the results as you get them.

How to change marker for locations other than destination in Mapbox?

For navigation feature in my app, I am using Mapbox SDK. Following is a snippet that I am using.
func showNavigationMap() {
let origin = Waypoint(coordinate: currentLocation.coordinate, name: "Your Location")
guard pickUpCoordinate != nil, dropOffCoordinate != nil else {
showAlertMessage("Locations not generated")
return
}
let pickUpLocation = Waypoint(coordinate: pickUpCoordinate, name: "Pickup Location")
let deliveryLocation = Waypoint(coordinate: dropOffCoordinate, name: "Delivery Location")
let options = NavigationRouteOptions(waypoints: [origin, pickUpLocation, deliveryLocation])
Directions.shared.calculate(options) { (waypoints, routes, error) in
guard let route = routes?.first else {
self.showAlertMessage("No possible routes detected")
return
}
self.mapNavigationViewController = NavigationViewController(for: route)
self.mapNavigationViewController.delegate = self
self.present(self.mapNavigationViewController, animated: true, completion: {
print("Navigation shown")
})
}
}
The sample screen is as shown below.
The first stop location is indicated as "1". I would like this location to be represented by some custom marker image. I have tried the mapbox documentation (https://www.mapbox.com/ios-sdk/navigation/examples/custom-destination-marker/). However, I could change the destination marker only. Is it possible to change the marker for the desired location?
Here is the workaround, I have tested it and it works.
extension NavigationManager: NavigationViewControllerDelegate {
public func navigationViewController(_ navigationViewController: NavigationViewController, waypointStyleLayerWithIdentifier identifier: String, source: MGLSource) -> MGLStyleLayer? {
let waypointStyleLayer = MGLCircleStyleLayer(identifier: identifier, source: source)
// Way to custom waypoint style
//waypointStyleLayer.circleColor = NSExpression(forConstantValue: UIColor.yellow)
//waypointStyleLayer.circleRadius = NSExpression(forConstantValue: 12)
//waypointStyleLayer.circleStrokeColor = NSExpression(forConstantValue: UIColor.black)
//waypointStyleLayer.circleStrokeWidth = NSExpression(forConstantValue: 2)
// Hides waypoint
waypointStyleLayer.circleOpacity = NSExpression(forConstantValue: 0)
waypointStyleLayer.circleStrokeOpacity = NSExpression(forConstantValue: 0)
return waypointStyleLayer
}
public func navigationViewController(_ navigationViewController: NavigationViewController, waypointSymbolStyleLayerWithIdentifier identifier: String, source: MGLSource) -> MGLStyleLayer? {
let waypointSymbolStyleLayer = MGLSymbolStyleLayer(identifier: identifier, source: source)
// Way to custom waypoint symbol
//waypointSymbolStyleLayer.text = NSExpression(forKeyPath: "title")
//waypointSymbolStyleLayer.textColor = NSExpression(forConstantValue: UIColor.white)
return waypointSymbolStyleLayer
}
}
Reference:
https://github.com/mapbox/mapbox-navigation-ios/issues/1893

UISegment stays at same row when tableview get scrolled

I have this problem, I have a tableview with 3 different kind of news manage by segmented control. When I scrolled the news stays for example in the fifth new, if I click in the second segment, appears in the position 5 . Is not showing the news from the segment 1(the new one),at row 0 (beginning) , stays in the position I leave when I was scrolling. Why is happening this? what I'm doing wrong?. I'm using one tableview for the 3 different kinds of news and reload the tableview data every time I change the segment.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
NSLog("selectedSegmentIndex: \(self.sectionSegmentedControl.selectedSegmentIndex) - Row: \(indexPath.row)")
let cell:UITableViewCell = self.tableview.dequeueReusableCellWithIdentifier("Cell")!
// Grab the elements using the tag
let labelTitle:UILabel? = cell.viewWithTag(1) as! UILabel?
let labelSection:UILabel? = cell.viewWithTag(2) as! UILabel?
let labelDate:UILabel? = cell.viewWithTag(3) as! UILabel?
let imageView:UIImageView? = cell.viewWithTag(4) as! UIImageView?
// Check which segment to get data from
switch self.sectionSegmentedControl.selectedSegmentIndex {
case 0:
// If segment is 0, take data from cover News
if (indexPath.row <= self.coverNews.count - 1){
//Current new to display
let currentNewToDisplay = self.coverNews[indexPath.row]
//let currentNewToDisplay = self.news[indexPath.row]
// Get the image and assign to the imageView
if let actualImageView = imageView {
// Imageview actually exists
if currentNewToDisplay.imageUrl != "" {
// Image url exists, so download it
let imageUrl:NSURL? = NSURL(string: currentNewToDisplay.imageUrl)
// Download image with SDWebImage library
if let url = imageUrl {
actualImageView.sd_setImageWithURL(url)
}
}
}
// Get the news title and assign to the label
if let actualLabelTitle = labelTitle {
let title = currentNewToDisplay.title
actualLabelTitle.text = title
actualLabelTitle.numberOfLines = 0
actualLabelTitle.minimumScaleFactor = 0.1
}
// Get the news date and assign to the label
if let actualLabelDate = labelDate {
let character = "| "
actualLabelDate.text = character + currentNewToDisplay.date_short
}
// Get the news section and assign to the label
if let actualabelSection = labelSection {
actualabelSection.text = currentNewToDisplay.section
}
}
case 1:
// If segment is 1, take data from toprated News
if (indexPath.row <= self.topratedNews.count - 1){
let currentNewToDisplay2 = self.topratedNews[indexPath.row]
// Get the image and assign to the imageView
if let actualImageView2 = imageView {
// Imageview actually exists
if currentNewToDisplay2.imageUrl != "" {
// Image url exists, so download it
let imageUrl2:NSURL? = NSURL(string: currentNewToDisplay2.imageUrl)
// Download image with SDWebImage library
if let url2 = imageUrl2 {
actualImageView2.sd_setImageWithURL(url2)
}
}
}
// Get the news title and assign to the label
if let actualLabelTitle2 = labelTitle {
actualLabelTitle2.text = currentNewToDisplay2.title
actualLabelTitle2.numberOfLines = 0
actualLabelTitle2.minimumScaleFactor = 0.1
}
// Get the news date and assign to the label
if let actualLabelDate2 = labelDate {
let character2 = "| "
actualLabelDate2.text = character2 + currentNewToDisplay2.date_short
}
// Get the news section and assign to the label
if let actualabelSection2 = labelSection {
actualabelSection2.text = currentNewToDisplay2.section
}
}
case 2:
if (indexPath.row <= self.latestNews.count - 1){
// If segment is 2, take data from latestNews News
let currentNewToDisplay3 = self.latestNews[indexPath.row]
// Get the image and assign to the imageView
if let actualImageView3 = imageView {
// Imageview actually exists
if currentNewToDisplay3.imageUrl != "" {
// Image url exists, so download it
let imageUrl3:NSURL? = NSURL(string: currentNewToDisplay3.imageUrl)
// Download image with SDWebImage library
if let url3 = imageUrl3 {
actualImageView3.sd_setImageWithURL(url3)
}
}
}
// Get the news title and assign to the label
if let actualLabelTitle3 = labelTitle {
actualLabelTitle3.text = currentNewToDisplay3.title
actualLabelTitle3.numberOfLines = 0
actualLabelTitle3.minimumScaleFactor = 0.1
}
// Get the news date and assign to the label
if let actualLabelDate3 = labelDate {
let character3 = "| "
actualLabelDate3.text = character3 + currentNewToDisplay3.date_short
}
// Get the news section and assign to the label
if let actualabelSection3 = labelSection {
actualabelSection3.text = currentNewToDisplay3.section
}
}
default:
break
}
// Set insets to zero
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
// MARK: Segmented Control
#IBAction func segmentedChanged(sender: UISegmentedControl) {
switch self.sectionSegmentedControl.selectedSegmentIndex {
case 0:
// If segment is 0, return rows for coverNews array
if (self.coverNews.count == 0) {
loadNews()
}else{
dispatch_async(dispatch_get_main_queue(), {
self.tableview.reloadData()
})
}
case 1:
// If segment is 1, return rows for topratedNews array
if (self.topratedNews.count == 0) {
loadNews()
}else{
dispatch_async(dispatch_get_main_queue(), {
self.tableview.reloadData()
})
}
case 2:
// If segment is 2, return rows for latestNews array
if (self.latestNews.count == 0) {
loadNews()
}else{
dispatch_async(dispatch_get_main_queue(), {
self.tableview.reloadData()
})
}
default:
break
}
}
// MARK: Load News
func loadNews(){
switch(sectionSegmentedControl.selectedSegmentIndex){
case 0:
self.model.getFeedNews("cover")
case 1:
self.model.getFeedNews("toprated")
case 2:
self.model.getFeedNews("latest")
default:
break
}
}

App crash on sendEvent method

When I rotate the app twice after selecting a few items, it crashes. I have overridden the sendEvent method and that's where the debugger stops. When I try to print the event type, it shows me something weird (I think it's a memory location that doesn't exist):
(lldb) print event.type
(UIEventType) $R10 = <invalid> (0xff)
Somehow I think this is related to how I handle the rotation. I have a master-detail style application, that uses a different type of navigation for pad-landscape, pad-portrait and phone. I have created a class named NavigationFlowController which handles all navigational events and sets up the views accordingly. On rotation, it breaks up the view trees and recomposes them with the correct navigation
func changeViewHierarchyForDevideAndOrientation(newOrientation:UIInterfaceOrientation? = nil){
print("MA - Calling master layout method")
UIApplication.myDelegate().window?.frame = UIScreen.mainScreen().bounds
let idiom = UIDevice.currentDevice().userInterfaceIdiom
var orientation:UIInterfaceOrientation!
if let no = newOrientation{
orientation = no
}else{
orientation = UIApplication.sharedApplication().statusBarOrientation
}
print("MA - Breaking up view tree...")
breakupFormerViewTree([sidebarViewController, listViewController, detailViewController, loginViewController])
print("MA - Start init navbackbone")
initNavBackboneControllers()
guard let _ = UIApplication.myDelegate().currentUser else {
if idiom == UIUserInterfaceIdiom.Phone{
currentState = AppState.PHONE
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsLandscape(orientation){
currentState = AppState.PAD_LANDSCAPE
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsPortrait(orientation){
currentState = AppState.PAD_PORTRAIT
}
print("MA - Current user is nil - resetting")
mainViewController.addChildViewController(loginViewController)
return
}
if idiom == UIUserInterfaceIdiom.Phone{
currentState = AppState.PHONE
leftNavigationController?.viewControllers = [listViewController]
slideViewController?.rearViewController = sidebarViewController
slideViewController?.frontViewController = leftNavigationController
slideViewController?.rearViewRevealWidth = 267;
mainViewController.addChildViewController(slideViewController!)
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsLandscape(orientation){
currentState = AppState.PAD_LANDSCAPE
leftNavigationController!.viewControllers = [sidebarViewController, listViewController]
rightNavigationController!.viewControllers = [detailViewController]
detailViewController.navigationItem.leftBarButtonItems = []
detailViewController.initLayout()
print("MA - Init split view controller with VCs")
splitViewController!.viewControllers = [leftNavigationController!, rightNavigationController!]
mainViewController.addChildViewController(splitViewController!)
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsPortrait(orientation){
currentState = AppState.PAD_PORTRAIT
leftNavigationController!.pushViewController(sidebarViewController, animated: false)
leftNavigationController!.pushViewController(listViewController, animated: false)
rightNavigationController!.pushViewController(detailViewController, animated: false)
rightNavigationController?.setNavigationBarHidden(false, animated: false)
slideViewController!.rearViewController = leftNavigationController
slideViewController!.frontViewController = rightNavigationController
detailViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "< Documenten", style: UIBarButtonItemStyle.Bordered, target: slideViewController, action: "revealToggle:")
detailViewController.initLayout()
slideViewController!.rearViewRevealWidth = 350;
mainViewController.addChildViewController(slideViewController!)
}
}
func breakupFormerViewTree(vcs:[UIViewController?]){
for vc in vcs{
if let vcUnwrapped = vc, _ = vcUnwrapped.parentViewController {
vcUnwrapped.removeFromParentViewController()
vcUnwrapped.view.removeFromSuperview()
}
}
}
func initNavBackboneControllers(){
leftNavigationController = UINavigationController()
leftNavigationController?.navigationBar.barTintColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
leftNavigationController?.navigationBar.tintColor = UIColor.whiteColor()
leftNavigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
leftNavigationController?.navigationBar.translucent = false
rightNavigationController = UINavigationController()
rightNavigationController?.navigationBar.barTintColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
rightNavigationController?.navigationBar.tintColor = UIColor.whiteColor()
rightNavigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
rightNavigationController?.navigationBar.translucent = false
slideViewController = SWRevealViewController()
slideViewController?.rearViewRevealOverdraw = 0;
slideViewController?.bounceBackOnOverdraw = false;
slideViewController?.stableDragOnOverdraw = true;
slideViewController?.delegate = self
if UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad{
splitViewController = UISplitViewController()
}
}
EDIT (in response to Justin's questions):
1) I've experienced the crash on all iOS8 iPad simulators.
2) From a fresh start, if I select like 6-7 items and then I rotate twice, it crashes. But I can also select an item, rotate a few times, select some more and keep rotating and at some point it will crash.
3) When an item is selected, the following code is executed:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let document = getInfoForSection(indexPath.section).documents[indexPath.item]
if document.canOpen{
openDocument(document)
DataManager.sharedInstance.getDocument(document.uri, after: {
(document:Document?) -> () in
if let documentUnwrapped = document{
let detailVC = NavigationFlowController.sharedInstance.detailViewController;
if detailVC.document?.uri == documentUnwrapped.uri{
NavigationFlowController.sharedInstance.detailViewController.documentUpdated(documentUnwrapped)
}
}
})
}
}
And then in the detail view controller:
func initLayout(){
if viewForCard == nil{
// views not yet initialized, happens when initLayout if called from the document setter before this view has been loaded
// just return, the layouting will be done on viewDidLoad with the correct document instead
return
}
self.navigationItem.rightBarButtonItems = []
if document == nil{
// Removed code that handles no document selected
...
return
}
heightForCard.constant = NavigationFlowController.sharedInstance.currentState == AppState.PHONE ? CARD_HEIGHT_PHONE : CARD_HEIGHT_TABLET
viewForCard.hidden = false
removeAllSubviews(viewForCard)
removeAllSubviews(viewForDetails)
viewForDetails.translatesAutoresizingMaskIntoConstraints = false
self.metaVC?.document = document
//self.documentVC?.document = document
self.navigationItem.rightBarButtonItems = []
downloadDocumentIfNeeded()
if NavigationFlowController.sharedInstance.currentState == AppState.PAD_LANDSCAPE || NavigationFlowController.sharedInstance.currentState == AppState.PAD_PORTRAIT{
self.viewForDetails.backgroundColor = document?.senderStyling?.color
addChildViewController(self.metaVC!)
addChildViewController(self.documentVC!)
let metaView = self.metaVC!.view
let documentView:UIView = self.documentVC!.view
viewForDetails.addSubview(metaView)
viewForDetails.addSubview(documentView)
// whole lot of layouting code removed
...
let doubleTap = UITapGestureRecognizer(target: self, action: "toggleZoom")
documentVC!.view.addGestureRecognizer(doubleTap)
}else{
// Phone version code removed
...
}
}
EDIT2:
func downloadDocumentIfNeeded(){
var tmpPath:NSURL?
if let url = document?.contentUrl{
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
if let docName = self.document?.name,
safeName = disallowedCharacters?.stringByReplacingMatchesInString(docName, options: [], range: NSMakeRange(0, docName.characters.count), withTemplate: "-"){
tmpPath = directoryURL.URLByAppendingPathComponent("\(safeName)_\(DetailViewController.dateFormatter.stringFromDate(self.document!.creationDate!)).pdf")
}
if let urlString = tmpPath?.path{
if NSFileManager.defaultManager().fileExistsAtPath(urlString) {
// File is there, load it
loadDocumentInWebview(tmpPath!)
}else{
// Download file
let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = {
(temporaryURL, response) in
if let path = tmpPath{
return path
}
return temporaryURL
}
download(.GET, URLString: url, destination: destination).response {
(request, response, data, error) in
if error != nil && error?.code != 516{
ToastView.showToastInParentView(self.view, withText: "An error has occurred while loading the document", withDuaration: 10)
}else if let pathUnwrapped = tmpPath {
self.loadDocumentInWebview(pathUnwrapped)
}
}
}
}
}
}
func loadDocumentInWebview(path:NSURL){
if self.navigationItem.rightBarButtonItems == nil{
self.navigationItem.rightBarButtonItems = []
}
self.documentVC?.finalPath = path
let shareItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Action, target: self, action: "share")
shareItem.tag = SHARE_ITEM_TAG
addNavItem(shareItem)
}
func addNavItem(navItem:UIBarButtonItem){
var addIt = true
for item in self.navigationItem.rightBarButtonItems!{
if item.tag == navItem.tag{
addIt = false
}
}
if addIt{
self.navigationItem.rightBarButtonItems?.append(navItem)
self.navigationItem.rightBarButtonItems!.sortInPlace({ $0.tag > $1.tag })
}
}
EDIT3: I've overridden the sendEvent method to track whether or not a user is touching the app or not, but even if I take out this code, it still crashes, and the debugger then breaks on UIApplicationMain.
override func sendEvent(event: UIEvent)
{
super.sendEvent(event)
if event.type == UIEventType.Touches{
if let touches = event.allTouches(){
for item in touches{
if let touch = item as? UITouch{
if touch.phase == UITouchPhase.Began{
touchCounter++
}else if touch.phase == UITouchPhase.Ended || touch.phase == UITouchPhase.Cancelled{
touchCounter--
}
if touchCounter == 0{
receiver?.notTouching()
}
}
}
}
}
}
Tough one, a bit more insight in the events upto this bug might be helpful.
Does it happen on every device (if not, which devices gives you troubles)
It happens after "vigorously selecting" items. Did your device change orientation before that. Does it also happen before you once rotate?
What do you do in code when you "select an item".
Other then that, I'd start to get the flow of removing your child ViewControllers in breakupFormerViewTree() right. Based on the Apple Docs you want to tell the child it's being removed, before removing the view and then finally removing the child from the Parent ViewController
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html
Here it actually says you want to call willMoveToParentViewController(nil) before doing the removing. It doesn't say what happens if you don't, but I can imagine the OS doing some lifecycle management there, preventing it from sending corrupt events at a later point.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/willMoveToParentViewController:
EDIT (After extra was code posted)
I don't see anything else in your code that might cause it to crash. It does look like a memory-error as you stated, but no idea where it's coming from. Try turning on Zombie objects and Guard Malloc (Scheme > Run > Diagnostics) and maybe you can get a bit more info on what's causing it.
Other then that, I'd just comment out loads of your implementation, swap Subclasses with empty ViewControllers until it doesn't happen again. You should be able to pinpoint what part of your implementation is involved in creating this event. Once you do that, well, pinpoint more and evaluate every single line of code in that implementation.

Resources