[{"data":1,"prerenderedAt":895},["ShallowReactive",2],{"blog-\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-2\u002F":3,"related-blog-\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-2\u002F":865},{"id":4,"title":5,"abstract":6,"author":6,"body":7,"description":848,"excerpt":6,"extension":849,"head":6,"image":6,"keywords":850,"meta":856,"modified":6,"navigation":119,"path":857,"proficiencyLevel":858,"published":859,"rawbody":860,"schemaOrg":6,"schemaType":861,"seo":862,"stem":863,"__hash__":864},"blog\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-2.md","Ferrostar: Building a Cross-Platform Navigation SDK in Rust (Part 2 - iOS Packaging)",null,{"type":8,"value":9,"toc":839},"minimark",[10,14,36,39,42,49,54,67,73,304,310,317,324,334,341,406,409,416,420,423,426,429,440,453,503,513,523,541,555,559,568,585,588,617,628,650,657,660,664,667,678,688,697,701,704,729,732,736,739,742,756,759,789,806,812,816,819,832,835],[11,12,5],"h1",{"id":13},"ferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-2-ios-packaging",[15,16,17,18,23,24,31,32,35],"p",{},"It's been a while since our ",[19,20,22],"a",{"href":21},"\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1","first deep dive","\ninto the tech behind ",[19,25,30],{"href":26,"rel":27,"target":29},"https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fferrostar\u002F?utm_source=marketing_site&utm_campaign=ferrostar_tech_blog_2&utm_medium=blog",[28],"external","_blank","Ferrostar",", our new turn-by-turn navigation SDK.\nAs a recap, our ",[19,33,34],{"href":21},"last post"," covered why we're writing the core in Rust,\nhow we approached both code and data model sharing,\nand looked at the architecture from a high level.",[15,37,38],{},"Writing some code for private use is one thing,\nbut publishing it for others to use can be maddeningly difficult.\nWhen the time came for us to publish Swift Packages,\nwe found something closer to the latter.",[15,40,41],{},"In this post, we'll cover the practical details of how we\ncross-compiled the Rust library for iOS,\npackaged it in an XCFramework,\nand published it as a Swift Package.\nThis is the missing manual we wish we had.",[15,43,44],{},[45,46],"img",{"alt":47,"src":48},"A screenshot of Ferrostar navigating on an iPhone","\u002Fimages\u002Fcontent\u002Fferrostar-landscape-screenshot.png",[50,51,53],"h2",{"id":52},"swift-packaging","Swift Packaging",[15,55,56,57,61,62,66],{},"Let's start off by looking at the Swift Package structure.\nWe'll need to add ",[58,59,60],"em",{},"two"," targets to our ",[63,64,65],"code",{},"Package.swift",":\na binary target for the static library,\nand a Swift target for the generated bindings.",[15,68,69,70,72],{},"Binary targets in Swift Package Manager can be tricky,\nand this complexity makes our ",[63,71,65],{}," file a bit unwieldy from the start.",[74,75,80],"pre",{"className":76,"code":77,"language":78,"meta":79,"style":79},"language-swift shiki shiki-themes github-light","let binaryTarget: Target\nlet useLocalFramework = false  \u002F\u002F NB: Set this to true when developing locally!\n\nif useLocalFramework {\n    binaryTarget = .binaryTarget(\n        name: \"FerrostarCoreRS\",\n        \u002F\u002F IMPORTANT: Swift packages importing this locally will not be able to\n        \u002F\u002F import Ferrostar core unless you specify this as a relative path!\n        path: \".\u002Fcommon\u002Ftarget\u002Fios\u002Flibferrostar-rs.xcframework\"\n    )\n} else {\n    \u002F\u002F Git stuff which we'll come back to at the end.\n    let releaseTag = \"0.23.0\"\n    let releaseChecksum = \"fa308b519db5424d73d00d60ca03fc18c1dcf2f88704aadce29259d12f2de2b2\"\n    binaryTarget = .binaryTarget(\n        name: \"FerrostarCoreRS\",\n        url:\n        \"https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Freleases\u002Fdownload\u002F\\(releaseTag)\u002Flibferrostar-rs.xcframework.zip\",\n        checksum: releaseChecksum\n    )\n}\n","swift","",[63,81,82,95,114,121,130,147,163,169,175,186,192,204,210,224,237,250,261,270,284,293,298],{"__ignoreMap":79},[83,84,87,91],"span",{"class":85,"line":86},"line",1,[83,88,90],{"class":89},"sD7c4","let",[83,92,94],{"class":93},"sgsFI"," binaryTarget: Target\n",[83,96,98,100,103,106,110],{"class":85,"line":97},2,[83,99,90],{"class":89},[83,101,102],{"class":93}," useLocalFramework ",[83,104,105],{"class":89},"=",[83,107,109],{"class":108},"sYu0t"," false",[83,111,113],{"class":112},"sAwPA","  \u002F\u002F NB: Set this to true when developing locally!\n",[83,115,117],{"class":85,"line":116},3,[83,118,120],{"emptyLinePlaceholder":119},true,"\n",[83,122,124,127],{"class":85,"line":123},4,[83,125,126],{"class":89},"if",[83,128,129],{"class":93}," useLocalFramework {\n",[83,131,133,136,138,141,144],{"class":85,"line":132},5,[83,134,135],{"class":93},"    binaryTarget ",[83,137,105],{"class":89},[83,139,140],{"class":93}," .",[83,142,143],{"class":108},"binaryTarget",[83,145,146],{"class":93},"(\n",[83,148,150,153,156,160],{"class":85,"line":149},6,[83,151,152],{"class":108},"        name",[83,154,155],{"class":93},": ",[83,157,159],{"class":158},"sYBdl","\"FerrostarCoreRS\"",[83,161,162],{"class":93},",\n",[83,164,166],{"class":85,"line":165},7,[83,167,168],{"class":112},"        \u002F\u002F IMPORTANT: Swift packages importing this locally will not be able to\n",[83,170,172],{"class":85,"line":171},8,[83,173,174],{"class":112},"        \u002F\u002F import Ferrostar core unless you specify this as a relative path!\n",[83,176,178,181,183],{"class":85,"line":177},9,[83,179,180],{"class":108},"        path",[83,182,155],{"class":93},[83,184,185],{"class":158},"\".\u002Fcommon\u002Ftarget\u002Fios\u002Flibferrostar-rs.xcframework\"\n",[83,187,189],{"class":85,"line":188},10,[83,190,191],{"class":93},"    )\n",[83,193,195,198,201],{"class":85,"line":194},11,[83,196,197],{"class":93},"} ",[83,199,200],{"class":89},"else",[83,202,203],{"class":93}," {\n",[83,205,207],{"class":85,"line":206},12,[83,208,209],{"class":112},"    \u002F\u002F Git stuff which we'll come back to at the end.\n",[83,211,213,216,219,221],{"class":85,"line":212},13,[83,214,215],{"class":89},"    let",[83,217,218],{"class":93}," releaseTag ",[83,220,105],{"class":89},[83,222,223],{"class":158}," \"0.23.0\"\n",[83,225,227,229,232,234],{"class":85,"line":226},14,[83,228,215],{"class":89},[83,230,231],{"class":93}," releaseChecksum ",[83,233,105],{"class":89},[83,235,236],{"class":158}," \"fa308b519db5424d73d00d60ca03fc18c1dcf2f88704aadce29259d12f2de2b2\"\n",[83,238,240,242,244,246,248],{"class":85,"line":239},15,[83,241,135],{"class":93},[83,243,105],{"class":89},[83,245,140],{"class":93},[83,247,143],{"class":108},[83,249,146],{"class":93},[83,251,253,255,257,259],{"class":85,"line":252},16,[83,254,152],{"class":108},[83,256,155],{"class":93},[83,258,159],{"class":158},[83,260,162],{"class":93},[83,262,264,267],{"class":85,"line":263},17,[83,265,266],{"class":108},"        url",[83,268,269],{"class":93},":\n",[83,271,273,276,279,282],{"class":85,"line":272},18,[83,274,275],{"class":158},"        \"https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Freleases\u002Fdownload\u002F",[83,277,278],{"class":158},"\\(releaseTag)",[83,280,281],{"class":158},"\u002Flibferrostar-rs.xcframework.zip\"",[83,283,162],{"class":93},[83,285,287,290],{"class":85,"line":286},19,[83,288,289],{"class":108},"        checksum",[83,291,292],{"class":93},": releaseChecksum\n",[83,294,296],{"class":85,"line":295},20,[83,297,191],{"class":93},[83,299,301],{"class":85,"line":300},21,[83,302,303],{"class":93},"}\n",[15,305,306,307,309],{},"Unfortunately, we haven't found a way to use the same ",[63,308,65],{},"\nunmodified for both local development within the \"project\" and for publishing.\n(To be fair, this is an extremely rare feature,\nbut it's worth noting that Cargo workspaces let you do this seamlessly with Rust crates.)\nSo, we need to add some switching logic to our package definition.",[15,311,312,313,316],{},"The above is the solution we came up with.\nWe always ensure that ",[63,314,315],{},"useLocalFramework = false"," for the version checked in to git.\nPublished versions will need to point to the released artifact,\nor else we'd require every developer to check out and build the whole project locally.",[15,318,319,320,323],{},"For local development and CI builds,\nwhere you may need unreleased changes in the Rust core,\nyou have to override the value to ",[63,321,322],{},"true",".\nIf anyone knows a cleaner solution to this, let us know!",[15,325,326,327,330,331,333],{},"Another complicating detail is that you ",[58,328,329],{},"need"," a checksum for remote binary downloads.\nThis is a safety measure built into the Swift Package Manager\nto prevent supply chain attacks.\nSince the checksum can't be known until build time,\nwe have CI rewrite this line in ",[63,332,65],{}," when publishing a release.\n(We'll cover this dance later.)",[15,335,336,337,340],{},"One target down... the other is fortunately a bit simpler.\nWe've called this ",[63,338,339],{},"FerrostarCoreFFI"," in our project,\nto make the distinction clear between the FFI bindings and the Rust binary.",[74,342,344],{"className":76,"code":343,"language":78,"meta":79,"style":79},".target(\n    name: \"FerrostarCoreFFI\",\n    dependencies: [.target(name: \"FerrostarCoreRS\")],\n    path: \"apple\u002FSources\u002FUniFFI\"\n)\n",[63,345,346,356,368,391,401],{"__ignoreMap":79},[83,347,348,351,354],{"class":85,"line":86},[83,349,350],{"class":93},".",[83,352,353],{"class":108},"target",[83,355,146],{"class":93},[83,357,358,361,363,366],{"class":85,"line":97},[83,359,360],{"class":108},"    name",[83,362,155],{"class":93},[83,364,365],{"class":158},"\"FerrostarCoreFFI\"",[83,367,162],{"class":93},[83,369,370,373,376,378,381,384,386,388],{"class":85,"line":116},[83,371,372],{"class":108},"    dependencies",[83,374,375],{"class":93},": [.",[83,377,353],{"class":108},[83,379,380],{"class":93},"(",[83,382,383],{"class":108},"name",[83,385,155],{"class":93},[83,387,159],{"class":158},[83,389,390],{"class":93},")],\n",[83,392,393,396,398],{"class":85,"line":123},[83,394,395],{"class":108},"    path",[83,397,155],{"class":93},[83,399,400],{"class":158},"\"apple\u002FSources\u002FUniFFI\"\n",[83,402,403],{"class":85,"line":132},[83,404,405],{"class":93},")\n",[15,407,408],{},"This is pure Swift and doesn't have any surprises.\nIt just depends on the binary target,\nand includes a source directory which will contain the generated bindings.",[15,410,411,412,415],{},"You'll need to add both the source and binary target to your list of ",[63,413,414],{},"targets",".\nYou can now reference them by name from any of your other targets,\nand\u002For publish them via products.",[50,417,419],{"id":418},"cross-compilation","Cross compilation",[15,421,422],{},"Now that we've looked at the package structure,\nlet's see what actually goes into the two targets.",[15,424,425],{},"We'll tackle cross compilation first.\nWe're going to compile the Rust code for all the relevant iOS target architectures,\njust like Xcode does for our Swift projects.",[15,427,428],{},"Cross compilation is quite tricky (maybe even traumatic) in some languages,\nbut Cargo is full of pleasant surprises.",[74,430,434],{"className":431,"code":432,"language":433,"meta":79,"style":79},"language-shell shiki shiki-themes github-light","cargo build --lib --release --target some-target-triple\n","shell",[63,435,436],{"__ignoreMap":79},[83,437,438],{"class":85,"line":86},[83,439,432],{},[15,441,442,443,448,449,452],{},"That's it!\nJust specify the ",[19,444,447],{"href":445,"rel":446,"target":29},"https:\u002F\u002Fdoc.rust-lang.org\u002Fnightly\u002Frustc\u002Fplatform-support.html",[28],"target triple","!\nFor iOS, we actually need to compile for ",[58,450,451],{},"three"," targets:",[454,455,456,469],"table",{},[457,458,459],"thead",{},[460,461,462,466],"tr",{},[463,464,465],"th",{},"Target triple",[463,467,468],{},"Description",[470,471,472,483,493],"tbody",{},[460,473,474,480],{},[475,476,477],"td",{},[63,478,479],{},"x86_64-apple-ios",[475,481,482],{},"Simulator for Intel-based Macs",[460,484,485,490],{},[475,486,487],{},[63,488,489],{},"aarch64-apple-ios-sim",[475,491,492],{},"Simulator for ARM-based Macs",[460,494,495,500],{},[475,496,497],{},[63,498,499],{},"aarch64-apple-ios",[475,501,502],{},"iOS devices",[15,504,505,506,509,510,350],{},"If you try running this on your machine right now,\nyou may get an error since you need to have the target toolchain installed.\nFortunately, this is easy.\nIf you're using ",[63,507,508],{},"rustup",", you can add any target with the command\n",[63,511,512],{},"rustup target add your-target-triple",[15,514,515,516,350],{},"For iOS, there are just three targets,\nbut we'll have a LOT more by the time we're done with this series.\nRemembering all of these is a lot of work,\nbut there's a relatively under-appreciated file that can help us out:\n",[19,517,520],{"href":518,"rel":519,"target":29},"https:\u002F\u002Frust-lang.github.io\u002Frustup\u002Foverrides.html#the-toolchain-file",[28],[63,521,522],{},"rust-toolchain.toml",[15,524,525,526,528,529,532,533,350],{},"If you're using ",[63,527,508],{}," to install ",[63,530,531],{},"cargo",",\nit will automatically install the relevant toolchains for you!\nWe can check this into git and call it a day.\nHere's ",[19,534,537,538,540],{"href":535,"rel":536,"target":29},"https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Fblob\u002Fmain\u002Fcommon\u002Frust-toolchain.toml",[28],"the full ",[63,539,522],{}," for Ferrostar",[15,542,543,544,546,547,554],{},"While this file is specifically designed for use with ",[63,545,508],{},",\nother build systems often have a way of consuming it too.\nFor example, Nix users could leverage ",[19,548,551],{"href":549,"rel":550,"target":29},"https:\u002F\u002Fgithub.com\u002Fnix-community\u002Ffenix",[28],[63,552,553],{},"fenix.fromToolchainFile","\nto get the same effect.",[50,556,558],{"id":557},"generating-the-ffi-bindings","Generating the FFI Bindings",[15,560,561,562,567],{},"Now that we have the static library built in its first form,\nwe need an easy way for Swift to interface with it.\nIt's ",[19,563,566],{"href":564,"rel":565,"target":29},"https:\u002F\u002Fgithub.com\u002Fmozilla\u002Funiffi-rs",[28],"UniFFI","'s time to shine!",[15,569,570,571,574,575,578,579,584],{},"Since our ",[19,572,573],{"href":21},"first post",", UniFFI has evolved a ",[63,576,577],{},"uniffi-bindgen-swift"," crate concept\nwith a slightly different CLI.\nNot a lot has changed, but it's worth a quick note in case you're on an older version.\nIf you're starting fresh, this ",[19,580,583],{"href":581,"rel":582,"target":29},"https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter",[28],"starter template","\nhas everything ready to go with the new setup.",[15,586,587],{},"Here's how we generate the bindings:",[589,590,591,601,611],"ol",{},[592,593,594,595,597,598,350],"li",{},"Run ",[63,596,577],{}," via ",[63,599,600],{},"cargo run -p",[592,602,603,604,607,608,610],{},"Copy the ",[58,605,606],{},"generated Swift file"," to the appropriate source directory (the ",[63,609,339],{}," target's source path in our example).",[592,612,603,613,616],{},[58,614,615],{},"clang module map"," into a framework staging directory.",[15,618,619,620,623,624,627],{},"Here's the relevant portion of our build script,\nwhere ",[63,621,622],{},"$1"," is the name of the library (",[63,625,626],{},"ferrostar"," in our case).",[74,629,633],{"className":630,"code":631,"language":632,"meta":79,"style":79},"language-zsh shiki shiki-themes github-light","cargo run -p uniffi-bindgen-swift -- target\u002Faarch64-apple-ios\u002Frelease\u002Flib$1.a target\u002Funiffi-xcframework-staging --swift-sources --headers --modulemap --module-name $1FFI --modulemap-filename module.modulemap\nmv target\u002Funiffi-xcframework-staging\u002F*.swift ..\u002Fapple\u002FSources\u002FUniFFI\u002F\nmv target\u002Funiffi-xcframework-staging\u002Fmodule.modulemap target\u002Funiffi-xcframework-staging\u002Fmodule.modulemap\n","zsh",[63,634,635,640,645],{"__ignoreMap":79},[83,636,637],{"class":85,"line":86},[83,638,639],{},"cargo run -p uniffi-bindgen-swift -- target\u002Faarch64-apple-ios\u002Frelease\u002Flib$1.a target\u002Funiffi-xcframework-staging --swift-sources --headers --modulemap --module-name $1FFI --modulemap-filename module.modulemap\n",[83,641,642],{"class":85,"line":97},[83,643,644],{},"mv target\u002Funiffi-xcframework-staging\u002F*.swift ..\u002Fapple\u002FSources\u002FUniFFI\u002F\n",[83,646,647],{"class":85,"line":116},[83,648,649],{},"mv target\u002Funiffi-xcframework-staging\u002Fmodule.modulemap target\u002Funiffi-xcframework-staging\u002Fmodule.modulemap\n",[15,651,652,654,655,350],{},[63,653,577],{}," looks at the static library\nand generates everything we need to smoothly interact with it from Swift.\nThis includes enums, protocols, library loading code, etc.;\nthis is where it does the magic that makes the Rust library feel like a native Swift package.\nWe just need to copy everything into the location we said in our ",[63,656,65],{},[15,658,659],{},"NOTE: It is possible to integrate these steps into Xcode.\nHowever, given the relative difficulty of doing this\nand the overall flakiness of the Xcode build process,\nwe opted for a simple, reliable shell script.\nThis does mean you need to manually rebuild when changing Rust files,\nbut that's a small step.",[50,661,663],{"id":662},"building-a-fat-library-universal-binary","Building a \"fat\" library (universal binary)",[15,665,666],{},"As a last step before creating the XCFramework,\nwe need to do something a bit funny and fuse two of our three binary targets together.",[15,668,669,670,673,674,677],{},"The way that XCFramework is designed,\nit expects a single binary for each ",[58,671,672],{},"platform",", not each ",[58,675,676],{},"CPU architecture",".\nAnd we have two binaries for the iOS Simulator platform:\none for the newer Apple Silicon and one for Intel.",[15,679,680,681,683,684,687],{},"To get down to one binary per ",[58,682,672],{}," (simulator and device),\nwe'll use an old tool, ",[63,685,686],{},"lipo"," to generate a \"fat\" binary with both architectures\nin a single file.",[74,689,691],{"className":630,"code":690,"language":632,"meta":79,"style":79},"lipo -create target\u002Fx86_64-apple-ios\u002Frelease\u002Flib$1.a target\u002Faarch64-apple-ios-sim\u002Frelease\u002Flib$1.a -output target\u002Fios-simulator-fat\u002Frelease\u002Flib$1.a\n",[63,692,693],{"__ignoreMap":79},[83,694,695],{"class":85,"line":86},[83,696,690],{},[50,698,700],{"id":699},"generating-the-xcframework","Generating the XCFramework",[15,702,703],{},"Now we finally put all of this together in an XCFramework.\nSome teams actually do this by hand,\nsince it's a relatively simple structure,\nbut we'll stick to Apple's official tooling.",[74,705,707],{"className":630,"code":706,"language":632,"meta":79,"style":79},"xcodebuild -create-xcframework \\\n    -library target\u002Faarch64-apple-ios\u002Frelease\u002Flib$1.a -headers target\u002Funiffi-xcframework-staging \\\n    -library target\u002Fios-simulator-fat\u002Frelease\u002Flib$1.a -headers target\u002Funiffi-xcframework-staging \\\n    -output target\u002Fios\u002Flib$1-rs.xcframework\n",[63,708,709,714,719,724],{"__ignoreMap":79},[83,710,711],{"class":85,"line":86},[83,712,713],{},"xcodebuild -create-xcframework \\\n",[83,715,716],{"class":85,"line":97},[83,717,718],{},"    -library target\u002Faarch64-apple-ios\u002Frelease\u002Flib$1.a -headers target\u002Funiffi-xcframework-staging \\\n",[83,720,721],{"class":85,"line":116},[83,722,723],{},"    -library target\u002Fios-simulator-fat\u002Frelease\u002Flib$1.a -headers target\u002Funiffi-xcframework-staging \\\n",[83,725,726],{"class":85,"line":123},[83,727,728],{},"    -output target\u002Fios\u002Flib$1-rs.xcframework\n",[15,730,731],{},"This command combines the two static libraries, header, and module map\ninto a single directory.\nAnd if all went well, we now have a working Swift package that you can test locally!",[50,733,735],{"id":734},"distributing-the-xcframework","Distributing the XCFramework",[15,737,738],{},"Now it's time to revisit the git and checksum dance that we glossed over at the start.",[15,740,741],{},"Here's our distribution checklist:",[589,743,744,747,750],{},[592,745,746],{},"Zip up the folder.",[592,748,749],{},"Compute a checksum for the archive.",[592,751,752,753,755],{},"Update ",[63,754,65],{}," with your release tag and checksum.",[15,757,758],{},"We script this in our CI actions like so:",[74,760,762],{"className":630,"code":761,"language":632,"meta":79,"style":79},"ditto -c -k --sequesterRsrc --keepParent target\u002Fios\u002Flib$1-rs.xcframework target\u002Fios\u002Flib$1-rs.xcframework.zip\nchecksum=$(swift package compute-checksum target\u002Fios\u002Flib$1-rs.xcframework.zip)\nversion=$(cargo metadata --format-version 1 | jq -r --arg pkg_name \"$1\" '.packages[] | select(.name==$pkg_name) .version')\nsed -i \"\" -E \"s\u002F(let releaseTag = \\\")[^\\\"]+(\\\")\u002F\\1$version\\2\u002Fg\" ..\u002FPackage.swift\nsed -i \"\" -E \"s\u002F(let releaseChecksum = \\\")[^\\\"]+(\\\")\u002F\\1$checksum\\2\u002Fg\" ..\u002FPackage.swift\n",[63,763,764,769,774,779,784],{"__ignoreMap":79},[83,765,766],{"class":85,"line":86},[83,767,768],{},"ditto -c -k --sequesterRsrc --keepParent target\u002Fios\u002Flib$1-rs.xcframework target\u002Fios\u002Flib$1-rs.xcframework.zip\n",[83,770,771],{"class":85,"line":97},[83,772,773],{},"checksum=$(swift package compute-checksum target\u002Fios\u002Flib$1-rs.xcframework.zip)\n",[83,775,776],{"class":85,"line":116},[83,777,778],{},"version=$(cargo metadata --format-version 1 | jq -r --arg pkg_name \"$1\" '.packages[] | select(.name==$pkg_name) .version')\n",[83,780,781],{"class":85,"line":123},[83,782,783],{},"sed -i \"\" -E \"s\u002F(let releaseTag = \\\")[^\\\"]+(\\\")\u002F\\1$version\\2\u002Fg\" ..\u002FPackage.swift\n",[83,785,786],{"class":85,"line":132},[83,787,788],{},"sed -i \"\" -E \"s\u002F(let releaseChecksum = \\\")[^\\\"]+(\\\")\u002F\\1$checksum\\2\u002Fg\" ..\u002FPackage.swift\n",[15,790,791,794,795,798,799,801,802,805],{},[63,792,793],{},"ditto"," is an archiving utility found on all macOS systems.\nWe use this to create the ZIP archive.\nThen, we compute the checksum using ",[63,796,797],{},"swift package compute-checksum",".\nThis utility is included in the Xcode Command-line Tools.\nFor versioning, our repository prefers to keep all platforms in sync,\nand derives all versions from the Rust project using a bit of CLI magic.\nTo update ",[63,800,65],{},", we use trusty old ",[63,803,804],{},"sed"," to rewrite the relevant lines in-place.",[15,807,808,809,811],{},"For your package to be usable by others,\nyou'll need to host your XCFramework for download somewhere and update ",[63,810,65],{},"\nwith an archive checksum and git tag.\nWe use GitHub's release artifact hosting since it's easy and free,\nbut you can also self-host the archive.",[50,813,815],{"id":814},"wrap-up","Wrap-up",[15,817,818],{},"Packaging a binary framework for iOS isn't easy,\nand the best practices have evolved in the last few years.\nWhich is probably why no comprehensive (modern) guide exists for our use case!\nWe hope this helps anyone else shipping binary frameworks on iOS.\nWe've been using this process for about a year and a half for Ferrostar,\nand have automated all the steps with shell scripts and CI workflows.",[15,820,821,822,826,827,350],{},"If you're curious to try this in your own project,\ncheck out the ",[19,823,825],{"href":581,"rel":824,"target":29},[28],"UniFFI starter",",\nwhich includes ready-to-go build scripts and all the rest of the project boilerplate.\nAnd for a \"real-world\" CI pipeline on GitHub Actions,\ncheck out the ",[19,828,831],{"href":829,"rel":830,"target":29},"https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Fblob\u002Fmain\u002F.github\u002Fworkflows\u002Fios-release.yml",[28],"iOS Release action for Ferrostar",[15,833,834],{},"In the next installment, we'll cover the build process and packaging for Android.\nGive us a follow on social media, join our Slack or Discord communities,\nor subscribe to our mailing list to get the news first!",[836,837,838],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}",{"title":79,"searchDepth":123,"depth":123,"links":840},[841,842,843,844,845,846,847],{"id":52,"depth":97,"text":53},{"id":418,"depth":97,"text":419},{"id":557,"depth":97,"text":558},{"id":662,"depth":97,"text":663},{"id":699,"depth":97,"text":700},{"id":734,"depth":97,"text":735},{"id":814,"depth":97,"text":815},"A deep dive into building and packaging a cross-platform navigation SDK for iOS.","md",[851,852,853,854,855,30],"Rust","iOS Development","Swift Package","XCFramework","Cross-Platform Development",{},"\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-2","Expert","2024-12-03","---\ndescription: \"A deep dive into building and packaging a cross-platform navigation SDK for iOS.\"\npublished: \"2024-12-03\"\nkeywords:\n  - Rust\n  - iOS Development\n  - Swift Package\n  - XCFramework\n  - Cross-Platform Development\n  - Ferrostar\nschemaType: TechArticle\nproficiencyLevel: Expert\n---\n\n# Ferrostar: Building a Cross-Platform Navigation SDK in Rust (Part 2 - iOS Packaging)\n\nIt's been a while since our [first deep dive](\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1)\ninto the tech behind [Ferrostar](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fferrostar\u002F?utm_source=marketing_site&utm_campaign=ferrostar_tech_blog_2&utm_medium=blog), our new turn-by-turn navigation SDK.\nAs a recap, our [last post](\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1) covered why we're writing the core in Rust,\nhow we approached both code and data model sharing,\nand looked at the architecture from a high level.\n\nWriting some code for private use is one thing,\nbut publishing it for others to use can be maddeningly difficult.\nWhen the time came for us to publish Swift Packages,\nwe found something closer to the latter.\n\nIn this post, we'll cover the practical details of how we\ncross-compiled the Rust library for iOS,\npackaged it in an XCFramework,\nand published it as a Swift Package.\nThis is the missing manual we wish we had.\n\n![A screenshot of Ferrostar navigating on an iPhone](\u002Fimages\u002Fcontent\u002Fferrostar-landscape-screenshot.png)\n\n## Swift Packaging\n\nLet's start off by looking at the Swift Package structure.\nWe'll need to add _two_ targets to our `Package.swift`:\na binary target for the static library,\nand a Swift target for the generated bindings.\n\nBinary targets in Swift Package Manager can be tricky,\nand this complexity makes our `Package.swift` file a bit unwieldy from the start.\n\n```swift\nlet binaryTarget: Target\nlet useLocalFramework = false  \u002F\u002F NB: Set this to true when developing locally!\n\nif useLocalFramework {\n    binaryTarget = .binaryTarget(\n        name: \"FerrostarCoreRS\",\n        \u002F\u002F IMPORTANT: Swift packages importing this locally will not be able to\n        \u002F\u002F import Ferrostar core unless you specify this as a relative path!\n        path: \".\u002Fcommon\u002Ftarget\u002Fios\u002Flibferrostar-rs.xcframework\"\n    )\n} else {\n    \u002F\u002F Git stuff which we'll come back to at the end.\n    let releaseTag = \"0.23.0\"\n    let releaseChecksum = \"fa308b519db5424d73d00d60ca03fc18c1dcf2f88704aadce29259d12f2de2b2\"\n    binaryTarget = .binaryTarget(\n        name: \"FerrostarCoreRS\",\n        url:\n        \"https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Freleases\u002Fdownload\u002F\\(releaseTag)\u002Flibferrostar-rs.xcframework.zip\",\n        checksum: releaseChecksum\n    )\n}\n```\n\nUnfortunately, we haven't found a way to use the same `Package.swift`\nunmodified for both local development within the \"project\" and for publishing.\n(To be fair, this is an extremely rare feature,\nbut it's worth noting that Cargo workspaces let you do this seamlessly with Rust crates.)\nSo, we need to add some switching logic to our package definition.\n\nThe above is the solution we came up with.\nWe always ensure that `useLocalFramework = false` for the version checked in to git.\nPublished versions will need to point to the released artifact,\nor else we'd require every developer to check out and build the whole project locally.\n\nFor local development and CI builds,\nwhere you may need unreleased changes in the Rust core,\nyou have to override the value to `true`.\nIf anyone knows a cleaner solution to this, let us know!\n\nAnother complicating detail is that you _need_ a checksum for remote binary downloads.\nThis is a safety measure built into the Swift Package Manager\nto prevent supply chain attacks.\nSince the checksum can't be known until build time,\nwe have CI rewrite this line in `Package.swift` when publishing a release.\n(We'll cover this dance later.)\n\nOne target down... the other is fortunately a bit simpler.\nWe've called this `FerrostarCoreFFI` in our project,\nto make the distinction clear between the FFI bindings and the Rust binary.\n\n```swift\n.target(\n    name: \"FerrostarCoreFFI\",\n    dependencies: [.target(name: \"FerrostarCoreRS\")],\n    path: \"apple\u002FSources\u002FUniFFI\"\n)\n```\n\nThis is pure Swift and doesn't have any surprises.\nIt just depends on the binary target,\nand includes a source directory which will contain the generated bindings.\n\nYou'll need to add both the source and binary target to your list of `targets`.\nYou can now reference them by name from any of your other targets,\nand\u002For publish them via products.\n\n## Cross compilation\n\nNow that we've looked at the package structure,\nlet's see what actually goes into the two targets.\n\nWe'll tackle cross compilation first.\nWe're going to compile the Rust code for all the relevant iOS target architectures,\njust like Xcode does for our Swift projects.\n\nCross compilation is quite tricky (maybe even traumatic) in some languages,\nbut Cargo is full of pleasant surprises.\n\n```shell\ncargo build --lib --release --target some-target-triple\n```\n\nThat's it!\nJust specify the [target triple](https:\u002F\u002Fdoc.rust-lang.org\u002Fnightly\u002Frustc\u002Fplatform-support.html)!\nFor iOS, we actually need to compile for _three_ targets:\n\n| Target triple           | Description                    |\n|-------------------------|--------------------------------|\n| `x86_64-apple-ios`      | Simulator for Intel-based Macs |\n| `aarch64-apple-ios-sim` | Simulator for ARM-based Macs   |\n| `aarch64-apple-ios`     | iOS devices                    |\n\nIf you try running this on your machine right now,\nyou may get an error since you need to have the target toolchain installed.\nFortunately, this is easy.\nIf you're using `rustup`, you can add any target with the command\n`rustup target add your-target-triple`.\n\nFor iOS, there are just three targets,\nbut we'll have a LOT more by the time we're done with this series.\nRemembering all of these is a lot of work,\nbut there's a relatively under-appreciated file that can help us out:\n[`rust-toolchain.toml`](https:\u002F\u002Frust-lang.github.io\u002Frustup\u002Foverrides.html#the-toolchain-file).\n\nIf you're using `rustup` to install `cargo`,\nit will automatically install the relevant toolchains for you!\nWe can check this into git and call it a day.\nHere's [the full `rust-toolchain.toml` for Ferrostar](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Fblob\u002Fmain\u002Fcommon\u002Frust-toolchain.toml).\n\nWhile this file is specifically designed for use with `rustup`,\nother build systems often have a way of consuming it too.\nFor example, Nix users could leverage [`fenix.fromToolchainFile`](https:\u002F\u002Fgithub.com\u002Fnix-community\u002Ffenix)\nto get the same effect.\n\n## Generating the FFI Bindings\n\nNow that we have the static library built in its first form,\nwe need an easy way for Swift to interface with it.\nIt's [UniFFI](https:\u002F\u002Fgithub.com\u002Fmozilla\u002Funiffi-rs)'s time to shine!\n\nSince our [first post](\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1), UniFFI has evolved a `uniffi-bindgen-swift` crate concept\nwith a slightly different CLI.\nNot a lot has changed, but it's worth a quick note in case you're on an older version.\nIf you're starting fresh, this [starter template](https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter)\nhas everything ready to go with the new setup.\n\nHere's how we generate the bindings:\n\n1. Run `uniffi-bindgen-swift` via `cargo run -p`.\n2. Copy the _generated Swift file_ to the appropriate source directory (the `FerrostarCoreFFI` target's source path in our example).\n3. Copy the _clang module map_ into a framework staging directory.\n\nHere's the relevant portion of our build script,\nwhere `$1` is the name of the library (`ferrostar` in our case).\n\n```zsh\ncargo run -p uniffi-bindgen-swift -- target\u002Faarch64-apple-ios\u002Frelease\u002Flib$1.a target\u002Funiffi-xcframework-staging --swift-sources --headers --modulemap --module-name $1FFI --modulemap-filename module.modulemap\nmv target\u002Funiffi-xcframework-staging\u002F*.swift ..\u002Fapple\u002FSources\u002FUniFFI\u002F\nmv target\u002Funiffi-xcframework-staging\u002Fmodule.modulemap target\u002Funiffi-xcframework-staging\u002Fmodule.modulemap\n```\n\n`uniffi-bindgen-swift` looks at the static library\nand generates everything we need to smoothly interact with it from Swift.\nThis includes enums, protocols, library loading code, etc.;\nthis is where it does the magic that makes the Rust library feel like a native Swift package.\nWe just need to copy everything into the location we said in our `Package.swift`.\n\nNOTE: It is possible to integrate these steps into Xcode.\nHowever, given the relative difficulty of doing this\nand the overall flakiness of the Xcode build process,\nwe opted for a simple, reliable shell script.\nThis does mean you need to manually rebuild when changing Rust files,\nbut that's a small step.\n\n## Building a \"fat\" library (universal binary)\n\nAs a last step before creating the XCFramework,\nwe need to do something a bit funny and fuse two of our three binary targets together.\n\nThe way that XCFramework is designed,\nit expects a single binary for each _platform_, not each _CPU architecture_.\nAnd we have two binaries for the iOS Simulator platform:\none for the newer Apple Silicon and one for Intel.\n\nTo get down to one binary per _platform_ (simulator and device),\nwe'll use an old tool, `lipo` to generate a \"fat\" binary with both architectures\nin a single file.\n\n```zsh\nlipo -create target\u002Fx86_64-apple-ios\u002Frelease\u002Flib$1.a target\u002Faarch64-apple-ios-sim\u002Frelease\u002Flib$1.a -output target\u002Fios-simulator-fat\u002Frelease\u002Flib$1.a\n```\n\n## Generating the XCFramework\n\nNow we finally put all of this together in an XCFramework.\nSome teams actually do this by hand,\nsince it's a relatively simple structure,\nbut we'll stick to Apple's official tooling.\n\n```zsh\nxcodebuild -create-xcframework \\\n    -library target\u002Faarch64-apple-ios\u002Frelease\u002Flib$1.a -headers target\u002Funiffi-xcframework-staging \\\n    -library target\u002Fios-simulator-fat\u002Frelease\u002Flib$1.a -headers target\u002Funiffi-xcframework-staging \\\n    -output target\u002Fios\u002Flib$1-rs.xcframework\n```\n\nThis command combines the two static libraries, header, and module map\ninto a single directory.\nAnd if all went well, we now have a working Swift package that you can test locally!\n\n## Distributing the XCFramework\n\nNow it's time to revisit the git and checksum dance that we glossed over at the start.\n\nHere's our distribution checklist:\n\n1. Zip up the folder.\n2. Compute a checksum for the archive.\n3. Update `Package.swift` with your release tag and checksum.\n\nWe script this in our CI actions like so:\n\n```zsh\nditto -c -k --sequesterRsrc --keepParent target\u002Fios\u002Flib$1-rs.xcframework target\u002Fios\u002Flib$1-rs.xcframework.zip\nchecksum=$(swift package compute-checksum target\u002Fios\u002Flib$1-rs.xcframework.zip)\nversion=$(cargo metadata --format-version 1 | jq -r --arg pkg_name \"$1\" '.packages[] | select(.name==$pkg_name) .version')\nsed -i \"\" -E \"s\u002F(let releaseTag = \\\")[^\\\"]+(\\\")\u002F\\1$version\\2\u002Fg\" ..\u002FPackage.swift\nsed -i \"\" -E \"s\u002F(let releaseChecksum = \\\")[^\\\"]+(\\\")\u002F\\1$checksum\\2\u002Fg\" ..\u002FPackage.swift\n```\n\n`ditto` is an archiving utility found on all macOS systems.\nWe use this to create the ZIP archive.\nThen, we compute the checksum using `swift package compute-checksum`.\nThis utility is included in the Xcode Command-line Tools.\nFor versioning, our repository prefers to keep all platforms in sync,\nand derives all versions from the Rust project using a bit of CLI magic.\nTo update `Package.swift`, we use trusty old `sed` to rewrite the relevant lines in-place.\n\nFor your package to be usable by others,\nyou'll need to host your XCFramework for download somewhere and update `Package.swift`\nwith an archive checksum and git tag.\nWe use GitHub's release artifact hosting since it's easy and free,\nbut you can also self-host the archive.\n\n## Wrap-up\n\nPackaging a binary framework for iOS isn't easy,\nand the best practices have evolved in the last few years.\nWhich is probably why no comprehensive (modern) guide exists for our use case!\nWe hope this helps anyone else shipping binary frameworks on iOS.\nWe've been using this process for about a year and a half for Ferrostar,\nand have automated all the steps with shell scripts and CI workflows.\n\nIf you're curious to try this in your own project,\ncheck out the [UniFFI starter](https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter),\nwhich includes ready-to-go build scripts and all the rest of the project boilerplate.\nAnd for a \"real-world\" CI pipeline on GitHub Actions,\ncheck out the [iOS Release action for Ferrostar](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Fblob\u002Fmain\u002F.github\u002Fworkflows\u002Fios-release.yml).\n\nIn the next installment, we'll cover the build process and packaging for Android.\nGive us a follow on social media, join our Slack or Discord communities,\nor subscribe to our mailing list to get the news first!\n","TechArticle",{"title":5,"description":848},"blog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-2","f0rjv0OjtbYLOvAniwhV2Cod5JvY7CmqkoYHKFUCUiE",[866,874,885],{"title":867,"description":868,"path":21,"published":869,"keywords":870,"rawbody":873},"Ferrostar: Building a Cross-Platform Navigation SDK in Rust (Part 1)","The first in a series of technical blog posts covering cross-platform mobile development in Rust.","2023-11-15",[851,871,872,855,30],"Mobile Development","Navigation SDK","---\ndescription: \"The first in a series of technical blog posts covering cross-platform mobile development in Rust.\"\npublished: \"2023-11-15\"\nupdated: \"2024-02-06\"\nkeywords:\n  - \"Rust\"\n  - \"Mobile Development\"\n  - \"Navigation SDK\"\n  - \"Cross-Platform Development\"\n  - \"Ferrostar\"\nschemaType: TechArticle\nproficiencyLevel: Expert\n---\n\n# Ferrostar: Building a Cross-Platform Navigation SDK in Rust (Part 1)\n\nThis is the first in a series of technical blog posts\ncovering the joys and challenges\nof building a cross-platform shared library,\nwith a focus on use in mobile applications.\n\nAs a motivating example to frame things,\nwe'll follow the journey of building Ferrostar,\na free and open-source navigation SDK.\nWe want to offer a modern SDK that is cross-platform,\nvendor-neutral, and easily extensible\nboth by direct contributors and developers using it in their apps.\n\n## Why?\n\nBefore we get into the technical weeds too much,\nit's worth taking a moment to ask _why_.\nAt Stadia Maps,\nwe have offered [highly customizable routing](\u002Fproducts\u002Frouting-navigation\u002F)\nsince 2017,\nand support use cases including safe pedestrian routing through lit areas,\ngolf cart routing that prefers multi-use paths,\nand more!\nBut developers are on their own\nwhen it comes to offering a turn-by-turn navigation UI.\nSeveral open-source apps already offer navigation,\nbut the solution is not generalized.\nAmong open-source navigation SDKs,\nMapLibre Navigation Android being the most mature at the time of this writing.\nBut as the name suggests, it is Android-specific.\nAnd since the internals originally came from old open-source Mapbox code,\nit takes considerable effort to use with other vendors,\nand extensibility was never a core design consideration.\n\nStarting a new navigation SDK is certainly an ambitious project,\nbut at the moment there is no navigation SDK that is\nhigh-quality, cross-platform, and open-source.\nWe're here to change that,\nand enable developers to bring these routing innovations to their users.\n\nThis post series is about the nuts and bolts of how we plan to pull it off.\nWe'll start with an overview of ways to share common logic across platforms,\nexplain the architecture we settled on,\nand finish with a deep dive into the first technical topic:\nbinding generation with [UniFFI](https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002F).\n\n## How to Share Common Logic?\n\nWe want to target multiple platforms: iOS and Android for starters.\nCertain code is the same regardless of where you're running it.\nAlgorithms like detecting if the user has strayed off the route\nand calculating the distance to the next turn\nshould be written once and shared across platforms.\nAnd at an even higher level,\nthe broad \"business logic\" of a turn-by-turn navigation experience\nshould be common shared code.\n\nBroadly speaking, there are two common approaches\nfor sharing code across platforms in mobile apps today.\nFirst, there are cross-platform app development frameworks like\nFlutter, React Native, or Kotlin Multiplatform.\nThey promise the ability to write your business logic and UI once\nand run it anywhere.\nThe second approach is to put your business logic in a shared library.\nYou write your code in a reasonably portable language (usually C++)\nand link this with your (platform-specific) applications.\n\nIn general,\nthe cross-platform frameworks are optimized for building *applications*,\nnot *libraries*.\nWe want to ensure that we can support additional platforms\nwith relative ease.\nYou can't just pick up React Native, for example,\nand run it on a bicycle computer.\nThis is situation is improving though.\nFor example, Flutter now runs on certain embedded devices,\nand Kotlin Multiplatform has a quickly improving support for library targets.\nHowever, these are still very early stage developments,\nand they involve a certain amount of ecosystem lock-in.\n\nGiven our requirements, we opted for the shared library approach.\nHistorically, C++ has been a popular choice for the task.\nIt is portable across a wide range of platforms,\ncan be made to interoperate with almost any other language,\nand usually produces small, well-optimized binaries.\nWe preferred to use something else for a few reasons,\nthe most important being\nthat it is difficult to write safe and correct C++ code,\nespecially as a new contributor.\n\nAt Stadia Maps, we have been shipping Rust in production since 2018.\nOur investment has rewarded us with\nconsistently good performance (Rust is keen on zero-cost abstractions),\nmemory safety,\nand excellent maintainability.\nAnd Rust code tends to have fewer bugs as well!\nRust also has excellent interoperability with other languages\nvia a straightforward FFI using the platform's C ABI,\nin stark contrast to C++ which can be rather complex to interface with.\nRust also has a large and growing list of supported platforms,\nincluding iOS and Android on several architectures.\n\nWe aren't alone in our selection of Rust either.\nWe've seen a lot of exciting development in Rust mobile frameworks\nlike [Crux](https:\u002F\u002Fredbadger.github.io\u002Fcrux\u002F),\n[Dioxus](https:\u002F\u002Fdioxuslabs.com\u002F),\nand [Rinf](https:\u002F\u002Fgithub.com\u002Fcunarist\u002Frinf) over the past year.\nBut it's not just new experimental projects that are adopting Rust.\nWe know that both [Mozilla](https:\u002F\u002Fwiki.mozilla.org\u002FOxidation)\nand [Lyft](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=e2ID05dpJzo)\nare using Rust shared libraries in their mobile apps.\nRust is already being used in production mobile apps today.\n\n## Architecture Overview\n\nZooming out, let's look at the broader architecture we adopted.\nThe core is the place where we want all shared logic to live.\nIn the context of our navigation SDK,\nthis includes parsing API responses,\nfiguring out when to advance navigation to the next step,\ncalculating the distance to the next turn,\nand so on.\n\nShared logic in a \"functional core\" isn't new,\nbut we can go one step further and define our data models too!\nThis lets us keep the navigation logic vendor- and platform-agnostic.\nFor example,\nwe define a common location update type with fields like\ncoordinate, timestamp, and heading.\nThis way the core logic doesn't need to care about the differences between\n`CLLocation` and `android.location.Location`.\n\nWe're building a Rust library that's callable via the C ABI.\nBut if we want any mobile devs to even try our framework,\nwe'll need to expose a nicer Swift\u002FKotlin interface.\nThese are called bindings,\nand they provide a thin layer that hides the messy details\nof going back and forth between Rust and the \"native\" mobile code.\n\nFinally, we have the native mobile library.\nThis is the only thing that most app developers will see,\nand it handles communications with the \"outside world\" (ex: internet and GPS),\nand provides a higher level API that can be a bit more \"opinionated.\"\nWe won't say much more about this layer in the rest of our discussions,\nas many volumes have been written on good API and library design.\n\n**Spoiler to avert anxiety and frantic typing**: the rest of this blog goes deep into\n\"how the sausage is made.\"\nTo save you the tedious work of reproducing the structure for your next library,\nwe've put together a [public template repo](https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter)\non GitHub which is ready to go with no typing or other fiddling required.\nWith that, let's dive in!\n\n![Ferrostar Architecture Diagram](\u002Fimages\u002Fcontent\u002Fferrostar-arch.png)\n\n## Bindings: Bridging Rust and Platform Code\n\nIf you're familiar with the concept of foreign function interfaces (FFIs),\nyou may remember `extern` declarations,\ncalling convention specifications,\nand having to write a lot of boilerplate.\nBut you know what is really good at generating boilerplate?\nComputers!\nEnter: binding generators.\n\nMozilla has developed a fantastic tool for binding generation:\n[UniFFI](https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002F).\nIn their case, the motivation was to build pieces of Firefox\nwhich could be shared across platforms.\nUniFFI is designed to generate safe bindings that feel idiomatic\nto users of the target language.\nThe [design principles](https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002Finternals\u002Fdesign_principles.html)\nof UniFFI aligned well with our requirements for Ferrostar,\nand we think they tend to be a good fit for most similar mobile use cases.\n\n### Cargo workspace setup\n\nLet's dive in with discussing the cargo workspace setup for Ferrostar.\nThis should be a helpful guide for anyone wanting to create their own\ncross-platform library.\n\nWe chose to structure the Rust portion of our library as a workspace\nwith two member projects: `uniffi-bindgen` and `ferrostar`.\nAt the time of this writing,\nthe ability to run the binary from the `uniffi` crate\nis only available in nightly,\nso we opted for a second `uniffi-bindgen` crate in our workspace\nfor the CLI binary.\nHere's what it looks like on disk:\n\n```\n├── Cargo.lock\n├── Cargo.toml\n├── ferrostar\n│   ├── Cargo.toml\n│   └── src\n│       └── lib.rs\n└── uniffi-bindgen\n    ├── Cargo.toml\n    └── src\n        └── main.rs\n```\n\nScaffolding in place, we need to add UniFFI to our workspace's `Cargo.toml`.\nThis way we can keep the binding generator and library versions in sync.\nAt the time this article was originally published,\nproc macro features were still under active development.\nSince then, they have stabilized somewhat,\nand it might make sense to start using the crates.io releases.\nIf you want the newest features and fixes though,\nit's best to use git dependencies.\nTracking the latest commits can cause pain,\nbut it is pretty rewarding if you want access to the latest improvements,\nwhich do come quite fast.\n\n```toml\n[workspace]\n\nmembers = [\n    \"uniffi-bindgen\",\n    \"ferrostar\",\n]\nresolver = \"2\"\n\n[workspace.dependencies]\nuniffi = \"0.26.1\"\n```\n\nThen, we can add it to the `uniffi-bindgen` and `ferrostar` crates respectively.\nIn the `uniffi-bindgen` crate, we need the `cli` feature.\n\n```toml\n[dependencies]\nuniffi = { workspace = true, features = [\"cli\"] }\n```\n\nThe `main` function in `uniffi-bindgen\u002Fsrc\u002Fmain.rs` is a single line.\nHere's the entire file:\n\n```rust\nfn main() {\n    uniffi::uniffi_bindgen_main()\n}\n```\n\nIn the `ferrostar` crate, we also need `uniffi` as a normal dependency\n*and* build dependency.\nWe also need to configure the library target with a few different\n[`crate_type`s](https:\u002F\u002Fdoc.rust-lang.org\u002Freference\u002Flinkage.html).\n\n* `cdylib` - A dynamic system library. Used on most platforms.\n* `staticlib` - A system library with *all* upstream dependencies included.\n  Required to target iOS.\n* `lib` - A Rust library.\n  This isn't mentioned in the UniFFI documentation as of this writing,\n  but if you want to add integration tests (runnable via `cargo test`)\n  and you don't include a Rust library target,\n  you'll get strange errors.\n\n```toml\n[dependencies]\nuniffi.workspace = true\n\n[build-dependencies]\nuniffi = { workspace = true, features = [\"build\"] }\n\n[lib]\ncrate-type = [\"cdylib\", \"staticlib\", \"lib\"]\n```\n\n### Defining what to export\n\nInternally, UniFFI uses an IDL (Interface Definition Language) to describe\nthe public interface of your crate to foreign code (ex: Swift and Kotlin).\nThis is similar to the process of writing a header file in C\nin that it is a set of definitions,\nbut the definition file is used to generate the foreign language bindings.\n\nThis used to require writing a bunch of boilerplate by hand.\nFortunately, UniFFI (starting from v0.25.0) automates this using procedural macros.\nFor our use case,\nwe were able to replace *all* handwritten UDL with macros,\nand expect this will be the best path for most library authors going forward.\n\n#### Functions\n\nLet's get started by exporting a function from Rust\nwhich generates a parser for routing data coming from our APIs.\nA single macro, `uniffi:export` is all we need to export a top-level function.\n\n```rust\n#[uniffi::export]\nfn create_osrm_response_parser(polyline_precision: u32) -> Arc\u003Cdyn RouteResponseParser> {\n    Arc::new(OsrmResponseParser::new(polyline_precision))\n}\n```\n\nUniFFI macros generate the definitions for us automatically\nbased on the function's type signature.\nMany built-in types like numbers, `String`, and `bool`\n\"just work\" as you expect.\nCollections like `Vec\u003CT>`, `Option\u003CT>` and `HashMap\u003CK, V>` are also implemented\nfor all types that UniFFI knows how to represent over the FFI.\nThey even have special cases for types like `Vec\u003Cu8>`\nwhich idiomatically map to a language-specific byte sequence type\n(ex: `Data` in Swift)!\n\nIf anything you export references a type\nthat UniFFI doesn't know how to convert,\nyou will get a compilation error.\nSo, how do we tell UniFFI about new types?\n\n#### Data models\n\nLet's look at two data models: user locations and route requests.\nWe'll export these using derive macros.\nIf you've used the `serde` crate, this should feel familiar.\n\nUser location is just a struct with some properties like coordinates\nand course over ground.\nThe `uniffi::Record` macro exposes our Rust types idiomatically.\nThey will show up as `struct`s in Swift, and `data class`es in Kotlin.\n\n```rust\n#[derive(uniffi::Record)]\npub struct UserLocation {\n    pub coordinates: GeographicCoordinates,\n    pub horizontal_accuracy: f64,\n    pub course_over_ground: Option\u003CCourseOverGround>,\n    pub timestamp: SystemTime,\n}\n```\n\nRoute requests are a little different.\nThey don't really map to a record type.\nSince we want to build an extensible framework,\nwe need to account for multiple ways of getting a route.\nFor example, making an HTTP request, or computing a route on-device.\n\nThis maps well to an enumeration.\nThe `uniffi::Enum` derive macro exports these like you would expect.\n\n```rust\n#[derive(uniffi::Enum)]\npub enum RouteRequest {\n    HttpPost {\n        url: String,\n        headers: HashMap\u003CString, String>,\n        body: Vec\u003Cu8>,\n    },\n    \u002F\u002F ...\n}\n```\n\n#### Objects\n\nYou can also export more complex objects\nwith local state, methods, and constructors!\n\nOne example of this in Ferrostar is our route adapter.\nIt stores a request generator and response parser\nand exposes a generic interface\nso the caller doesn't need to know about the details.\n\n```rust\n#[derive(uniffi::Object)]\npub struct RouteAdapter {\n    request_generator: Arc\u003Cdyn RouteRequestGenerator>,\n    response_parser: Arc\u003Cdyn RouteResponseParser>,\n}\n\n#[uniffi::export]\nimpl RouteAdapter {\n    #[uniffi::constructor]\n    pub fn new(\n        request_generator: Arc\u003Cdyn RouteRequestGenerator>,\n        response_parser: Arc\u003Cdyn RouteResponseParser>,\n    ) -> Self {\n        Self {\n            request_generator,\n            response_parser,\n        }\n    }\n\n    #[uniffi::constructor]\n    pub fn new_valhalla_http(endpoint_url: String, profile: String) -> Arc\u003CSelf> {\n        let request_generator = create_valhalla_request_generator(endpoint_url, profile);\n        let response_parser = create_osrm_response_parser(6);\n        Self::new(request_generator, response_parser)\n    }\n\n    pub fn generate_request(\n        &self,\n        user_location: UserLocation,\n        waypoints: Vec\u003CGeographicCoordinates>,\n    ) -> Result\u003CRouteRequest, RoutingRequestGenerationError> {\n        self.request_generator\n            .generate_request(user_location, waypoints)\n    }\n\n    pub fn parse_response(\n        &self,\n        response: Vec\u003Cu8>,\n    ) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError> {\n        self.response_parser.parse_response(response)\n    }\n}\n```\n\nThis example is a bit more complicated than the others we've looked at so far.\nFirst, we use the `uniffi::Object` derive macro on the struct.\nIn contrast to `uniffi::Record`,\nthis signals that the type will be passed by reference and may have methods.\nThis will be exposed in Swift or Kotlin as a class.\n\nYou must annotate your constructors with the `uniffi::constructor` macro.\nAt the time of this writing, constructors must return an `Arc\u003CSelf>`.\nThe `new` constructor is special, and is exposed most naturally,\nbut other named constructors are also supported if you mark them as such.\nThey are not currently as idiomatic as the default constructor,\nbut I expect things like exposing a `convenience init` to Swift\nwill be possible in the future.\n\nFinally, types exported with `uniffi::Object` tell the binding generator\nto emit a `protocol` (Swift) or `interface` (Kotlin) definition.\n**Pro tip:** This makes it easier for you to write mock implementations\nfor unit testing!\nOur rule of thumb is to _pass_ the protocol\u002Finterface to your native methods,\nand define your instance variables with protocol\u002Finterface types.\nWhen the time comes to construct one,\nyour business logic will dictate which _concrete_ implementation to construct.\nThis makes your code significantly more extensible,\nand allows you to either use implementations from the Rust core\nor define your own in Swift\u002FKotlin.\n\n#### Errors\n\nResult types in Rust signal errors,\nand UniFFI can expose these idiomatically in the generated bindings.\nWe use this pattern in the above example to signal response parsing errors.\n\n```rust\n#[derive(Debug, thiserror::Error, uniffi::Error)]\npub enum RoutingResponseParseError {\n    #[error(\"Failed to parse route response: {error}.\")]\n    ParseError { error: String },\n    \u002F\u002F ...\n    #[error(\"An unknown error parsing a response was raised in foreign code.\")]\n    UnknownError,\n}\n```\n\nThe `uniffi::Error` derive macro will work with any type\nconforming to `std::Error`.\nWe make extensive use of `thiserror` for simplicity.\nIn Swift and Kotlin code,\nthis definition will be bridged into an enum conforming to `Error`\nand a sealed subclass of `Exception` respectively.\n\nBesides exposing the error types themselves,\nUniFFI also makes the error handling idiomatic.\nLet's return to the response parsing function signature.\n\n```rust\npub fn parse_response(\n    &self,\n    response: Vec\u003Cu8>,\n) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError>\n```\n\nSemantically speaking,\nthis function will either return an ordered list of routes,\nor it will generate an error of type `RoutingResponseParseError`.\nAs such, this is how the generated Swift bindings will look:\n\n```swift\npublic func parseResponse(response: Data) throws -> [Route]\n```\n\nSwift does not explicitly list error types, but uses the `throws` keyword.\nIf you inspect the generated code,\nyou can see that UniFFI does indeed handle error variants,\nmapping them to your error types.\n\n#### Traits\n\nCarrying on the motivating use case of vendor-agnostic routing API integration,\nwe define traits in our crate for response parsing\nand similar things that we want to ensure remain generic.\n\n```rust\n#[uniffi::export(with_foreign)]\npub trait RouteResponseParser: Send + Sync {\n    \u002F\u002F\u002F Parses a raw response from the routing backend into a route.\n    fn parse_response(&self, response: Vec\u003Cu8>) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError>;\n}\n```\n\nYou can export traits with the `uniffi::export` macro,\nand will they show up as protocols\u002Finterfaces in your foreign code.\nEven better, if you use `with_foreign`, you can supply foreign code to Rust!\nIt requires a bit of a dance Rust-side\n(you must use types of the form `Arc\u003Cdyn Trait>` at the time of this writing),\nbut that's a small price to pay for such flexibility.\n\nHere's what it looks like in Swift.\nFirst, the Rust `trait` is translated into a Swift `protocol` automatically\nby the UniFFI binding generator.\nThe method even has `throws`, just like you'd write in a pure Swift project!\n\n```swift\npublic protocol RouteResponseParser: AnyObject {\n    \u002F**\n     * Parses a raw response from the routing backend into a route.\n     *\n     * We use a sequence of octets as a common interchange format.\n     * as this works for all currently conceivable formats (JSON, PBF, etc.).\n     *\u002F\n    func parseResponse(response: Data) throws -> [Route]\n}\n```\n\nNow we can implement it just like any other protocol.\nHere's an example of an implementation that we use in our testing.\nIt's a mock that is initialized with a fixed set of routes for\n\"end-to-end\" tests without hitting a network service.\nIn the real world, you might use `Codable`\nto convert the raw data into a Swift model\nand then map it to Ferrostar `Route`s.\n\n```swift\nclass MockRouteResponseParser: RouteResponseParser {\n    private let routes: [Route]\n\n    init(routes: [Route]) {\n        self.routes = routes\n    }\n\n    func parseResponse(response _: Data) throws -> [Route] {\n        routes\n    }\n}\n```\n\nBy making things like `RouteRequestGenerator` and `RouteResponseParser` traits,\nwe make it possible for anyone to do the following:\n\n1. Connect to a custom routing server (for example, one they are self-hosting).\n2. Parse routes in a different format.\n3. Generate routes on-device (in another library, Swift, Kotlin, etc.).\n\nWe're really excited about how this will enable more innovation by developers,\nsince Ferrostar will be easily adaptable to custom routing backends,\nboth for research and commercial use.\n\n## What works well?\n\nThe APIs UniFFI generates truly feel idiomatic!\nIt's hard to overstate how nice this is.\nIt also goes out of its way to generate protocols\u002Finterfaces,\nwhich make it easy to mock parts for unit testing in native code,\nand even has pleasant surprises\nlike `Vec\u003Cu8>` mapping to idiomatic byte sequence types.\n\nUniFFI encourages good hygiene all the way from Rust to your application code\nvia type safety.\nAny type that appears in your signature must be exported,\nso you get checks from end to end.\nAnd Rust functions that return a `Result` type\nare translated into idiomatic errors and exceptions.\n\nYou can even bridge custom types!\nWe don't have space to go deep into that here,\nbut here's some documentation on [how you can do it](https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002Flatest\u002Ftypes\u002Fcustom_types.html)\nwithout using primitives as a lowest common denominator.\n\nFinally, UniFFI makes it easy to use native code implementations\nof a protocol\u002Finterface with your Rust core!\nThis lets us do things like dependency injection in a type-safe manner\nacross an FFI boundary.\n\n## What are the rough edges?\n\nWhen I gave a talk about this three months ago at the Seoul Rust meetup,\nand I had an entire slide full of challenges.\nAlmost all of them were related to UDL,\nand almost are non-issues when you replace UDL generation with proc macros!\nA few remain though.\n\nFirst, **you'll need to change your habits around struct mutation**.\nSince we are introducing foreign code,\nUniFFI must assume that objects may be mutated from other threads.\nSo, you can't have mutable references to `self` in UniFFI interfaces\nand must rely on interior mutability patterns instead.\nImportantly, your storage needs to be both `Send` and `Sync`,\nwhich means you'll need to turn to things like atomics and mutexes.\n\nUniFFI is also evolving quite rapidly.\nWhile some high-profile crates are fairly stable pre-1.0,\nyou can definitely expect some turbulence with UniFFI.\nThe changelogs are normally quite good,\nbut if you're tracking `main`\nyou can expect breaking changes.\nFortunately, the team are _super_ responsive on GitHub.\nThey're working on it every day, and are extremely responsive as maintainers\n(we submitted a PR to improve some docs around proc macros,\nand it was merged after a round of discussions in less than 24 hours).\n\nThe process of generating bindings for Swift and Kotlin,\nintegrating this into your build tooling,\nand packaging everything into a usable SPM \u002F Maven package\nis also quite complex.\nIn fact, we would even suggest that the best practices around this\nare still being explored.\nThis is a complex topic, and this post is already quite long,\nso we'll save it for the next article.\n\n## Wrap-up\n\nIn summary, while it is still a developing field,\nwe couldn't be more excited about the future of Rust on mobile.\nAs we've shown in this post, a shared core is not just possible;\nit's actually pretty straightforward and the developer experience is great\nthanks to UniFFI.\nWe've put what we've learned into a\n[template repo](https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter)\nso you can try it too, without spending days figuring out boilerplate.\n\nStay tuned for the next post,\nwhere we'll dive deeper into build processes and packaging.\nYou can follow us on social media,\njoin our Slack or Discord communities,\nor subscribe to our mailing list (no spam)\nto get the news first.\n\nFinally,\nif you're excited to bring the latest innovations in routing to more users,\ncheck out Ferrostar [on GitHub](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar).\nWe have an issue tracker and have even marked some good first issues.\n(We'd especially love to have someone who is excited about Jetpack Compose.)\nLet's build the future of mobile navigation together!\n",{"title":875,"description":876,"path":877,"published":878,"keywords":879,"rawbody":884},"Our SwiftUI DSL Is Joining the MapLibre Family","Our SwiftUI DSL makes it easier than ever to build apps with maps. And now it's an official MapLibre project!","\u002Fblog\u002Fmaplibre-swiftui-dsl","2024-10-07",[880,881,852,882,883],"SwiftUI","MapLibre","Maps SDK","DSL","---\ndescription: \"Our SwiftUI DSL makes it easier than ever to build apps with maps. And now it's an official MapLibre project!\"\npublished: 2024-10-07\nkeywords:\n  - SwiftUI\n  - MapLibre\n  - iOS Development\n  - Maps SDK\n  - DSL\nschemaType: TechArticle\nproficiencyLevel: Beginner\n---\n\n# Our SwiftUI DSL Is Joining the MapLibre Family\n\nWe're thrilled to announce [MapLibre SwiftUI DSL](https:\u002F\u002Fgithub.com\u002Fmaplibre\u002Fswiftui-dsl)\nhas graduated from the Stadia Maps labs to become a [MapLibre](https:\u002F\u002Fmaplibre.org\u002F)-hosted project.\nThe project makes MapLibre a first-class citizen in SwiftUI apps,\nand fills an important gap in the iOS developer experience for MapLibre.\n\nThe project began when our co-founder Ian Wagner wanted MapLibre to have a developer experience that rivaled MapKit for SwiftUI.\n[Jacob Fielding](https:\u002F\u002Fgithub.com\u002Farchdoog){ target=\"_blank\" } ([Rallista](https:\u002F\u002Frallista.app\u002F){ target=\"_blank\" }),\n[Patrick Wolowicz](https:\u002F\u002Fsubzero.eu\u002F){ target=\"_blank\" } and [Patrick Kladek](https:\u002F\u002Fgithub.com\u002FPatrick-Kladek){ target=\"_blank\" }\n([HudHud](https:\u002F\u002Fhudhud.sa\u002Fen){ target=\"_blank\" }) shared the vision\nand joined the project, contributing significantly to the development and success of the project.\nWe're grateful for their ongoing contributions,\nand for the support from their respective companies.\n\nBy transitioning to a MapLibre hosted project,\nthe SwiftUI DSL will have more visibility,\nand will unify ongoing initiatives to make MapLibre work better with SwiftUI.\n\n## Modernizing the Map Developer Experience\n\nBefore the SwiftUI DSL, developers had to build their own wrapper views\nto use MapLibre in a SwiftUI app.\nThis was a lot of work—often duplicating what others had already done—and required a deep understanding of MapLibre Native's Objective-C API.\nThe SwiftUI DSL makes this largely a thing of the past.\nWith just a few lines of code, developers can overlay custom layers, bind the camera to reactive state, and set up custom gestures.\n\nFor library authors, the DSL unlocks a whole new level of composability.\nIt's now easy to offer a \"default\" experience that users can customize.\nOur own [Ferrostar navigation SDK](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fferrostar\u002F?utm_source=marketing_site&utm_campaign=swiftui_dsl_maplibre)\nleverages this pattern,\ngiving developers the power to add custom map layers, overlays, and controls\non top of a map during turn-by-turn navigation.\nThis sort of behavior is extremely difficult and error-prone in UIKit,\nbut is a breeze in SwiftUI.\nWe're excited to see more library authors adopting this approach too.\n\n## Learn More & Next Steps\n\n- Check out our [quickstart](https:\u002F\u002Fdocs.stadiamaps.com\u002Fnative-multiplatform\u002Fmaplibre-gl-native\u002F?utm_source=marketing_site&utm_campaign=swiftui_dsl_maplibre#ios){ target=\"_blank\" }\n  to build your first map with SwiftUI.\n- Find the source code on [GitHub](https:\u002F\u002Fgithub.com\u002Fmaplibre\u002Fswiftui-dsl){ target=\"_blank\" } (and give it a star!).\n- Join the `#maplibre-swiftui-compose-playground` channel in the [OpenStreetMap US Slack](https:\u002F\u002Fslack.openstreetmap.us\u002F) to get help or join the development effort.\n- Follow Stadia Maps on [Mastodon](https:\u002F\u002Fen.osm.town\u002F@stadiamaps), [Twitter](https:\u002F\u002Ftwitter.com\u002F@stadiamaps),\n  or [LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fstadia-maps\u002F), or sign-up for\n  our [newsletter](https:\u002F\u002Feepurl.com\u002Fgs51fD) to hear about exciting developments first.\n",{"title":886,"description":887,"path":888,"published":889,"keywords":890,"rawbody":894},"New SDK for Swift is Now Available","Helping iOS developers build modern mapping experiences even faster with Stadia Map SDKs.","\u002Fblog\u002Fnew-sdk-swift","2023-07-20",[891,852,892,893],"Swift SDK","Xcode","Mobile SDK","---\ndescription: \"Helping iOS developers build modern mapping experiences even faster with Stadia Map SDKs.\"\npublished: \"2023-07-20\"\nkeywords:\n  - Swift SDK\n  - iOS Development\n  - Xcode\n  - Mobile SDK\n---\n\n# New SDK for Swift is Now Available\n\nToday we are excited to announce the initial release of our [Swift](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fswift\u002F) SDK.\n\nXcode users will get all the goodies like autocomplete, type definitions, and documentation as you type. All API methods\nleverage the async features introduced in Swift 5.5, which make it easier than ever to write correct asynchronous code.\nAnd since it's generated from official OpenAPI spec, you'll be among the first to get access to our latest features.\n\n## We've Listened and Delivered\n\nWith this latest addition, we now support multiple languages and platform types:\n\n* [JavaScript\u002FTypeScript](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fjavascript-typescript\u002F)\n* [Kotlin](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fkotlin-jvm\u002F)\n* [Python](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fpython\u002F)\n* [Swift](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fswift\u002F)\n\nWe also have a [MapLibre GL JS search box plugin](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@stadiamaps\u002Fmaplibre-search-box).\n\n## Why Use the SDKs?\n\nOf course you could write your code to directly interface with our APIs, but there are several benefits to taking\nadvantage of our SDKs:\n\n* Save time; no need to write your own API code since you have access to the full range of Stadia Maps geospatial APIs\n* Easily install via your favorite package manager\n* Faster access to documentation in your IDE with autocomplete\n* Always utilize the latest features because when we release a new API, we'll release an updated SDK\n\n## We Want Your Feedback\n\nSince this is the initial version of the Swift SDK, we are looking for feedback from the community on updates, features,\nand improvements that we can incorporate into upcoming releases.\n\n## Where to Find\n\nYou can find the Swift SDK, and its source code, on\n[GitHub](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fstadiamaps-api-swift).\n\n## Learn More & Next Steps\n\n- Read documentation about our [SDKs](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Foverview\u002F) and Plugins on our docs site.\n- [Create an account](https:\u002F\u002Fclient.stadiamaps.com\u002Fsignup\u002F?utm_source=marketing_site&utm_medium=blog&utm_campaign=sdk_launch&utm_content=swift_sdk_announcement) to start building today!\n- Join our community on [Slack](https:\u002F\u002Fslack.openstreetmap.us\u002F) or [Discord](https:\u002F\u002Fdiscord.gg\u002FqRBy6qqtdT), follow us\non [Mastodon](https:\u002F\u002Fen.osm.town\u002F@stadiamaps), [Twitter](https:\u002F\u002Ftwitter.com\u002F@stadiamaps), or\n[LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fstadia-maps\u002F), or sign-up for our [mailing list](https:\u002F\u002Feepurl.com\u002Fgs51fD)!\n\n",1778676026876]