Edit (Feature Service Table)

Download Sample Application

In this sample the features from a feature service are displayed in the map via the creation of a feature layer. This feature layer is created via a local geodatabase feature service table (AGSGDBFeatureServiceTable) using the feature service URL and layer ID. The pop-up, itself, manages the creation and editing of both attributes and attachments of the feature and integrates the use of the sketch layer to collect the feature's geometry. The pop-ups (AGSPopup) is stored in the pop-up's container view controller (AGSPopupsContainerViewController).

Sample Code

//SWIFT SAMPLE CODE
/*
Copyright 2014 Esri

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import Cocoa
import ArcGIS

let kFSTTiledMapServiceURL = "http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"
let kFSTFeatureServiceURL = "http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/SanFrancisco/311Incidents/FeatureServer/0"

class FSTEditingSwiftSample: NSViewController, AGSMapViewTouchDelegate, AGSMapViewLayerDelegate, AGSLayerDelegate, AGSPopupsContainerDelegate {
    
    @IBOutlet weak var mapView:AGSMapView!
    @IBOutlet weak var popupView:NSView!
    @IBOutlet weak var splitView:NSSplitView!
    @IBOutlet weak var addFeatureButton:NSButton!
    
    var sketchLayer:AGSSketchGraphicsLayer!
    var popupsContainerVC:AGSPopupsContainerViewController!
    var popups = Array<AGSPopup>()
    var mapPoint:AGSPoint!
    var currentPopup:AGSPopup!
    var currentEditingPopup:AGSPopup!
    var originalFeatureAttributes:Dictionary<NSObject, AnyObject>!
    var originalGeometry:AGSGeometry!
    var featureServiceTable:AGSGDBFeatureServiceTable!
    var featureTableLayer:AGSFeatureTableLayer!
    var inEditingMode = false
    
    // -------------------------------------------------------------------------------
    //  awakeFromNib
    // -------------------------------------------------------------------------------
    override func awakeFromNib() {
        
        //enable wrap around
        self.mapView.enableWrapAround()
        
        //set mapView delegates
        self.mapView.layerDelegate = self
        self.mapView.touchDelegate = self
        self.mapView.showMagnifierOnTapAndHold = true
        
        //add base layer to map and set delegate to know when layer loads or fails to load
        let baseMapLayer = AGSTiledMapServiceLayer(URL: NSURL(string: kFSTTiledMapServiceURL))
        baseMapLayer.delegate = self
        self.mapView.addMapLayer(baseMapLayer, withName:"Base Layer")
        
        //add feature table layer to map
        let featureLayerURL = NSURL(string: kFSTFeatureServiceURL)
        self.featureServiceTable = AGSGDBFeatureServiceTable(serviceURL: featureLayerURL, credential:nil, spatialReference:AGSSpatialReference.webMercatorSpatialReference())
        self.featureTableLayer = AGSFeatureTableLayer(featureTable: self.featureServiceTable)
        self.featureTableLayer.delegate = self
        self.mapView.addMapLayer(self.featureTableLayer, withName:"Feature Layer")
        
        //add sketch graphics layer to map
        self.sketchLayer = AGSSketchGraphicsLayer()
        self.mapView.addMapLayer(self.sketchLayer, withName:"Sketch Graphics Layer")
        
        let env = AGSEnvelope(xmin: -13623000, ymin:4547000, xmax:-13613000, ymax:4557000, spatialReference:AGSSpatialReference.webMercatorSpatialReference())
        self.mapView.zoomToEnvelope(env, animated: true)
        
        self.addFeatureButton.enabled = true
    }
    
    //MARK: - AGSLayerDelegate Methods
    
    func layer(layer: AGSLayer!, didFailToLoadWithError error: NSError!) {
        if let viewWindow = self.view.window {
            let alert = NSAlert()
            alert.informativeText = error.localizedDescription
            alert.messageText = "Failed to load layer: \(layer.name)"
            alert.alertStyle = .InformationalAlertStyle
            alert.beginSheetModalForWindow(viewWindow, modalDelegate:self, didEndSelector:nil, contextInfo:nil)
        }
    }
    
    //MARK: - AGSMapViewTouchDelegate Methods
    
    func mapView(mapView: AGSMapView!, didClickAtPoint screen: CGPoint, mapPoint mappoint: AGSPoint!, features: [NSObject : AnyObject]!) {

        self.featureTableLayer.clearSelection()
        
        //If not editing then select an existing feature and display it's popup
        if !self.inEditingMode {
            self.resetPopupsAndSubviews()
            
            //Check that the selected features belong to the featurelayer
            //Add the PopupInformation from the selected features into the PopupArray
            //A PopupArray is used because you may have selected more than one feature
            self.mapPoint = mappoint
            if features.count > 0 {
                for (layerName, anyObject) in features {
                    if layerName == self.featureTableLayer.name {
                        for gdbFeature in features[layerName] as [AGSGDBFeature] {
                            self.addPopupToPopupsForFeature(gdbFeature)
                        }
                    }
                }
                //Populate the PopupsContainer VC using the PopupArray
                self.populatePopupsContainer()
            }
        }
    }
    
    func mapView(mapView: AGSMapView!, didKeyDown event: NSEvent!) {
        //delete key is keyCode = 51
        if (event.keyCode == 51){
            self.sketchLayer.removeSelectedVertex()
        }
    }
    
    //MARK: - AGSPopupsContainerDelegate Methods
    
    func popupsContainer(popupsContainer: AGSPopupsContainer!, readyToEditGraphicGeometry geometry: AGSGeometry!, forPopup popup: AGSPopup!) {
        
        //register self for receiving notifications from the sketch layer
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "respondToGeomChanged:", name: AGSSketchGraphicsLayerGeometryDidChangeNotification, object: nil)
        
        // set toch delegate to sketch layer
        self.mapView.touchDelegate = self.sketchLayer
        
        //This is the starting point of the sketch
        self.sketchLayer.geometry = geometry
    }
    
    func popupsContainer(popupsContainer: AGSPopupsContainer!, didStartEditingForPopup popup: AGSPopup!) {
        
        // store original attribute and geometry
        // so can revert back if editing is canceled
        self.originalFeatureAttributes = popup.feature.allAttributes()
        if popup.feature.geometry != nil {
            self.originalGeometry = popup.feature.geometry.mutableCopy() as AGSGeometry
        }
        
        // set in editing
        self.inEditingMode = true
        
        // disable button
        self.addFeatureButton.enabled = false
    }
    
    func popupsContainer(popupsContainer: AGSPopupsContainer!, didDeleteForPopup popup: AGSPopup!) {
        self.finishEditingPopup(popup)
        self.resetPopupsAndSubviews()
    }
    
    func popupsContainer(popupsContainer: AGSPopupsContainer!, didFinishEditingForPopup popup: AGSPopup!) {
        
        // finish editing
        self.finishEditingPopup(popup)
        
        // set in editing
        self.inEditingMode = false
        
        // enable buttons
        self.addFeatureButton.enabled = true
    }
    
    func popupsContainer(popupsContainer: AGSPopupsContainer!, didChangeToCurrentPopup popup: AGSPopup!) {
        // change current popup
        // set selection
        self.currentPopup = popup
        self.refreshSelection()
    }
    
    func popupsContainer(popupsContainer: AGSPopupsContainer!, didCancelEditingForPopup popup: AGSPopup!) {
        
        self.currentEditingPopup = popup
        self.cancelEditing()
        
        // set in editing
        self.inEditingMode = false
        
        // enable buttons
        self.addFeatureButton.enabled = true
    }
    
    func popupsContainer(popupsContainer: AGSPopupsContainer!, wantsNewMutableGeometryForPopup popup: AGSPopup!) -> AGSGeometry! {
        //Return an empty mutable geometry of the type that our feature layer uses
        return AGSMutableGeometryFromType(popup.gdbFeatureTable.geometryType, self.mapView.spatialReference)
    }
    
    //MARK: - Helper methods
    
    @IBAction func addFeature(sender:AnyObject) {
        
        if self.featureTableLayer != nil {
            
            var gdbFeature:AGSGDBFeature!
            
            if self.featureServiceTable.types.count != 0 {
                let featureType = (self.featureServiceTable.types)[0] as AGSFeatureType
                gdbFeature = self.featureServiceTable.featureWithType(featureType)
            }
            else if self.featureServiceTable.templates.count != 0 {
                let featureTemplate = (self.featureServiceTable.templates)[0] as AGSFeatureTemplate
                gdbFeature = self.featureServiceTable.featureWithTemplate(featureTemplate)
            }
            else {
                gdbFeature = AGSGDBFeature(table: self.featureServiceTable)
            }
            
            self.resetPopupsAndSubviews()
            self.addPopupToPopupsForFeature(gdbFeature)
            self.populatePopupsContainer()
            self.popupsContainerVC.startEditingCurrentPopup()
            self.mapView.callout.hidden = true
            self.addFeatureButton.enabled = false
        }
    }
    
    func addPopupToPopupsForFeature(gdbFeature:AGSGDBFeature) {
        // add popup to popups array
        self.popupView.subviews = Array()
        let popupInfo = AGSPopupInfo(forGDBFeatureTable: gdbFeature.table)
        popupInfo.title = gdbFeature.table.tableName()
        
        let popup = AGSPopup(GDBFeature: gdbFeature, popupInfo:popupInfo)
        
        self.popups.append(popup)
    }
    
    func cancelEditing() {
        if self.currentEditingPopup.feature.geometry == nil {
            self.resetPopupsAndSubviews()
        }
        
        if self.currentEditingPopup != nil {
            self.currentEditingPopup.graphic.setAttributes(self.originalFeatureAttributes)
            self.currentEditingPopup.feature.geometry = self.originalGeometry
            self.currentEditingPopup = nil
        }
        self.sketchLayer.clear()
        self.mapView.touchDelegate = self
    }
    
    func finishEditingPopup(popup: AGSPopup) {
        self.currentEditingPopup = popup
        self.featureServiceTable = popup.gdbFeatureTable as AGSGDBFeatureServiceTable
        
        //First apply feature edits
        self.featureServiceTable.applyFeatureEditsWithCompletion { [weak self] (featureEditErrors, error) -> Void in
            if let weakSelf = self {
                let isFeatureEditError = weakSelf.checkApplyEditsForEditErrors(featureEditErrors, andOtherErrors:error)
                if !isFeatureEditError {
                    //No errors, apply Attachment edits now
                    weakSelf.featureServiceTable.applyAttachmentEditsWithCompletion({ [weak self] (attachmentEditErrors, error2) -> Void in
                        if let weakSelf = self {
                            let isAttachEditError = weakSelf.checkApplyEditsForEditErrors(attachmentEditErrors, andOtherErrors:error2)
                            if isAttachEditError {
                                println("Attachment Error")
                            }
                        }
                    })
                }
            }
        }
        
        self.currentPopup = popup
        self.mapView.touchDelegate = self
        self.sketchLayer.geometry = nil
        self.sketchLayer.clear()
        self.inEditingMode = false
        
        // reset original feature attribute/geometry
        self.originalFeatureAttributes = nil
        self.originalGeometry = nil
        
        //Reselect the graphic associated with the Popup
        self.refreshSelection()
        self.addFeatureButton.enabled = true
    }
    
    func refreshSelection() {
        
        self.featureTableLayer.clearSelection()
        self.featureTableLayer.setSelected(true, forFeature:self.currentPopup.feature)
    }
    
    func populatePopupsContainer() {
        
        if self.popupsContainerVC == nil || self.popupsContainerVC.popups.count == 0 {
            //Initialize the PopupsContainer VC with first layer's popups
            self.popupsContainerVC = AGSPopupsContainerViewController(popups: self.popups)
            //select the graphic for the first popup in the array
            self.currentPopup = self.popups[0]
            self.refreshSelection()
        }
        else {
            //The popupsContainerVC has already been initialized, Hence add additional popups from other layers.
            self.popupsContainerVC.showAdditionalPopups(self.popups)
        }
        
        if self.popupView.frame.size.width < 100.0  {
            self.splitView.setPosition(100.0, ofDividerAtIndex:0)
        }
        self.popupsContainerVC.view.frame = self.popupView.bounds
        self.popupsContainerVC.delegate = self
        
        //Add the PopupsContainer VC to the popup view
        self.popupView.addSubview(self.popupsContainerVC.view)
        self.popupsContainerVC.view.autoresizingMask = .ViewWidthSizable | .ViewHeightSizable;
    }
    
    func resetPopupsAndSubviews() {
        //Initialize Popups Array; If it already exists, remove all it's objects and reset it
        if self.popups.count > 0 {
            self.popups.removeAll(keepCapacity: false)
        }
        else {
            self.popups = Array()
        }
        //Clear the popups from the popupsContainerVC so that the latter can be reused.
        if self.popupsContainerVC != nil {
            self.popupsContainerVC.clearAllPopups()
        }
        //Reset the subview of the popupView
        if self.popupView.subviews.count > 0  {
            self.popupView.subviews = Array()
        }
        
        self.currentPopup = nil
        self.currentEditingPopup = nil
        
        self.splitView.setPosition(0.0, ofDividerAtIndex:0)
    }
    
    func respondToGeomChanged(notification: NSNotification) {
        if self.popupsContainerVC != nil {
            self.popupsContainerVC.geometryUpdated()
        }
    }
    
    func showErrorWithTitle(title: String, message:String) {
        //Display an alert to the user
        if let viewWindow = self.view.window {
            let alert = NSAlert()
            alert.addButtonWithTitle("Retry")
            alert.addButtonWithTitle("Cancel")
            alert.messageText = title
            alert.informativeText = message
            alert.beginSheetModalForWindow(viewWindow, modalDelegate:self, didEndSelector:"featureEditingErrorAlertDidEnd:returnCode:", contextInfo:nil)
        }
    }
    
    func checkApplyEditsForEditErrors(editErrors: [AnyObject]!, andOtherErrors error:NSError!) -> Bool {
    
        var isError = true
        
        if error != nil {
            self.showErrorWithTitle("Sync Error", message:error.localizedDescription)
        }
        else if editErrors != nil && editErrors.count > 0 {
            self.showErrorWithTitle("Edit Error", message:(editErrors[0] as NSError).localizedDescription)
        }
        else {
            //No errors
            isError = false
        }
    
        return isError
    }
    
    func featureEditingErrorAlertDidEnd(alert:NSAlert, returnCode:(Int)) {
        alert.window.orderOut(nil)
        
        if returnCode == NSAlertFirstButtonReturn {
            
            //try again to post edits
            self.finishEditingPopup(self.currentEditingPopup)
        }
        else if returnCode == NSAlertSecondButtonReturn {
            
            //cancel editing/posting edits
            self.cancelEditing()
            
            //This is so the popup VC knows something happened to cancel the editing process
            //and redraw itself and the current popup vc accordingly
            self.popupsContainerVC.cancelEditingCurrentPopup()
        }
    }
    
    //MARK: - NSSplitView delegate
    
    // -------------------------------------------------------------------------------
    //  splitView:constrainMaxCoordinate
    //
    //  this will restrict resize of splitView at specified max value
    // -------------------------------------------------------------------------------
    func splitView(splitView: NSSplitView, constrainMaxCoordinate proposedMaximumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
        return 350
    }
    
    // -------------------------------------------------------------------------------
    //  splitView:constrainMinCoordinate
    //
    //  this will restrict resize of splitView at specified min value
    // -------------------------------------------------------------------------------
    func splitView(splitView: NSSplitView, constrainMinCoordinate proposedMinimumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
        return 250
    }
    
    //MARK: -
    
    deinit {
        //remove observer
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}
//OBJECTIVE C SAMPLE CODE
/*
 Copyright 2013 Esri
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

#import "FSTEditingSample.h"

#define kTiledMapServiceURL @"http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"
#define kFeatureServiceURL @"http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/SanFrancisco/311Incidents/FeatureServer/0"

@interface FSTEditingSample ()

@property (nonatomic,strong) AGSSketchGraphicsLayer *sketchLayer;
@property (nonatomic,strong) AGSPopupsContainerViewController *popupsContainerVC;
@property (nonatomic,strong) NSMutableArray *popups;
@property (nonatomic,strong) AGSPoint *mapPoint;
@property (nonatomic,strong) AGSPopup *currentPopup;
@property (nonatomic,strong) AGSPopup *currentEditingPopup;
@property (nonatomic,strong) NSDictionary *originalFeatureAttributes;
@property (nonatomic,strong) AGSGeometry *originalGeometry;
@property (nonatomic,strong) AGSGDBFeatureServiceTable *featureServiceTable;
@property (nonatomic,strong) AGSFeatureTableLayer *featureTableLayer;
@property (nonatomic,assign) BOOL inEditingMode;

@end

@implementation FSTEditingSample


// -------------------------------------------------------------------------------
//  awakeFromNib
// -------------------------------------------------------------------------------
- (void)awakeFromNib {
    
    //enable wrap around
    [self.mapView enableWrapAround];
    
    //set layer delegate to know when mapView loads
    self.mapView.layerDelegate = self;
    self.mapView.touchDelegate = self;
    self.mapView.showMagnifierOnTapAndHold = YES;
    
    //add base layer to map and set delegate to know when layer loads or fails to load
	AGSTiledMapServiceLayer *baseMapLayer = [[AGSTiledMapServiceLayer alloc] initWithURL:[NSURL URLWithString:kTiledMapServiceURL]];
    baseMapLayer.delegate = self;
	[self.mapView addMapLayer:baseMapLayer withName:@"Base Map"];
    
    //add feature table layer to map
    NSURL *featureLayerURL = [NSURL URLWithString:kFeatureServiceURL];
	self.featureServiceTable = [[AGSGDBFeatureServiceTable alloc] initWithServiceURL:featureLayerURL credential:nil spatialReference:[AGSSpatialReference webMercatorSpatialReference]];
	self.featureTableLayer = [[AGSFeatureTableLayer alloc] initWithFeatureTable:self.featureServiceTable];
    self.featureTableLayer.delegate = self;
	[self.mapView addMapLayer:self.featureTableLayer withName:@"Feature Layer"];
    
    //add sketch graphics layer to map
    self.sketchLayer = [[AGSSketchGraphicsLayer alloc]init];
    [self.mapView addMapLayer:self.sketchLayer withName:@"Sketch Graphics Layer"];
    
    //zoom to predefined extend with known spatial reference of the map
	AGSSpatialReference *sr = [AGSSpatialReference webMercatorSpatialReference];
	AGSEnvelope *env = [AGSEnvelope envelopeWithXmin:-13623000 ymin:4547000 xmax:-13613000 ymax:4557000 spatialReference:sr];
	[self.mapView zoomToEnvelope:env animated:YES];
    
    self.addFeatureButton.enabled = YES;
    self.inEditingMode = NO;
}

#pragma mark - AGSLayerDelegate Methods

- (void)layer:(AGSLayer *)layer didFailToLoadWithError:(NSError *)error {
	NSString *layerNameStr = [NSString stringWithFormat:@"Failed to Load Layer! %@",layer.name];
	NSAlert *alert = [[NSAlert alloc] init];
    [alert setAlertStyle:NSInformationalAlertStyle];
    [alert setMessageText:layerNameStr];
    [alert setInformativeText:[error localizedDescription]];
	[alert beginSheetModalForWindow:self.view.window modalDelegate:self didEndSelector:nil contextInfo:nil];
}

#pragma mark - AGSMapViewTouchDelegate Methods

- (void)mapView:(AGSMapView *)mapView didClickAtPoint:(CGPoint)screen mapPoint:(AGSPoint *)mappoint features:(NSDictionary *)features {
    
    [self.featureTableLayer clearSelection];
    
    //If not editing then select an existing feature and display it's popup
    if (!self.inEditingMode) {
        
        //Clear the PopupsContainer VC and popup view
        [self resetPopupsAndSubviews];
        
        //Check that the selected features belong to the featurelayer
        //Add the PopupInformation from the selected features into the PopupArray
        //A PopupArray is used because you may have selected more than one feature
        self.mapPoint = mappoint;
        if ([features count] > 0) {
            for (NSString *layerName in features) {
                if ([layerName isEqual: self.featureTableLayer.name]){
                    for (AGSGDBFeature *gdbFeature in [features valueForKey:layerName]) {
                        [self addPopupToPopupsForFeature:gdbFeature];
                    }
                }
            }
            //Populate the PopupsContainer VC using the PopupArray
            [self populatePopupsContainer];
        }
    }
}

-(void)mapView:(AGSMapView *)mapView didKeyDown:(NSEvent *)event{
    //delete key is keyCode = 51
    if (event.keyCode == 51){
        [self.sketchLayer removeSelectedVertex];
    }
}


#pragma mark - AGSPopupsContainerDelegate Methods

-(void)popupsContainer:(id<AGSPopupsContainer>)popupsContainer readyToEditGeometry:(AGSGeometry*)geometry forPopup:(AGSPopup*)popup {
    
    //register self for receiving notifications from the sketch layer
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(respondToGeomChanged:) name:AGSSketchGraphicsLayerGeometryDidChangeNotification object:nil];
    
    // set touch delegate to sketch layer
    // so we can sketch geometry
    self.mapView.touchDelegate = self.sketchLayer;
    
    //This is the starting point of the sketch
    self.sketchLayer.geometry = geometry;
}

-(void)popupsContainer:(id<AGSPopupsContainer>)popupsContainer didStartEditingForPopup:(AGSPopup*)popup {
    
    self.originalFeatureAttributes = [popup.gdbFeature allAttributes];
    self.originalGeometry = [popup.gdbFeature.geometry mutableCopy];
    self.inEditingMode = YES;
    self.addFeatureButton.enabled = NO;
}


-(void)popupsContainer:(id<AGSPopupsContainer>)popupsContainer didDeleteForPopup:(AGSPopup *)popup {
    [self finishEditingPopup:popup];
    [self resetPopupsAndSubviews];
}

-(void)popupsContainer:(id<AGSPopupsContainer>)popupsContainer didFinishEditingForPopup:(AGSPopup*)popup {
    
    [self finishEditingPopup:popup];
    self.inEditingMode = NO;
    self.addFeatureButton.enabled = YES;
}

-(void)popupsContainer:(id<AGSPopupsContainer>)popupsContainer didChangeToCurrentPopup:(AGSPopup *)popup{
    
    self.currentPopup = popup;
    [self refreshSelection];
}

-(void)popupsContainer:(id<AGSPopupsContainer>)popupsContainer didCancelEditingForPopup:(AGSPopup*)popup{
    
    self.currentEditingPopup = popup;
    [self cancelEditing];
    
    // set in editing
    self.inEditingMode = NO;
    
    // enable buttons
    self.addFeatureButton.enabled = YES;
    
}


-(AGSGeometry*)popupsContainer:(id<AGSPopupsContainer>)popupsContainer wantsNewMutableGeometryForPopup:(AGSPopup*)popup {
    
    //Return an empty mutable geometry of the type that our feature layer uses
    return AGSMutableGeometryFromType(popup.gdbFeatureTable.geometryType, self.mapView.spatialReference);
}


#pragma mark - Helper Methods

- (void)addFeature:(id)sender {
    
    if(self.featureTableLayer) {
        
        AGSGDBFeature *gdbFeature = nil;
        
        if ([self.featureServiceTable.types count] != 0) {
            AGSFeatureType *featureType = (self.featureServiceTable.types)[0];
            gdbFeature = [self.featureServiceTable featureWithType:featureType];
        }
        else if ([self.featureServiceTable.templates count] != 0) {
            AGSFeatureTemplate *featureTemplate = (self.featureServiceTable.templates)[0];
            gdbFeature = [self.featureServiceTable featureWithTemplate:featureTemplate];
        }
        else {
            gdbFeature = [[AGSGDBFeature alloc]initWithTable:self.featureServiceTable];
        }
        
        [self resetPopupsAndSubviews];
		[self addPopupToPopupsForFeature:gdbFeature];
		[self populatePopupsContainer];
        [self.popupsContainerVC startEditingCurrentPopup];
        self.mapView.callout.hidden = YES;
        self.addFeatureButton.enabled = NO;
        
	}
}

- (void)addPopupToPopupsForFeature:(AGSGDBFeature *)gdbFeature {
    
    //Add the PopupInformation from the selected features into the PopupArray
	[self.popupView setSubviews:[NSArray array]];
	AGSPopupInfo *popupInfo = [AGSPopupInfo popupInfoForGDBFeatureTable:gdbFeature.table];
	popupInfo.title = gdbFeature.table.tableName;
	
	AGSPopup *popup = [AGSPopup popupWithGDBFeature:gdbFeature popupInfo:popupInfo];
	[self.popups addObject:popup];
}

-(void)cancelEditing {
    
    if (self.currentEditingPopup.feature.geometry == nil) {
        [self resetPopupsAndSubviews];
    }
    
    [self.currentEditingPopup.feature setAttributes:self.originalFeatureAttributes];
    self.currentEditingPopup.feature.geometry = self.originalGeometry;
    self.currentEditingPopup = nil;
    [self.sketchLayer clear];
    self.mapView.touchDelegate = self;
}

- (void)finishEditingPopup:(AGSPopup *)popup {
    
    self.currentEditingPopup = popup;
    self.featureServiceTable = (AGSGDBFeatureServiceTable *)popup.gdbFeatureTable;
	
	__weak FSTEditingSample *weakSelf = self;
	
	//First apply feature edits
	[self.featureServiceTable applyFeatureEditsWithCompletion:^(NSArray *featureEditErrors, NSError *error) {
		
		BOOL isFeatureEditError = [weakSelf checkApplyEditsForEditErrors:featureEditErrors andOtherErrors:error];
		
		if (!isFeatureEditError) {
            
			//No errors, apply Attachment edits now
			[weakSelf.featureServiceTable applyAttachmentEditsWithCompletion:^(NSArray *attachmentEditErrors, NSError *error2) {
				
				BOOL isAttachEditError = [weakSelf checkApplyEditsForEditErrors:attachmentEditErrors andOtherErrors:error2];
				
				if (isAttachEditError) {
					NSLog(@"Attachment Error");
				}
			}];
		}
	}];
    
    self.currentPopup = popup;
    self.mapView.touchDelegate = self;
    self.sketchLayer.geometry = nil;
    [self.sketchLayer clear];
    self.inEditingMode = NO;
    
    // reset original feature attribute/geometry
    self.originalFeatureAttributes = nil;
    self.originalGeometry = nil;
    
    //Reselect the graphic associated with the Popup
    [self refreshSelection];
    self.addFeatureButton.enabled = YES;
}

-(void)refreshSelection {
    [self.featureTableLayer clearSelection];
    [self.featureTableLayer setSelected:YES forFeature:self.currentPopup.feature];
}

-(void) populatePopupsContainer {
    
	if (!self.popupsContainerVC || ([self.popupsContainerVC.popups count] == 0)) {
        //Initialize the PopupsContainer VC with first layer's popups
		self.popupsContainerVC = [[AGSPopupsContainerViewController alloc] initWithPopups:self.popups];
        //select the graphic for the first popup in the array
        self.currentPopup = [self.popups objectAtIndex:0];
        [self refreshSelection];
	}
	else {
        //The popupsContainerVC has already been initialized.
		[self.popupsContainerVC showAdditionalPopups:self.popups];
	}
    
    if (self.popupView.frame.size.width < 100.0f) {
        [self.splitView setPosition:100.0f ofDividerAtIndex:0];
    }
    self.popupsContainerVC.view.frame = [self.popupView bounds];
    self.popupsContainerVC.delegate = self;
    
    //Add the PopupsContainer VC to the popup view
    [self.popupView addSubview:self.popupsContainerVC.view];
    self.popupsContainerVC.view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
}

-(void) resetPopupsAndSubviews {
    //Initialize Popups Array; If it already exists, remove all it's objects and reset it
	if (self.popups) {
		[self.popups removeAllObjects];
	}
	else {
		self.popups = [NSMutableArray array];
	}
    //Clear the popups from the popupsContainerVC so that the latter can be reused.
	if (self.popupsContainerVC) {
		[self.popupsContainerVC clearAllPopups];
	}
    //Reset the subview of the popupView
	if ([self.popupView.subviews count] > 0 ) {
        [self.popupView setSubviews:[NSArray array]];
	}
    
    self.currentPopup = nil;
    self.currentEditingPopup = nil;
    
    [self.splitView setPosition:0.0f ofDividerAtIndex:0];
}

- (void)respondToGeomChanged: (NSNotification*) notification {
    [self.popupsContainerVC geometryUpdated];
}

- (void)showErrorWithTitle:(NSString*)title message:(NSString*)message {
    NSAlert *alert = [[NSAlert alloc] init];
    [alert setAlertStyle:NSWarningAlertStyle];
    [alert setInformativeText:message];
    [alert setMessageText:title];
    [alert addButtonWithTitle:@"Retry"];
    [alert addButtonWithTitle:@"Cancel"];
    [alert beginSheetModalForWindow:self.view.window modalDelegate:self didEndSelector:@selector(featureEditingErrorAlertDidEnd:returnCode:contextInfo:) contextInfo:nil];
}

-(BOOL) checkApplyEditsForEditErrors:(NSArray *) editErrors andOtherErrors:(NSError *)error {
	
	__weak FSTEditingSample *weakSelf = self;
	__block BOOL isError = YES;
	
	if (error) {
		[weakSelf showErrorWithTitle:@"Sync Error" message:[error localizedDescription]];
	}
	else if (editErrors.count) {
		[weakSelf showErrorWithTitle:@"Edit Error" message:[(NSError *)editErrors[0] localizedDescription]];
	}
	else {
		//No errors
		isError = NO;
	}
	
	return isError;
}

-(void)featureEditingErrorAlertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo{
    
    [[alert window]orderOut:nil];
    
    if (returnCode == NSAlertFirstButtonReturn){
        //try again to post edits
        [self finishEditingPopup:self.currentEditingPopup];
    }
    else if (returnCode == NSAlertSecondButtonReturn){
        
        //cancel editing/posting edits
        [self cancelEditing];
        
        //This is so the popup VC knows something happened to cancel the editing process
        //and redraw itself and the current popup vc accordingly
        [self.popupsContainerVC cancelEditingCurrentPopup];
    }
}

#pragma mark - NSSplitView delegate

// -------------------------------------------------------------------------------
//  splitView:constrainMaxCoordinate
//
//  this will restrict resize of splitView at specified max value
// -------------------------------------------------------------------------------
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex {
    return 260;
}

// -------------------------------------------------------------------------------
//  splitView:constrainMinCoordinate
//
//  this will restrict resize of splitView at specified min value
// -------------------------------------------------------------------------------
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex {
    return 200;
}

#pragma mark -

// -------------------------------------------------------------------------------
//  dealloc
// -------------------------------------------------------------------------------
- (void)dealloc {
    
    //remove observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end

//OBJECTIVE C SAMPLE CODE
/*
 Copyright 2013 Esri
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

#import <Cocoa/Cocoa.h>

@interface FSTEditingSample : NSViewController

<AGSMapViewLayerDelegate, AGSLayerDelegate, AGSMapViewTouchDelegate, AGSPopupsContainerDelegate>

@property (strong) IBOutlet AGSMapView *mapView;
@property (strong) IBOutlet NSView *popupView;
@property (strong) IBOutlet NSSplitView *splitView;
@property (strong) IBOutlet NSButton *addFeatureButton;

- (IBAction)addFeature:(id)sender;

@end

Feedback on this topic?