Usage in UIKit
9 min
1\ define test users appstorys allows you to capture screens and elements of your app using the "test user" functionality 2\ adding test users you can add test users directly from your appstorys dashboard you need to define a "name" and "userid" for this test user the userid must match the one set in your app for that user 2 a how it works once added the test users will see a "capture screen" button on their devices within the app to help appstorys identify and track specific ui elements within your uikit screens, you can assign a custom identifier to each element use the appstorystag property to tag components you want appstorys to recognize and measure interactions with tapping this button will send the complete screen data — including layout elements, text, buttons, and structural hierarchy — to the appstorys dashboard the screens and elements then become available within your dashboard for campaign targeting and configuration like tooltips, spotlight, coachmarks and more override func viewdidappear() { super viewdidload() yourelement appstorystag = "your element name" } appstorys ships uikit native containers for inline content (storiescontainerview, widgetcontainerview) and a scene attached overlay window for everything else (banner, floater, csat, pip, modal, bottom sheet, tooltips, spotlight, coachmarks) you do not need to wrap any swiftui view in a uihostingcontroller yourself — the sdk handles that internally, including gesture routing, sizing, and overlay window lifecycle if you haven't already, complete initialize the sdk → uikit and track screens → uikit first the sections below assume that setup is in place a widgets drop appstorys widgetcontainerview into your view hierarchy at the position you want the widget to render the container auto sizes via intrinsiccontentsize and collapses to 0pt when the sdk has nothing to show for the current screen let widget = appstorys widgetcontainerview() // default position // or with explicit position let widget = appstorys widgetcontainerview(position "second widget") widget translatesautoresizingmaskintoconstraints = false view\ addsubview(widget) nslayoutconstraint activate(\[ widget topanchor constraint(equalto someanchor), widget leadinganchor constraint(equalto view\ leadinganchor), widget trailinganchor constraint(equalto view\ trailinganchor), ]) widget appstoryswidgettag = "second widget" // or fluent widget appstoryswidgettag("second widget") b overlays to call banner floater csat pip modal bottom sheet tooltips spotlight coachmarks attach the overlay window in scenedelegate scene( willconnectto\ options ) // scenedelegate swift import appstorys ios func scene( scene uiscene, willconnectto session uiscenesession, options connectionoptions uiscene connectionoptions) { // your normal window setup appstorys attachoverlays(to scene) } no per screen setup these campaigns render in the appstorys overlay window attached once from scenedelegate the overlay window appears automatically on whichever screens the dashboard has them to opt out of specific overlay types, pass a config to attachoverlays var config = appstorysoverlayconfig() config showpip = false config showcapture = false appstorys attachoverlays(to scene, config config) c stories let stories = appstorys storiescontainerview() stories translatesautoresizingmaskintoconstraints = false view\ addsubview(stories) nslayoutconstraint activate(\[ stories topanchor constraint(equalto view\ safearealayoutguide topanchor), stories leadinganchor constraint(equalto view\ leadinganchor), stories trailinganchor constraint(equalto view\ trailinganchor), ]) tapping a thumbnail opens the full screen story viewer in the overlay window — make sure you've called attachoverlays(to ) or the tap will appear to do nothing d embedding inside uitableview\ tableheaderview uitableview\ tableheaderview is frame locked once set — it does not auto re measure when a child's intrinsic content size grows later since storiescontainerview and widgetcontainerview are 0pt tall until the sdk fetches their content, you'll see a hero only header until you wire oncontentchanged to re fit let stories = appstorys storiescontainerview() ▎stories oncontentchanged = { \[weak self] in self? sizetableheadertofit() } let headerstack = uistackview(arrangedsubviews \[heroview, stories, widget]) headerstack axis = vertical headerstack spacing = 8 tableview\ tableheaderview = headerstack sizetableheadertofit() // helper — re measures the table header to fit its content private func sizetableheadertofit() { guard let header = tableview\ tableheaderview else { return } let w = tableview\ bounds width > 0 ? tableview\ bounds width view\ bounds width header setneedslayout(); header layoutifneeded() let fitted = header systemlayoutsizefitting( cgsize(width w, height 0), withhorizontalfittingpriority required, verticalfittingpriority fittingsizelevel ) if header frame height != fitted height { header frame = cgrect(x 0, y 0, width w, height fitted height) tableview\ tableheaderview = header } } override func viewdidlayoutsubviews() { super viewdidlayoutsubviews() sizetableheadertofit() } to hide the row entirely when the sdk returns nothing, also flip ishidden based on hascontent stories oncontentchanged = { \[weak self] in self? stories ishidden = !(self? stories hascontent ?? false) self? sizetableheadertofit() }