Skip to content
This repository has been archived by the owner on Jan 6, 2023. It is now read-only.

iOS WebMap #1012

Closed
aliawais opened this issue Nov 23, 2020 · 23 comments
Closed

iOS WebMap #1012

aliawais opened this issue Nov 23, 2020 · 23 comments
Labels

Comments

@aliawais
Copy link

Hi,

It is more of a question rather than an issue.

Is there still support for Webmap in iOS? If yes, if there is sample please provide.

I was looking at https://developers.arcgis.com/ios/10-2/swift/guide/viewing-web-map.htm it refers to using AGSWebMap which I cant find.

It redirects for sample on http://www.arcgis.com/home/item.html?id=d76ed99f71e24116b324ff624a194ef9 and the URL for the repo is following but it give 404.

https://github.com/Esri/arcgis-runtime-samples-ios/tree/master/WebmapSample

Reason
Current implementation is done using AGSMapView and layer URLs are in config file and layers can be shown/hidden using. Now we need to support multiple clients and each client can have their own maps and layers, client will be determined after login. So we were thinking to load client's map using their MapId.

@yo1995
Copy link
Collaborator

yo1995 commented Nov 23, 2020

Hi @aliawais , I'm not quite sure about your problem. Can you try to load a webmap with its portal item ID? e.g.

let portal = AGSPortal.arcGISOnline(withLoginRequired: false)
let portalItem = AGSPortalItem(portal: portal, itemID: yourPortalItemID)
let map = AGSMap(item: portalItem)
mapView.map = map

@aliawais
Copy link
Author

Is portalItemId different from MapId?

Is there a complete sample using this approach and also showing/hiding available layers?

@yo1995
Copy link
Collaborator

yo1995 commented Nov 23, 2020

Is portalItemId different from MapId?

I think they are the same thing.

Is there a complete sample using this approach and also showing/hiding available layers?

You may take a look at

for some inspirations.

AFAIK there isn't a sample that demonstrates the workflow to load multiple layers from ArcGIS Online and control their visibility. I also asked our team to see if they have suggestions.

@aliawais
Copy link
Author

@yo1995 thank you.

I tried using the mapId with login and without login it did not work.

I will look into the samples and hope to find something we need.

@yo1995
Copy link
Collaborator

yo1995 commented Nov 23, 2020

@yo1995 thank you.

I tried using the mapId with login and without login it did not work.

I will look into the samples and hope to find something we need.

Sure. Please feel free to reopen this issue if you've more questions, and I'll get you posted when I receive feedback regarding the AGSWebMap class.

@nixta
Copy link
Member

nixta commented Nov 23, 2020

@aliawais You could take a look at this sample: https://developers.arcgis.com/ios/latest/swift/sample-code/open-map-url/

That takes the URL to the item page for a web map in ArcGIS and opens it. Does that help?

@yo1995
Copy link
Collaborator

yo1995 commented Nov 23, 2020

As Nick indicated - In addition, consider taking a look at these 2 samples:

Hope it helps.

@aliawais
Copy link
Author

Thanks @nixta and @yo1995 will definitely have a look into these.

@aliawais
Copy link
Author

aliawais commented Nov 24, 2020

Thanks guys it worked: I used

        let cred = AGSCredential(user: _userName_, password: _password_)
        let portal = AGSPortal.arcGISOnline(withLoginRequired: true)
        portal.credential = cred
        let portalItem = AGSPortalItem(portal: portal, itemID: _mapId_)
        let map = AGSMap(item: portalItem)
        mapView.map = map

If I have token from service and want to use AGSCredential(token: String, referer: String) what should be the value of referer?

@aliawais
Copy link
Author

aliawais commented Dec 1, 2020

@nixta @yo1995
Hi guys,

I am loading the map using follwing:

let portal = AGSPortal(url: portalURL, loginRequired: true)
        portal.credential = agsCredential
        let portalItem = AGSPortalItem(portal: portal, itemID: mapId)
        let map = AGSMap(item: portalItem)
        esriMapView.map = map

where agsCredential are created using a token from API and referer that the app sends in request. The login popup still shows up for "You need to sign in to access www.arcgis.com"

If I add another portal (not used afterwards) as follows before the above code it works to load layers and map.

let arcGISPortal = AGSPortal.arcGISOnline(withLoginRequired: true)
            arcGISPortal.credential = agsCredential
            arcGISPortal.load { (error) in
                if let _ = error {
                    print("Failed loading/signing to acrGISOnline")
                }
            }

But when I try to fetch basemaps using code below it shows login popup again but to sign in to portal.

        portal.load { (error) in
            if error == nil {
                portal.fetchBasemaps { (basemaps, err) in
                
            }
        }

If I create agsCredential using username and password, then it works fine without creating the extra portal with let arcGISPortal = AGSPortal.arcGISOnline(withLoginRequired: true)

I would need help in sorting the above using the token and referer, secondly how to authenticate for multiple paths e.g. portalURL/portal and portalURL/server/rest...

@aliawais aliawais reopened this Dec 1, 2020
@nixta
Copy link
Member

nixta commented Dec 1, 2020

@nixta @yo1995
Hi guys,

I am loading the map using follwing:

let portal = AGSPortal(url: portalURL, loginRequired: true)
        portal.credential = agsCredential
        let portalItem = AGSPortalItem(portal: portal, itemID: mapId)
        let map = AGSMap(item: portalItem)
        esriMapView.map = map

What is the portalURL? And please provide details on how you’re getting the token.

Building a credential using the token is a less common approach (and you should use the AGSAuthenticationManager challengeHandler pattern if you’re doing that, for seamless token refreshes).

It sounds to me like you might be getting a token from one portal and using it on another.

@aliawais
Copy link
Author

aliawais commented Dec 1, 2020

Hi @nixta @yo1995

Before loading a map, the app makes calls one of our APIs and we pass a 'referer' in header. The API uses ESRI/ArcGIS service to obtain a token for the provided referer. Then API returns us a mapId, portalURL, token, tokenExpiry. The same API is used by our front-end (web).

JSON format of response:
{
"gisBaseUrl": "portalURL",
"token": {
"token": "",
"expires": 1606815960364,
"ssl": true
},
"authRequiredPaths": [
"/portal",
"/server/rest/services"
],
"gisPortalPath": "/portal",
"defaultWebMapId": "mapId"
}

One suggestion from we developer was that they also sign in to authRequiredPaths as it might be casing issue, how to do that?

The portalURLs (gisBaseURL) could be one of the following format:

Then use the portal URL and

agsCredential = AGSCredential(token: portalToken, referer: referer)
let portal = AGSPortal(url: portalURL, loginRequired: true)
        portal.credential = agsCredential
        let portalItem = AGSPortalItem(portal: portal, itemID: mapId)
        let map = AGSMap(item: portalItem)
        esriMapView.map = map
        map.load { (error) in 
              // handle error or handle successfully loaded map
              // load base maps
}

I tried one thing with AGSAuthenticationManager, after getting token from our API, set AGSAuthenticationManager.shared().delegate to self and then in one of the delegates did following:

func authenticationManager(_ authenticationManager: AGSAuthenticationManager, didReceive challenge: AGSAuthenticationChallenge) {
        agsCredential?.token = esriAuthViewModel.portalToken()
        challenge.continue(with: agsCredential)
    }

The strange thing I noticed was that agsCredential is class level variable, and its token was getting null.

So I need to use the token from our API and dont want to show the authentication challenge popup, if there any implementation suggestion please provide, can you also please refer to a proper implementation of using AGSAuthenticationManager.

@nixta
Copy link
Member

nixta commented Dec 2, 2020

There is a lot going on here, and it's not really relevant to this repo. Might be worth picking the conversation up over at the iOS Runtime forum. But…

Firstly, a token obtained for https://atlas.{client Name}.com (which, by the looks of things, is an on-premise ArcGIS Enterprise instance) will not work at https://{client Name}.maps.arcgis.com. And vice-versa.

agsCredential must be something in your codebase. Not sure what that is or how it's created. You can look at the proposedCredential that is passed to the challengeHandler. Set the token on that. However, you still want to make sure that the token was generated for the right service/portal.

You might find some useful info here too.

You appear to be using challengeHandler correctly, btw. Just not sure if passing back agsCredential is a good thing since we don't know what it is or where it comes from in your codebase.

@nixta
Copy link
Member

nixta commented Dec 2, 2020

What is the URL that is being used to generate the token?

@nixta
Copy link
Member

nixta commented Dec 2, 2020

It could also be that the token is expired. If you use the username and password, Runtime will automatically get a new token if the old one expires.

@aliawais
Copy link
Author

aliawais commented Dec 2, 2020

@nixta

Token is generated for each portal when it changes. The URL for one of them is
https://atlas.{clientName}.com/portal/sharing/rest/generateToken

The token is generated at time app make the call to our API, and its has an expiry of 24hours.

@nixta
Copy link
Member

nixta commented Dec 2, 2020

@nixta

Token is generated for each portal when it changes. The URL for one of them is
https://atlas.{clientName}.com/portal/sharing/rest/generateToken

The token is generated at time app make the call to our API, and its has an expiry of 24hours.

Then in the code above:

let portal = AGSPortal(url: portalURL, loginRequired: true)
        portal.credential = agsCredential
        let portalItem = AGSPortalItem(portal: portal, itemID: mapId)
        let map = AGSMap(item: portalItem)
        esriMapView.map = map

what is the portalURL? If it's not atlas.{clientName}.com or if the token was not obtained using credentials that authorize access to the mapId web map, then the credential will not work and you will be prompted.

@aliawais
Copy link
Author

aliawais commented Dec 2, 2020

The token is generated for that portalURL which we get in the response and the mapId is associated with that token and portal.

The thing is when create agsCredetails using user name and password it works fine. Can you please link me to the sample using the Authentication manager.

@nixta
Copy link
Member

nixta commented Dec 2, 2020

Unfortunately we don't have an AuthenticationManager sample that covers using credentials with token.

One thought. You said this:

I am loading the map using follwing:

let portal = AGSPortal(url: portalURL, loginRequired: true)
        portal.credential = agsCredential
        let portalItem = AGSPortalItem(portal: portal, itemID: mapId)
        let map = AGSMap(item: portalItem)
        esriMapView.map = map

where agsCredential are created using a token from API and referer that the app sends in request. The login popup still shows up for "You need to sign in to access www.arcgis.com"

If I add another portal (not used afterwards) as follows before the above code it works to load layers and map.

let arcGISPortal = AGSPortal.arcGISOnline(withLoginRequired: true)
            arcGISPortal.credential = agsCredential
            arcGISPortal.load { (error) in
                if let _ = error {
                    print("Failed loading/signing to acrGISOnline")
                }
            }

Is agsCredential the same when applied to both portals?

@aliawais
Copy link
Author

aliawais commented Dec 2, 2020

I used that approach for portal https://{client Name}.maps.arcgis.com, and the agsCredential was same for arcGISOnline(withLoginRequired: true)

For portal https://atlas.{client Name}.com it works without the extra AGSPortal.arcGISOnline(withLoginRequired: true).
At one time I using one portalURL and one mapId, for simplicity if UserA logs in to the app we use https://atlas.{client Name}.com and if UserB logs in we use https://atlas.{client Name}.com

@nixta
Copy link
Member

nixta commented Dec 2, 2020

To confirm:

This does NOT work:

Display a map from https://{client Name}.maps.arcgis.com.

// portalURL = https://{client Name}.maps.arcgis.com
// agsCredential is a token obtained from www.arcgis.com
let portal = AGSPortal(url: portalURL, loginRequired: true)
        portal.credential = agsCredential
        let portalItem = AGSPortalItem(portal: portal, itemID: mapId)
        let map = AGSMap(item: portalItem)
        esriMapView.map = map

This DOES work:

Display a map after loading the www.arcgis.com portal with a separate credential first:

// agsCredential is obtained from www.arcgis.com
let arcGISPortal = AGSPortal.arcGISOnline(withLoginRequired: true)
            arcGISPortal.credential = agsCredential
            arcGISPortal.load { (error) in
                if let _ = error {
                    print("Failed loading/signing to acrGISOnline")
                }
            }
// portalURL = https://{client Name}.maps.arcgis.com
// agsCredential is the same as above
let portal = AGSPortal(url: portalURL, loginRequired: true)
        portal.credential = agsCredential
        let portalItem = AGSPortalItem(portal: portal, itemID: mapId)
        let map = AGSMap(item: portalItem)
        esriMapView.map = map

Is that correct? That's what I understood from #1012 (comment)

@aliawais
Copy link
Author

aliawais commented Dec 2, 2020

yes

@aliawais
Copy link
Author

Posting my solution here, may its helpful for others.

Once our API returns the token it generated from ARCGisPortal, the controller sets its self as delegate to following AGSAuthenticationManager.shared().delegate = self

AGSPortal is created without setting its AGSCredential property and I do following:

let portal = AGSPortal(url: portalURL, loginRequired: true)
        portal.load { (error) in
            if let _ = error {
                self.delegate?.mapLoadingFailed()
            } else {
                let portalItem = AGSPortalItem(portal: portal, itemID: mapId)
                self.loadWebMap(portalItem: portalItem)
                self.loadBaseMaps(portal: portal)
            }
        }

Following is how I have handled one of the delegate methods

func authenticationManager(_ authenticationManager: AGSAuthenticationManager, didReceive challenge: AGSAuthenticationChallenge) {
            // creating credentials from token returned from our API
            let agsCredentials = AGSCredential(token: credentialToken, referer: referer)
            challenge.continue(with: agsCredentials)
        }
    }

So now whenever the credentials challenge is received, the method continue with the AGSCredentials.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants