SwiftUI ForEach no animation delay - foreach

I recently updated to Xcode 11.3 and now a perviously working animation delay in my ForEach statement has failed. Below is a simplified version of my code.
Thanks
import SwiftUI
struct ContentView: View {
#State var show: Bool = false
var transition: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
let removal = AnyTransition.move(edge: .leading)
return .asymmetric(insertion: insertion, removal: removal)
}
var body: some View {
VStack {
Button(action: { withAnimation { self.show.toggle() } } ) {
Text("start animation")
}
if show == true {
test()
.transition(transition)
}
}
}
}
struct wordArray: Identifiable{
var id = UUID()
var words: String
}
struct test: View{
let circleArray = [
wordArray(words: "This"),
wordArray(words: "Should"),
wordArray(words: "Be"),
wordArray(words: "Delayed"),
]
var body: some View{
VStack{
ForEach(circleArray) { wordArray in
Text("\(wordArray.words)")
.animation(Animation.easeInOut.delay(0.5))
}
Text("like")
.animation(Animation.easeInOut.delay(0.5))
Text("This")
.animation(Animation.easeInOut.delay(1))
}.frame(width: UIScreen.main.bounds.width)
}
}

It may be due to the double VStack. You can change the VStack to Group in testView.
Group{
ForEach(circleArray) { wordArray in
Text("\(wordArray.words)")
.animation(Animation.easeInOut.delay(0.5))
}
Text("like")
.animation(Animation.easeInOut.delay(0.5))
Text("This")
.animation(Animation.easeInOut.delay(1))
}.frame(width: UIScreen.main.bounds.width)

I found a solution to the problems related to, in many cases, views inside ForEach not working with either transitions or animations.
The first solution was to contain the items in a ForEach within a List view.
The other option is to store the group of views in the ForEach, within a ScrollView, which is my preferred options, as a list view comes with a great deal of limitations in what and how you can render it.

Related

Refreshable Indicator is presented on view's appear

Once a view appears refreshable indicator is already visible without the necessity of performing the swipe on list. When I swipe the list up it hides and then pull to refresh can be performed in correct way (it shows, performs actions and hide).
My View:
struct OrdersView: View {
#EnvironmentObject private var tabBarStateManager: TabBarStateManager
#EnvironmentObject private var profileViewModel: ProfileViewModel
#StateObject private var ordersViewModel: OrdersViewModel = OrdersViewModel()
#Environment(\.dismiss) private var dismiss: DismissAction
var body: some View {
List {
ForEach(ordersViewModel.datesForOrdersViewListSections, id: \.self) { stringDate in
Section {
ForEach(ordersViewModel.getOrdersFor(date: stringDate), id: \.self) { order in
NavigationLink(destination: OrderDetailsView(order: order,
orderProductsList: ordersViewModel.getOrderProductsFor(order: order))
.environmentObject(ordersViewModel)) {
VStack(alignment: .leading, spacing: 20) {
HStack(spacing: 10) {
Text(order.id)
.font(.ssCallout)
Spacer()
Text(Date.getDayAndMonthFrom(date: order.orderDate))
.font(.ssTitle3)
.foregroundColor(.accentColor)
}
Text("$\(order.totalCost, specifier: "%.2f")")
.font(.ssTitle3)
.foregroundColor(.accentColor)
VStack(alignment: .leading) {
HStack {
Text(TexterifyManager.localisedString(key: .ordersView(.products)))
.font(.ssCallout)
Text("\(ordersViewModel.getOrderProductsFor(order: order).count)")
.font(.ssTitle3)
.foregroundColor(.accentColor)
}
HStack {
Text(TexterifyManager.localisedString(key: .ordersView(.orderStatus)))
.font(.ssCallout)
Text(order.status.rawValue)
.font(.ssTitle3)
.foregroundColor(.accentColor)
}
}
}
.padding(.vertical)
}
}
} header: {
Text(stringDate)
.font(.ssTitle1)
.foregroundColor(.accentColor)
}
}
}
.listStyle(.grouped)
.refreshable {
profileViewModel.fetchUserOrders {
ordersViewModel.userOrders = profileViewModel.userOrders
}
}
.navigationTitle(TexterifyManager.localisedString(key: .ordersView(.navigationTitle)))
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
dismiss()
} label: {
Image(systemName: "arrow.backward.circle.fill")
.resizable()
.frame(width: 30, height: 30)
.foregroundColor(.accentColor)
}
}
}
.onAppear {
ordersViewModel.userOrders = profileViewModel.userOrders
}
}
}
Changing .refreshable modifier order does not fix the problem
Changing .listStyle also does not help
The problem only occurs on physical device on iPhone 13 Pro Max (iOS 16.1). The same version of simulator does not have the problem.
EDIT:
I discovered that my problem only occurs when .navigationBarTitleDisplayMode is set to .inline instead of default .large
Screenshot:
Here is an example implementation, not exactly yours because you have complex structures that I was not able to reproduce. I hope you get some good from this example:
//
// ContentView.swift
// Refreshable
//
// Created by Allan Garcia on 11/11/22.
//
import SwiftUI
struct ContentView: View {
#State private var isLoading = true
// You may start the view starting with a loading state, before the data was loaded
#State private var myList = [String]() {
didSet {
isLoading = false
}
}
// Once the data is set isLoading (the loading state) is turned off
var body: some View {
NavigationView {
List {
if isLoading {
ProgressView()
.progressViewStyle(.circular)
.font(.largeTitle)
}
ForEach(myList, id: \.self) { item in
Text(item)
}
}
.listStyle(.plain)
.onAppear {
DispatchQueue.main.async {
isLoading = true
withAnimation {
self.myList = ContentView.fetchedList[0..<10].map { $0.uppercased() }
}
}
// On Appear you will probably fetch "some" data with pagination
}
.refreshable {
DispatchQueue.main.async {
withAnimation {
self.myList = ContentView.fetchedList.map { $0.uppercased() }
// A note here... refreshable is good when new data comes on top. If the data comes from bellow is
// a infinite scrolling approach much more advisable.
}
}
// .refreshable works fine here... ir will fetch more data... here it fetchs all, but is probably a
// good practice to fetch "some" more, and let the user read through until he wants to fetch more and
// trigger refreshable again.
}
}
.padding()
}
static let fetchedList = ["a","abandon","ability","able","abortion","about","above","abroad","absence","absolute","absolutely","absorb","abuse","academic","accept","access","accident","accompany","accomplish","according","account","accurate","accuse","achieve","achievement","acid","acknowledge","acquire","across","act","action","active","activist","activity","actor","actress","actual","actually","ad","adapt","add","addition","additional","address","adequate","adjust","adjustment","administration","administrator","admire","admission","admit","adolescent","adopt","adult","advance","advanced","advantage","adventure","advertising","advice","advise","adviser","advocate","affair","affect","afford","afraid","African","African-American","after","afternoon","again","against","age","agency","agenda","agent","aggressive","ago","agree","agreement","agricultural","ah","ahead","aid","aide","AIDS","aim","air","aircraft","airline","airport","album","alcohol","alive","all","alliance","allow","ally","almost","alone","along","already","also","alter","alternative","although","always","AM","amazing","American","among","amount","analysis","analyst","analyze","ancient","and","anger","angle","angry","animal","anniversary","announce","annual","another","answer","anticipate","anxiety","any","anybody","anymore","anyone","anything","anyway","anywhere","apart","apartment","apparent","apparently","appeal","appear","appearance","apple","application","apply","appoint","appointment","appreciate","approach","appropriate","approval","approve","approximately","Arab","architect","area","argue","argument","arise","arm","armed","army","around","arrange","arrangement","arrest","arrival","arrive","art","article","artist","artistic","as","Asian","aside","ask","asleep","aspect","assault","assert","assess","assessment","asset","assign","assignment","assist","assistance","assistant","associate","association","assume","assumption","assure","at","athlete","athletic","atmosphere","attach","attack","attempt","attend","attention","attitude","attorney","attract","attractive","attribute","audience","author","authority","auto","available","average","avoid","award","aware","awareness","away","awful","baby","back","background","bad","badly","bag","bake","balance","ball","ban","band","bank","bar","barely","barrel","barrier","base","baseball","basic","basically","basis","basket","basketball","bathroom","battery","battle","be","beach","bean","bear","beat","beautiful","beauty","because","become","bed","bedroom","beer","before","begin","beginning","behavior","behind","being","belief","believe","bell","belong","below","belt","bench","bend","beneath","benefit","beside","besides","best","bet","better","between","beyond","Bible","big","bike","bill","billion","bind","biological","bird","birth","birthday","bit","bite","black","blade","blame","blanket","blind","block","blood","blow","blue","board","boat","body","bomb","bombing","bond","bone","book","boom","boot","border","born","borrow","boss","both","bother","bottle","bottom","boundary","bowl","box","boy","boyfriend","brain","branch","brand","bread","break","breakfast","breast","breath","breathe","brick","bridge","brief","briefly","bright","brilliant","bring","British","broad","broken","brother","brown","brush","buck","budget","build","building","bullet","bunch","burden","burn","bury","bus","business","busy","but","butter","button","buy","buyer","by","cabin","cabinet","cable","cake","calculate","call","camera","camp","campaign","campus","can","Canadian","cancer","candidate","cap","capability","capable","capacity","capital","captain","capture","car","carbon","card","care","career","careful","carefully","carrier","carry","case","cash","cast","cat","catch","category","Catholic","cause","ceiling","celebrate","celebration","celebrity","cell","center","central","century","CEO","ceremony","certain","certainly","chain","chair","chairman","challenge","chamber","champion","championship","chance","change","changing","channel","chapter","character","characteristic","characterize","charge","charity","chart","chase","cheap","check","cheek","cheese","chef","chemical","chest","chicken","chief","child","childhood","Chinese","chip","chocolate","choice","cholesterol","choose","Christian","Christmas","church","cigarette","circle","circumstance","cite","citizen","city","civil","civilian","claim","class","classic","classroom","clean","clear","clearly","client","climate","climb","clinic","clinical","clock","close","closely","closer","clothes","clothing","cloud","club","clue","cluster","coach","coal","coalition","coast","coat","code","coffee","cognitive","cold","collapse","colleague","collect","collection","collective","college","colonial","color","column","combination","combine","come","comedy","comfort","comfortable","command","commander","comment","commercial","commission","commit","commitment","committee","common","communicate","communication","community","company","compare","comparison","compete","competition","competitive","competitor","complain","complaint","complete","completely","complex","complicated","component","compose","composition","comprehensive","computer","concentrate","concentration","concept","concern","concerned","concert","conclude","conclusion","concrete","condition","conduct","conference","confidence","confident","confirm","conflict","confront","confusion","Congress","congressional","connect","connection","consciousness","consensus","consequence","conservative","consider","considerable","consideration","consist","consistent","constant","constantly","constitute","constitutional","construct","construction","consultant","consume","consumer","consumption","contact","contain","container","contemporary","content","contest","context","continue","continued","contract","contrast","contribute","contribution","control","controversial","controversy","convention","conventional","conversation","convert","conviction","convince","cook","cookie","cooking","cool","cooperation","cop","cope","copy","core","corn","corner","corporate","corporation","correct","correspondent","cost","cotton","couch","could","council","counselor","count","counter","country","county","couple","courage","course","court","cousin","cover","coverage","cow","crack","craft","crash","crazy","cream","create","creation","creative","creature","credit","crew","crime","criminal","crisis","criteria","critic","critical","criticism","criticize","crop","cross","crowd","crucial","cry","cultural","culture","cup","curious","current","currently","curriculum","custom","customer","cut","cycle","dad","daily","damage","dance","danger","dangerous","dare","dark","darkness","data","date","daughter","day","dead","deal","dealer","dear","death","debate","debt","decade","decide","decision","deck","declare","decline","decrease","deep","deeply","deer","defeat","defend","defendant","defense","defensive","deficit","define","definitely","definition","degree","delay","deliver","delivery","demand","democracy","Democrat","democratic","demonstrate","demonstration","deny","department","depend","dependent","depending","depict","depression","depth","deputy","derive","describe","description","desert","deserve","design","designer","desire","desk","desperate","despite","destroy","destruction","detail","detailed","detect","determine","develop","developing","development","device","devote","dialogue","die","diet","differ","difference","different","differently","difficult","difficulty","dig","digital","dimension","dining","dinner","direct","direction","directly","director","dirt","dirty","disability","disagree","disappear","disaster","discipline","discourse","discover","discovery","discrimination","discuss","discussion","disease","dish","dismiss","disorder","display","dispute","distance","distant","distinct","distinction","distinguish","distribute","distribution","district","diverse","diversity","divide","division","divorce","DNA","do","doctor","document","dog","domestic","dominant","dominate","door","double","doubt","down","downtown","dozen","draft","drag","drama","dramatic","dramatically","draw","drawing","dream","dress","drink","drive","driver","drop","drug","dry","due","during","dust","duty","each","eager","ear","early","earn","earnings","earth","ease","easily","east","eastern","easy","eat","economic","economics","economist","economy","edge","edition","editor","educate","education","educational","educator","effect","effective","effectively","efficiency","efficient","effort","egg","eight","either","elderly","elect","election","electric","electricity","electronic","element","elementary","eliminate","elite","else","elsewhere","e-mail","embrace","emerge","emergency","emission","emotion","emotional","emphasis","emphasize","employ","employee","employer","employment","empty","enable","encounter","encourage","end","enemy","energy","enforcement","engage","engine","engineer","engineering","English","enhance","enjoy","enormous","enough","ensure","enter","enterprise","entertainment","entire","entirely","entrance","entry","environment","environmental","episode","equal","equally","equipment","era","error","escape","especially","essay","essential","essentially","establish","establishment","estate","estimate","etc","ethics","ethnic","European","evaluate","evaluation","even","evening","event","eventually","ever","every","everybody","everyday","everyone","everything","everywhere","evidence","evolution","evolve","exact","exactly","examination","examine","example","exceed","excellent","except","exception","exchange","exciting","executive","exercise","exhibit","exhibition","exist","existence","existing","expand","expansion","expect","expectation","expense","expensive","experience","experiment","expert","explain","explanation","explode","explore","explosion","expose","exposure","express","expression","extend","extension","extensive","extent","external","extra","extraordinary","extreme","extremely","eye","fabric","face","facility","fact","factor","factory","faculty","fade","fail","failure","fair","fairly","faith","fall","false","familiar","family","famous","fan","fantasy","far","farm","farmer","fashion","fast","fat","fate","father","fault","favor","favorite","fear","feature","federal","fee","feed","feel","feeling","fellow","female","fence","few","fewer","fiber","fiction","field","fifteen","fifth","fifty","fight","fighter","fighting","figure","file","fill","film","final","finally","finance","financial","find","finding","fine","finger","finish","fire","firm","first","fish","fishing","fit","fitness","five","fix","flag","flame","flat","flavor","flee","flesh","flight","float","floor","flow","flower","fly","focus","folk","follow","following","food","foot","football","for","force","foreign","forest","forever","forget","form","formal","formation","former","formula","forth","fortune","forward","found","foundation","founder","four","fourth","frame","framework","free","freedom","freeze","French","frequency","frequent","frequently","fresh","friend","friendly","friendship","from","front","fruit","frustration","fuel","full","fully","fun","function","fund","fundamental","funding","funeral","funny","furniture","furthermore","future","gain","galaxy","gallery","game","gang","gap","garage","garden","garlic","gas","gate","gather","gay","gaze","gear","gender","gene","general","generally","generate","generation","genetic","gentleman","gently","German","gesture","get","ghost","giant","gift","gifted","girl","girlfriend","give","given","glad","glance","glass","global","glove","go","goal","God","gold","golden","golf","good","government","governor","grab","grade","gradually","graduate","grain","grand","grandfather","grandmother","grant","grass","grave","gray","great","greatest","green","grocery","ground","group","grow","growing","growth","guarantee","guard","guess","guest","guide","guideline","guilty","gun","guy","habit","habitat","hair","half","hall","hand","handful","handle","hang","happen","happy","hard","hardly","hat","hate","have","he","head","headline","headquarters","health","healthy","hear","hearing","heart","heat","heaven","heavily","heavy","heel","height","helicopter","hell","hello","help","helpful","her","here","heritage","hero","herself","hey","hi","hide","high","highlight","highly","highway","hill","him","himself","hip","hire","his","historian","historic","historical","history","hit","hold","hole","holiday","holy","home","homeless","honest","honey","honor","hope","horizon","horror","horse","hospital","host","hot","hotel","hour","house","household","housing","how","however","huge","human","humor","hundred","hungry","hunter","hunting","hurt","husband","hypothesis","I","ice","idea","ideal","identification","identify","identity","ie","if","ignore","ill","illegal","illness","illustrate","image","imagination","imagine","immediate","immediately","immigrant","immigration","impact","implement","implication","imply","importance","important","impose","impossible","impress","impression","impressive","improve","improvement","in","incentive","incident","include","including","income","incorporate","increase","increased","increasing","increasingly","incredible","indeed","independence","independent","index","Indian","indicate","indication","individual","industrial","industry","infant","infection","inflation","influence","inform","information","ingredient","initial","initially","initiative","injury","inner","innocent","inquiry","inside","insight","insist","inspire","install","instance","instead","institution","institutional","instruction","instructor","instrument","insurance","intellectual","intelligence","intend","intense","intensity","intention","interaction","interest","interested","interesting","internal","international","Internet","interpret","interpretation","intervention","interview","into","introduce","introduction","invasion","invest","investigate","investigation","investigator","investment","investor","invite","involve","involved","involvement","Iraqi","Irish","iron","Islamic","island","Israeli","issue","it","Italian","item","its","itself","jacket","jail","Japanese","jet","Jew","Jewish","job","join","joint","joke","journal","journalist","journey","joy","judge","judgment","juice","jump","junior","jury","just","justice","justify","keep","key","kick","kid","kill","killer","killing","kind","king","kiss","kitchen","knee","knife","knock","know","knowledge","lab","label","labor","laboratory","lack","lady","lake","land","landscape","language","lap","large","largely","last","late","later","Latin","latter","laugh","launch","law","lawn","lawsuit","lawyer","lay","layer","lead","leader","leadership","leading","leaf","league","lean","learn","learning","least","leather","leave","left","leg","legacy","legal","legend","legislation","legitimate","lemon","length","less","lesson","let","letter","level","liberal","library","license","lie","life","lifestyle","lifetime","lift","light","like","likely","limit","limitation","limited","line","link","lip","list","listen","literally","literary","literature","little","live","living","load","loan","local","locate","location","lock","long","long-term","look","loose","lose","loss","lost","lot","lots","loud","love","lovely","lover","low","lower","luck","lucky","lunch","lung","machine","mad","magazine","mail","main","mainly","maintain","maintenance","major","majority","make","maker","makeup","male","mall","man","manage","management","manager","manner","manufacturer","manufacturing","many","map","margin","mark","market","marketing","marriage","married","marry","mask","mass","massive","master","match","material","math","matter","may","maybe","mayor","me","meal","mean","meaning","meanwhile","measure","measurement","meat","mechanism","media","medical","medication","medicine","medium","meet","meeting","member","membership","memory","mental","mention","menu","mere","merely","mess","message","metal","meter","method","Mexican","middle","might","military","milk","million","mind","mine","minister","minor","minority","minute","miracle","mirror","miss","missile","mission","mistake","mix","mixture","mm-hmm","mode","model","moderate","modern","modest","mom","moment","money","monitor","month","mood","moon","moral","more","moreover","morning","mortgage","most","mostly","mother","motion","motivation","motor","mount","mountain","mouse","mouth","move","movement","movie","Mr","Mrs","Ms","much","multiple","murder","muscle","museum","music","musical","musician","Muslim","must","mutual","my","myself","mystery","myth","naked","name","narrative","narrow","nation","national","native","natural","naturally","nature","near","nearby","nearly","necessarily","necessary","neck","need","negative","negotiate","negotiation","neighbor","neighborhood","neither","nerve","nervous","net","network","never","nevertheless","new","newly","news","newspaper","next","nice","night","nine","no","nobody","nod","noise","nomination","none","nonetheless","nor","normal","normally","north","northern","nose","not","note","nothing","notice","notion","novel","now","nowhere","n't","nuclear","number","numerous","nurse","nut","object","objective","obligation","observation","observe","observer","obtain","obvious","obviously","occasion","occasionally","occupation","occupy","occur","ocean","odd","odds","of","off","offense","offensive","offer","office","officer","official","often","oh","oil","ok","okay","old","Olympic","on","once","one","ongoing","onion","online","only","onto","open","opening","operate","operating","operation","operator","opinion","opponent","opportunity","oppose","opposite","opposition","option","or","orange","order","ordinary","organic","organization","organize","orientation","origin","original","originally","other","others","otherwise","ought","our","ourselves","out","outcome","outside","oven","over","overall","overcome","overlook","owe","own","owner","pace","pack","package","page","pain","painful","paint","painter","painting","pair","pale","Palestinian","palm","pan","panel","pant","paper","parent","park","parking","part","participant","participate","participation","particular","particularly","partly","partner","partnership","party","pass","passage","passenger","passion","past","patch","path","patient","pattern","pause","pay","payment","PC","peace","peak","peer","penalty","people","pepper","per","perceive","percentage","perception","perfect","perfectly","perform","performance","perhaps","period","permanent","permission","permit","person","personal","personality","personally","personnel","perspective","persuade","pet","phase","phenomenon","philosophy","phone","photo","photograph","photographer","phrase","physical","physically","physician","piano","pick","picture","pie","piece","pile","pilot","pine","pink","pipe","pitch","place","plan","plane","planet","planning","plant","plastic","plate","platform","play","player","please","pleasure","plenty","plot","plus","PM","pocket","poem","poet","poetry","point","pole","police","policy","political","politically","politician","politics","poll","pollution","pool","poor","pop","popular","population","porch","port","portion","portrait","portray","pose","position","positive","possess","possibility","possible","possibly","post","pot","potato","potential","potentially","pound","pour","poverty","powder","power","powerful","practical","practice","pray","prayer","precisely","predict","prefer","preference","pregnancy","pregnant","preparation","prepare","prescription","presence","present","presentation","preserve","president","presidential","press","pressure","pretend","pretty","prevent","previous","previously","price","pride","priest","primarily","primary","prime","principal","principle","print","prior","priority","prison","prisoner","privacy","private","probably","problem","procedure","proceed","process","produce","producer","product","production","profession","professional","professor","profile","profit","program","progress","project","prominent","promise","promote","prompt","proof","proper","properly","property","proportion","proposal","propose","proposed","prosecutor","prospect","protect","protection","protein","protest","proud","prove","provide","provider","province","provision","psychological","psychologist","psychology","public","publication","publicly","publish","publisher","pull","punishment","purchase","pure","purpose","pursue","push","put","qualify","quality","quarter","quarterback","question","quick","quickly","quiet","quietly","quit","quite","quote","race","racial","radical","radio","rail","rain","raise","range","rank","rapid","rapidly","rare","rarely","rate","rather","rating","ratio","raw","reach","react","reaction","read","reader","reading","ready","real","reality","realize","really","reason","reasonable","recall","receive","recent","recently","recipe","recognition","recognize","recommend","recommendation","record","recording","recover","recovery","recruit","red","reduce","reduction","refer","reference","reflect","reflection","reform","refugee","refuse","regard","regarding","regardless","regime","region","regional","register","regular","regularly","regulate","regulation","reinforce","reject","relate","relation","relationship","relative","relatively","relax","release","relevant","relief","religion","religious","rely","remain","remaining","remarkable","remember","remind","remote","remove","repeat","repeatedly","replace","reply","report","reporter","represent","representation","representative","Republican","reputation","request","require","requirement","research","researcher","resemble","reservation","resident","resist","resistance","resolution","resolve","resort","resource","respect","respond","respondent","response","responsibility","responsible","rest","restaurant","restore","restriction","result","retain","retire","retirement","return","reveal","revenue","review","revolution","rhythm","rice","rich","rid","ride","rifle","right","ring","rise","risk","river","road","rock","role","roll","romantic","roof","room","root","rope","rose","rough","roughly","round","route","routine","row","rub","rule","run","running","rural","rush","Russian","sacred","sad","safe","safety","sake","salad","salary","sale","sales","salt","same","sample","sanction","sand","satellite","satisfaction","satisfy","sauce","save","saving","say","scale","scandal","scared","scenario","scene","schedule","scheme","scholar","scholarship","school","science","scientific","scientist","scope","score","scream","screen","script","sea","search","season","seat","second","secret","secretary","section","sector","secure","security","see","seed","seek","seem","segment","seize","select","selection","self","sell","Senate","senator","send","senior","sense","sensitive","sentence","separate","sequence","series","serious","seriously","serve","service","session","set","setting","settle","settlement","seven","several","severe","sex","sexual","shade","shadow","shake","shall","shape","share","sharp","she","sheet","shelf","shell","shelter","shift","shine","ship","shirt","shit","shock","shoe","shoot","shooting","shop","shopping","shore","short","shortly","shot","should","shoulder","shout","show","shower","shrug","shut","sick","side","sigh","sight","sign","signal","significance","significant","significantly","silence","silent","silver","similar","similarly","simple","simply","sin","since","sing","singer","single","sink","sir","sister","sit","site","situation","six","size","ski","skill","skin","sky","slave","sleep","slice","slide","slight","slightly","slip","slow","slowly","small","smart","smell","smile","smoke","smooth","snap","snow","so","so-called","soccer","social","society","soft","software","soil","solar","soldier","solid","solution","solve","some","somebody","somehow","someone","something","sometimes","somewhat","somewhere","son","song","soon","sophisticated","sorry","sort","soul","sound","soup","source","south","southern","Soviet","space","Spanish","speak","speaker","special","specialist","species","specific","specifically","speech","speed","spend","spending","spin","spirit","spiritual","split","spokesman","sport","spot","spread","spring","square","squeeze","stability","stable","staff","stage","stair","stake","stand","standard","standing","star","stare","start","state","statement","station","statistics","status","stay","steady","steal","steel","step","stick","still","stir","stock","stomach","stone","stop","storage","store","storm","story","straight","strange","stranger","strategic","strategy","stream","street","strength","strengthen","stress","stretch","strike","string","strip","stroke","strong","strongly","structure","struggle","student","studio","study","stuff","stupid","style","subject","submit","subsequent","substance","substantial","succeed","success","successful","successfully","such","sudden","suddenly","sue","suffer","sufficient","sugar","suggest","suggestion","suicide","suit","summer","summit","sun","super","supply","support","supporter","suppose","supposed","Supreme","sure","surely","surface","surgery","surprise","surprised","surprising","surprisingly","surround","survey","survival","survive","survivor","suspect","sustain","swear","sweep","sweet","swim","swing","switch","symbol","symptom","system","table","tablespoon","tactic","tail","take","tale","talent","talk","tall","tank","tap","tape","target","task","taste","tax","taxpayer","tea","teach","teacher","teaching","team","tear","teaspoon","technical","technique","technology","teen","teenager","telephone","telescope","television","tell","temperature","temporary","ten","tend","tendency","tennis","tension","tent","term","terms","terrible","territory","terror","terrorism","terrorist","test","testify","testimony","testing","text","than","thank","thanks","that","the","theater","their","them","theme","themselves","then","theory","therapy","there","therefore","these","they","thick","thin","thing","think","thinking","third","thirty","this","those","though","thought","thousand","threat","threaten","three","throat","through","throughout","throw","thus","ticket","tie","tight","time","tiny","tip","tire","tired","tissue","title","to","tobacco","today","toe","together","tomato","tomorrow","tone","tongue","tonight","too","tool","tooth","top","topic"]
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Why don't #State parameter changes cause a view update?

I am trying to follow the guidance in a WWDC video to use a #State struct to configure and present a child view. I would expect the struct to be able to present the view, however the config.show boolean value does not get updated when set by the button.
The code below has two buttons, each toggling a different boolean to show an overlay. Toggling showWelcome shows the overlay but toggling config.show does nothing. This seems to be working as intended, I just don't understand why SwiftUI behaves this way. Can someone explain why it's not functioning like I expect, and/or suggest a workaround?
https://developer.apple.com/videos/play/wwdc2020/10040/ # 5:14
struct InformationOverlayConfig {
#State var show = false
#State var title: String?
}
struct InformationOverlay: View {
#Binding var config: InformationOverlayConfig
var body: some View {
if config.title != nil {
Text(config.title!)
.padding()
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 15))
}
}
}
struct TestView: View {
#State private var configWelcome = InformationOverlayConfig(title: "Title is here")
#State private var showWelcome = false
var body: some View {
VStack {
Text("hello world")
Spacer()
Button("Toggle struct parameter", action: {
configWelcome.show.toggle()
})
Button("Toggle boolean state", action: {
showWelcome.toggle()
})
}
.overlay(
VStack {
InformationOverlay(config: $configWelcome).opacity(configWelcome.show ? 1 : 0)
InformationOverlay(config: $configWelcome).opacity(showWelcome ? 1 : 0)
})
}
You "Config" is not a View. State variables only go in Views.
Also, do not use force unwrapping for config.title. Optional binding or map are the non-redundant solutions.
Lastly, there is no need for config to be a Binding if it actually functions as a constant for a particular view.
struct InformationOverlay: View {
struct Config {
var show = false
var title: String?
}
let config: Config
var body: some View {
VStack {
if let title = config.title {
Text(title)
.padding()
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 15))
}
// or
config.title.map {
Text($0)
.padding()
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 15))
}
}
}
}
struct TestView: View {
#State private var configWelcome = InformationOverlay.Config(title: "Title is here")
#State private var showWelcome = false
var body: some View {
VStack {
Text("hello world")
Spacer()
Button("Toggle struct parameter") {
configWelcome.show.toggle()
}
Button("Toggle boolean state") {
showWelcome.toggle()
}
}
.overlay(
VStack {
InformationOverlay(config: configWelcome).opacity(configWelcome.show ? 1 : 0)
InformationOverlay(config: configWelcome).opacity(showWelcome ? 1 : 0)
}
)
}
}

Why does this SwiftUI List require an extra objectWillChange.send?

Here is a simple list view of "Topic" struct items. The goal is to present an editor view when a row of the list is tapped. In this code, tapping a row is expected to cause the selected topic to be stored as "tappedTopic" in an #State var and sets a Boolean #State var that causes the EditorV to be presented.
When the code as shown is run and a line is tapped, its topic name prints properly in the Print statement in the Button action, but then the app crashes because self.tappedTopic! finds tappedTopic to be nil in the EditTopicV(...) line.
If the line "tlVM.objectWillChange.send()" is uncommented, the code runs fine. Why is this needed?
And a second puzzle: in the case where the code runs fine, with the objectWillChange.send() uncommented, a print statement in the EditTopicV init() shows that it runs twice. Why?
Any help would be greatly appreciated. I am using Xcode 13.2.1 and my deployment target is set to iOS 15.1.
Topic.swift:
struct Topic: Identifiable {
var name: String = "Default"
var iconName: String = "circle"
var id: String { name }
}
TopicListV.swift:
struct TopicListV: View {
#ObservedObject var tlVM: TopicListVM
#State var tappedTopic: Topic? = nil
#State var doEditTappedTopic = false
var body: some View {
VStack(alignment: .leading) {
List {
ForEach(tlVM.topics) { topic in
Button(action: {
tappedTopic = topic
// why is the following line needed?
tlVM.objectWillChange.send()
doEditTappedTopic = true
print("Tapped topic = \(tappedTopic!.name)")
}) {
Label(topic.name, systemImage: topic.iconName)
.padding(10)
}
}
}
Spacer()
}
.sheet(isPresented: $doEditTappedTopic) {
EditTopicV(tlVM: tlVM, originalTopic: self.tappedTopic!)
}
}
}
EditTopicV.swift (Editor View):
struct EditTopicV: View {
#ObservedObject var tlVM: TopicListVM
#Environment(\.presentationMode) var presentationMode
let originalTopic: Topic
#State private var editTopic: Topic
#State private var ic = "circle"
let iconList = ["circle", "leaf", "photo"]
init(tlVM: TopicListVM, originalTopic: Topic) {
print("DBG: EditTopicV: originalTopic = \(originalTopic)")
self.tlVM = tlVM
self.originalTopic = originalTopic
self._editTopic = .init(initialValue: originalTopic)
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Button("Cancel") {
presentationMode.wrappedValue.dismiss()
}
Spacer()
Button("Save") {
editTopic.iconName = editTopic.iconName.lowercased()
tlVM.change(topic: originalTopic, to: editTopic)
presentationMode.wrappedValue.dismiss()
}
}
HStack {
Text("Name:")
TextField("name", text: $editTopic.name)
Spacer()
}
Picker("Color Theme", selection: $editTopic.iconName) {
ForEach(iconList, id: \.self) { icon in
Text(icon).tag(icon)
}
}
.pickerStyle(.segmented)
Spacer()
}
.padding()
}
}
TopicListVM.swift (Observable Object View Model):
class TopicListVM: ObservableObject {
#Published var topics = [Topic]()
func append(topic: Topic) {
topics.append(topic)
}
func change(topic: Topic, to newTopic: Topic) {
if let index = topics.firstIndex(where: { $0.name == topic.name }) {
topics[index] = newTopic
}
}
static func ex1() -> TopicListVM {
let tvm = TopicListVM()
tvm.append(topic: Topic(name: "leaves", iconName: "leaf"))
tvm.append(topic: Topic(name: "photos", iconName: "photo"))
tvm.append(topic: Topic(name: "shapes", iconName: "circle"))
return tvm
}
}
Here's what the list looks like:
Using sheet(isPresented:) has the tendency to cause issues like this because SwiftUI calculates the destination view in a sequence that doesn't always seem to make sense. In your case, using objectWillSend on the view model, even though it shouldn't have any effect, seems to delay the calculation of your force-unwrapped variable and avoids the crash.
To solve this, use the sheet(item:) form:
.sheet(item: $tappedTopic) { item in
EditTopicV(tlVM: tlVM, originalTopic: item)
}
Then, your item gets passed in the closure safely and there's no reason for a force unwrap.
You can also capture tappedTopic for a similar result, but you still have to force unwrap it, which is generally something we want to avoid:
.sheet(isPresented: $doEditTappedTopic) { [tappedTopic] in
EditTopicV(tlVM: tlVM, originalTopic: tappedTopic!)
}

SwiftUI: prevent View from refreshing when presenting a sheet

I have noticed that SwiftUI completely refresh view when adding sheetmodifier.
Let's say I have View that displays random number. I expect that this value would be independent and not connected to the sheet logic (not changing every time I open/close sheet), but every time sheet presented/dismissed Text is changing.
Is it supposed to work so?
Am I wrong that main point of #Sateis to update only connected Views but not all stack?
How can I prevent my View from refreshing itself when presenting a modal?
struct ContentView: View {
#State var active = false
var body: some View {
VStack {
Text("Random text: \(Int.random(in: 0...100))")
Button(action: { self.active.toggle() }) {
Text("Show pop up")
}
}
.sheet(isPresented: $active) {
Text("POP UP")
}
}
}
P.S. ContentView calls onAppear()/onDisappear() and init() only ones.
It needs to make separated condition-independent view to achieve behavior as you wish, like below
struct RandomView: View {
var body: some View {
Text("Random text: \(Int.random(in: 0...100))")
}
}
struct ContentView: View {
#State var active = false
var body: some View {
VStack {
RandomView()
Button(action: { self.active.toggle() }) {
Text("Show pop up")
}
}
.sheet(isPresented: $active) {
Text("POP UP")
}
}
}
In this case RandomView is not rebuilt because is not dependent on active state.
Asperi sad :
View is struct, value type, if any part of it changed then entire
value changed
He is absolutely right! But for that we have state properties. When the view is recreated, the value of state doesn't change.
This should work, as you expected
struct ContentView: View {
#State var active = false
#State var number = Int.random(in: 0 ... 100)
var body: some View {
VStack {
Text("Random text: \(number)")
Button(action: { self.active.toggle() }) {
Text("Show pop up")
}
}
.sheet(isPresented: $active) {
Text("POP UP")
}
}
}
What is the advantage? For simple things, the state / binding is the best solution, without any doubt.
import SwiftUI
struct SheetView: View {
#Binding var randomnumber: Int
var body: some View {
Button(action: {
self.randomnumber = Int.random(in: 0 ... 100)
}) {
Text("Generate new random number")
}
}
}
struct ContentView: View {
#State var active = false
#State var number = Int.random(in: 0 ... 100)
var body: some View {
VStack {
Text("Random text: \(number)")
Button(action: { self.active.toggle() }) {
Text("Show pop up")
}
}
.sheet(isPresented: $active) {
SheetView(randomnumber: self.$number)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Now you can dismiss the sheet with or without generating new random number. No external model is required ...

Do something when Toggle state changes

I am using SwiftUI and want to do something when toggle state changes but I cannot find a solution for this.
How can I call a function or do whatever when toggle state changes ?
#State var condition = false
Toggle(isOn: $condition) {
Text("Toggle text here")
}
If you want to do something whenever the toggle state change, you may use didSet.
struct ContentView: View {
#State var condition = false{
didSet{
print("condition changed to \(condition)")
}
}
var body: some View {
let bind = Binding<Bool>(
get:{self.condition},
set:{self.condition = $0}
)
return Toggle(isOn: bind){
Text("Toggle text here")
}
}
}
When we say #State var condition = true, it's type is State<Bool> and not Bool.
when we use binding in toggle ($condition in this case), toggle directly changes the stored value. Meaning it never changes the #State, so didSet will never be triggered.
So here we create and assign to let bind an instance of Binding<Value> struct, and use that Binding struct directly in toggle. Than when we set self.condition = $0, it will trigger didSet.
You can use the .onChange modifier like in the first example below.
However, keep in mind that in most cases we don't need it.
In case of a view setting, you bind to a #State bool value, then you use this bool to modify the view hierarchy, as in the first example.
In case of a change in your model, you bind the toggle to a #Published value in your model or model controller. In this case, the model or model controller receive the change from the switch, do whatever is requested, and the view is rerendered. It must have an #ObservedObject reference to your model.
I show how to use the .onChange modifier in the first example. It simply logs the state.
Remember there should be as few logic as possible in SwiftUI views.
By the way that was already true far before the invention of SwiftUI ;)
Example 1 - #State value. ( Local UI settings )
import SwiftUI
struct ToggleDetailView: View {
#State var isDetailVisible: Bool = true
var body: some View {
VStack {
Toggle("Display Detail", isOn: $isDetailVisible)
.onChange(of: self.isDetailVisible, perform: { value in
print("Value has changed : \(value)")
})
if isDetailVisible {
Text("Detail is visible!")
}
}
.padding(10)
.frame(width: 300, height: 150, alignment: .top)
}
}
struct ToggleDetailView_Previews: PreviewProvider {
static var previews: some View {
ToggleDetailView()
}
}
Example 2 - #ObservedObject value. ( Action on model )
import SwiftUI
class MyModel: ObservableObject {
#Published var isSolid: Bool = false
var string: String {
isSolid ? "This is solid" : "This is fragile"
}
}
struct ToggleModelView: View {
#ObservedObject var model: MyModel
var body: some View {
VStack {
Toggle("Solid Model", isOn: $model.isSolid)
Text(model.string)
}
.padding(10)
.frame(width: 300, height: 150, alignment: .top)
}
}
struct ToggleModelView_Previews: PreviewProvider {
static var previews: some View {
ToggleModelView(model: MyModel())
}
}
Try
struct ContentView: View {
#State var condition = false
var body: some View {
if condition {
callMe()
}
else {
print("off")
}
return Toggle(isOn: $condition) {
Text("Toggle text here")
}
}
func callMe() {
}
}
Karen please check
#State var isChecked: Bool = true
#State var index: Int = 0
Toggle(isOn: self.$isChecked) {
Text("This is a Switch")
if (self.isChecked) {
Text("\(self.toggleAction(state: "Checked", index: index))")
} else {
CustomAlertView()
Text("\(self.toggleAction(state: "Unchecked", index: index))")
}
}
Add function like this
func toggleAction(state: String, index: Int) -> String {
print("The switch no. \(index) is \(state)")
return ""
}
I tend to use:
Toggle(isOn: $condition, label: {
Text("Power: \(condition ? "On" : "Off")")
})
.padding(.horizontal, 10.0)
.frame(width: 225, height: 50, alignment: .center)

Resources