I am new to swift. I am trying to achieve the requirement is when the spacific view record is selected I am want to move into details view with selected record including image. But the problems is the text property is display as expected not image failed.
so far I have tried Image(people.avatar ?? "")
but actually it not working. even I can not see it into preview as well.
Here is the content view code ..
import SwiftUI
struct PeopleListView: View {
@StateObject var viewModel: PeopleListViewModel
var body: some View {
NavigationStack {
VStack {
if viewModel.customError != nil && !viewModel.refreshing {
alertView()
} else {
if viewModel.refreshing {
progressView()
}
if viewModel.peopleLists.count > 0 && !viewModel.refreshing {
List(viewModel.peopleLists, id: \.self) { people in
NavigationLink(destination: PeopleDetailsView(people: people)) {
PeopleCellView(people: people)
}
}
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
getToolBarView()
}
}
.navigationTitle(Text("Employees List"))
}.task{
await getDataFromAPI()
}
.refreshable {
await getDataFromAPI()
}
}
func getDataFromAPI() async {
await viewModel.getPeopleList(urlStr: NetworkURL.peopleUrl)
}
@ViewBuilder
func getToolBarView() -> some View {
Button {
Task{
await getDataFromAPI()
}
} label: {
HStack {
Image(systemName: "arrow.clockwise")
.padding(.all, 10.0)
}.fixedSize()
}
.cornerRadius(5.0)
}
@ViewBuilder
func progressView() -> some View {
VStack{
RoundedRectangle(cornerRadius: 15)
.fill(.white)
.frame(height: 180)
.overlay{
VStack{
ProgressView().padding(50)
Text("Please Wait Message").font(.headline)
}
}
}
}
@ViewBuilder
func alertView() -> some View {
Text("").alert(isPresented: $viewModel.isErrorOccured) {
Alert(title: Text("General_Error"), message: Text(viewModel.customError?.localizedDescription ?? ""),dismissButton: .default(Text("Okay")))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
PeopleListView(viewModel: PeopleListViewModel(repository: PeopleRepositoryImplementation(networkManager: NetworkManager())))
}
}
Here is code for cashed ..
import SwiftUI
struct CacheAsyncImage<Content>: View where Content: View {
private let url: URL
private let scale: CGFloat
private let transaction: Transaction
private let content: (AsyncImagePhase) -> Content
init(url: URL,
scale: CGFloat = 1.0,
transaction: Transaction = Transaction(),
@ViewBuilder content: @escaping (AsyncImagePhase) -> Content) {
self.url = url
self.scale = scale
self.transaction = transaction
self.content = content
}
var body: some View {
if let cached = ImageCache[url] {
content(.success(cached))
} else {
AsyncImage(url: url,
scale: scale,
transaction: transaction) { phase in
cacheAndRender(phase: phase)
}
}
}
func cacheAndRender(phase: AsyncImagePhase) -> some View {
if case .success(let image) = phase {
ImageCache[url] = image
}
return content(phase)
}
}
fileprivate class ImageCache {
static private var cache: [URL: Image] = [:]
static subscript(url: URL) -> Image? {
get {
ImageCache.cache[url]
}
set {
ImageCache.cache[url] = newValue
}
}
}
Here is code for async image cashed ..
import SwiftUI
struct PeopleAsyncImageView: View {
let url: URL
private let imageWidth = 150.0
private let cellHeight = 150.0
var body: some View {
CacheAsyncImage(
url: url) { phase in
switch phase {
case .success(let image):
VStack {
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: imageWidth)
.padding(.trailing, 10)
Spacer()
}
case .failure:
Image("placeholder-image")
case .empty:
HStack(alignment: .center) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .red))
}
@unknown default:
Image("placeholder-image")
}
}
}
}
Here is the code for view for respective properties including the image ..
import SwiftUI
struct PeopleCellView: View {
let people: PeopleData
var body: some View {
HStack {
if let url = URL(string: people.avatar ?? ""){
PeopleAsyncImageView(url: url)
.frame(width: 150, height: 150)
.mask(RoundedRectangle(cornerRadius: 16))
}
VStack(alignment: .leading,spacing: 5){
Text("First Name: " + people.firstName)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.headline)
Text("Last Name: " + (people.lastName ?? ""))
.frame(maxWidth: .infinity, alignment: .leading)
Text("Email: " + (people.email ?? ""))
.frame(maxWidth: .infinity, alignment: .leading)
.font(.subheadline)
}
}
}
}
struct PeopleCellView_Previews: PreviewProvider {
static var previews: some View {
PeopleCellView(people: Constants.previewPeopleObj)
}
}
Here is the details view code ..
import SwiftUI
struct PeopleDetailsView: View {
@State var people: PeopleData
var body: some View {
VStack(alignment: .center) {
Image(people.avatar ?? "")
Text("First Name - \(people.firstName)")
Text("Last Name: \(people.lastName ?? " ")")
Text("Email: \(people.email ?? " ")")
}
}
}
struct PeopleDetailsView_Previews: PreviewProvider {
static var previews: some View {
PeopleDetailsView(people: Constants.previewPeopleObj)
}
}
Here is the screenshot of the app ..
Here is the screenshot of the details view ..
Based on your code, the people.avatar
is just a URL, which your list is loading into a custom PeopleAsyncImageView(url: url)
, which presumably downloads the image.
While in details you are trying to use Image(people.avatar ?? "")
- that is passing a URL to Image
view, which does not accept URLs.
So you have 2 options:
PeopleAsyncImageView
in your PeopleDetailsView
viewPeopleAsyncImageView
to cache an image it downloaded (either on disk, or in memory) and pass it to PeopleDetailsView
.The first solution is simpler, just copy how the image is loaded in your list:
struct PeopleDetailsView: View {
@State var people: PeopleData
var body: some View {
VStack(alignment: .center) {
// Image(people.avatar ?? "") <-- remove this
if let url = URL(string: people.avatar ?? ""){ // <-- add this
PeopleAsyncImageView(url: url)
.frame(width: 150, height: 150)
.mask(RoundedRectangle(cornerRadius: 16))
}