SwiftUI AsyncImage orientation - ios

I am using AsyncImage to download a jpg from my Parse Server. If I download the file manually it has an orientation of 6 (90 Degree counterclockwise), but when the file is returned by AsyncImage, it is missing this orientation and appears rotated.
As an aside, when I substitute SBPAsyncImage for AsyncImage, the orientation is correct.
Is there away for AsyncImage to detect and correct for orientation?
AsyncImage(url: photoURL) { image in
image
.resizable()
.scaledToFill()
.frame(width: 200, height: 200, alignment: .leading)
} placeholder: {
ProgressView()
}
Edit: I was over zealous in simplifying the code. Orientation is correct when AsyncImage is displayed in a simple View but my layout has a list of ScrollViews displaying the images fetched from a Parse Server. Here is a version of the original:
struct TimeLineView: View {
//: A view model in SwiftUI
#StateObject var viewModel = PFTour.query(matchesKeyInQuery(key: "routeID", queryKey: "uniqueID", query: PFRoute.query("state" == "Colorado")))
.order([.descending("date")])
.include("route")
.include("creator")
.include("photos")
.viewModel
var body: some View {
Group {
if let error = viewModel.error {
Text(error.description)
} else {
List(viewModel.results, id: \.id) { tour in
ParseTourImageOnlyScrollView(tour: tour)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.edgesIgnoringSafeArea(.all)
.onAppear(perform: {
viewModel.find()
})
}
Each cell displays a ScrollView:
struct ParseTourImageOnlyScrollView: View {
let tour: PFTour
var body: some View {
VStack {
Divider()
ScrollView(.horizontal) {
HStack(spacing: 5.0) {
if let photos = tour.photos {
ForEach(photos, id: \.self) { parsePhoto in
PhotoOnlyView(photoFile: parsePhoto.photo!)
}
}
}
}
Divider()
Spacer()
}
}
When comparing AsyncImage with BackPortAsyncImage, the former does not show the correct orientation for the image data at https://parsefiles.back4app.com/zgqRRfY6Tr5ICfdPocRLZG8EXK59vfS5dpDM5bqr/7d5eaf509e0745be0314aa493099dc82_file.bin:
struct PhotoOnlyView: View {
let photoFile: ParseFile
var body: some View {
if #available(iOS 15.0, *) {
VStack{
SwiftUI.AsyncImage(url: photoFile.url) { image in
image
.resizable()
.scaledToFill()
} placeholder: {
ProgressView()
}
.frame(width: UIScreen.main.bounds.size.width - 150, height: UIScreen.main.bounds.size.width - 150, alignment: .leading)
BackportAsyncImage(url: photoFile.url) { image in
image
.resizable()
.scaledToFill()
} placeholder: {
ProgressView()
}
.frame(width: UIScreen.main.bounds.size.width - 150, height: UIScreen.main.bounds.size.width - 150, alignment: .leading)
}
}
else {
ProgressView()
}
}

Related

SwiftUI Crash BoxVTable

I am seeing an odd crash with some beta testers on TestFlight. It seems to be at app startup, but prior to the point where we are making any network calls. There does not seem to be a lot on the internet regarding this, and symbolication isn't helping much either. Just curious if anyone has run into this before and what they did about it? I have had it happen on two tester's devices and I have never been able to recreate the issue locally.
For a bit of context, I am creating a list view on my main screen with a LazyVGrid that contains a LazyHGrid inside of it that are both filled using published vars from my viewmodel, though this seems to happen prior to any of those being created.
Thanks for any ideas / help
Edit: Some more details / actual code:
The view with the grids:
import SwiftUI
import Kingfisher
struct FeaturedView: View {
#EnvironmentObject var viewRouter: ViewRouter
#EnvironmentObject var tabRouter : TabRouter
#StateObject var loadingViewModel = LoadingViewModel()
private let imageProcessor = SVGImgProcessor()
private let playerManager = PlayerManager.shared
private var gridItemLayout = [GridItem(.flexible())]
let userDefaults = UserDefaults.standard
var body: some View {
let padding: CGFloat = 20
let paddingHStack: CGFloat = 25
GeometryReader { geometry in
ZStack(alignment: .top){
Color(hex:"#00091C").edgesIgnoringSafeArea(.all)
VStack {
HStack {
HStack {
Text("Hello, \(loadingViewModel.name)")
.frame(alignment: .leading)
.multilineTextAlignment(.center)
.font(Font.custom("poppins-medium", size: 20))
.foregroundColor(Color(hex:"#667C95"))
.padding(.leading, 15)
.padding(.top, 15)
.padding(.bottom, 15)
Image("PremiumStar")
.resizable()
.frame(width: 15.0, height: 15.0)
.opacity(userDefaults.isSubscriber() ? 1 : 0)
}
Spacer()
Button(action: {
print("Settings Clicked")
viewRouter.currentPage = .settingsFlow
}) {
Image("Settings")
.resizable()
.frame(width: 22.0, height: 22.0)
.padding(15)
}
}
ScrollView(showsIndicators: false) {
VStack(spacing: 10) {
LazyVGrid(columns: gridItemLayout, spacing: 17) {
ForEach(loadingViewModel.getCategories()) { category in
Text(category.title)
.foregroundColor(.white)
.font(Font.custom("poppins-bold", size: 30))
ZStack {
KFImage(URL(string: (category.background?.svg!)!))
.resizable()
.setProcessor(imageProcessor)
.frame(width: geometry.size.width - padding, height: 175, alignment: .topLeading)
.aspectRatio(contentMode: .fill)
.clipped()
.cornerRadius(5)
ScrollView(.horizontal, showsIndicators: false) {
LazyHGrid(rows: gridItemLayout, spacing: 20){
ForEach(loadingViewModel.getSoundsForCategory(category: category.key)) { sound in
Button(action: {
playerManager.play(sound: sound)
}) {
VStack {
ZStack{
RoundedRectangle(cornerRadius: 5).frame(width: 90, height: 90)
.foregroundColor(Color.black)
.opacity(0.85)
ZStack(alignment:.bottomTrailing){
KFImage(URL(string: sound.icon.png!)!)
.resizable()
.renderingMode(.template)
.foregroundColor(.white)
.frame(width: 75, height: 75)
.aspectRatio(contentMode: .fill)
.clipped()
Image("LockIcon")
.frame(width: 12, height: 12, alignment: .bottomTrailing)
.aspectRatio(contentMode: .fill)
.clipped()
.hidden(loadingViewModel.isSubscriber || sound.tier != 2)
}
}
Text(sound.name)
.foregroundColor(.white)
.font(Font.custom("poppins-regular", size: 12))
.lineLimit(1)
.frame(minWidth: 0, idealWidth: 90, maxWidth: 100, alignment: .center)
}
}
}
}.padding(.horizontal)
}
.frame(width: geometry.size.width - paddingHStack, height: 175, alignment: .topLeading)
}
Button("Explore All"){
print("Explore All \(category.title) Tapped")
tabRouter.categoryKey = category.key
tabRouter.hasChanged = true
tabRouter.currentTab = 1
}
.font(Font.custom("poppins-bold", size: 15))
.foregroundColor(Color.white)
}
}.padding(.bottom, 120)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
FeaturedView()
}
}
That is loaded by this tab view, which goes between two screens which share the same viewModel / load the same data
// MARK: Tab View
var body: some View {
ZStack{
TabView(selection: $selectedTab){
FeaturedView()
.environmentObject(tabRouter)
.tabItem{
Label("FEATURED", image: "Featured")
}.tag(0)
.overlay(
VStack {
if (showOverlay && !isExpanded){
playView
} else {
EmptyView()
}
}
)
SoundView()
.environmentObject(tabRouter)
.tabItem{
Label("SOUNDS", image: "Sounds")
}.tag(1)
.overlay(
VStack {
if (showOverlay && !isExpanded){
playView
} else {
EmptyView()
}
}
)
}
}
}

Getting size of SwiftUI image view

I would like to get the frame size of my image, to use for some calculations in a drag gesture recognizer (basically normalize the touch coordinates of the drag).
I have tried to use GeometryReader but it expands to fill the whole height and thus the reported height is not correct.
How can I fix this behavior? Is there any other way of getting the view size of the image?
struct ContentView: View {
var body: some View {
ZStack(alignment: .center) {
GeometryReader { reader in
Image(uiImage: UIImage(named: "test")!)
.resizable()
.aspectRatio(contentMode: .fit)
.shadow(radius: 5)
//.gesture(dragGesture(forSize: reader.size))
}
.background(Color.red)
}
}
}
Use AVMakeRect from AVFoundation. For more
struct ContentView: View {
var body: some View {
GeometryReader { reader in
ZStack(alignment: .center) {
if let uiImage = UIImage(named: "test") {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fit)
.shadow(radius: 5)
.onReceive(Just(reader), perform: { _ in
let localFrame = reader.frame(in: .local)
let imageFrame = AVMakeRect(aspectRatio: uiImage.size, insideRect: localFrame)
print("Full frame : ", localFrame)
print("Image frame : ", imageFrame)
})
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
}.background(Color.red)
}
}

Injecting A Button to ScrollView

I'm developing a iOS app relies on SwiftUI.
I have a ZStack view and inside it, I call a another view along with a button.
ZStack(alignment: .bottomTrailing) {
ImageStepView(data: self.data[randomImageNum])
Button(action: { self.showFavorites = true }) {
HStack {
Image(systemName: "suit.heart.fill")
Text("FAVORITES")
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 15)
.foregroundColor(.white)
.padding()
.background(Color.black.opacity(0.6))
.cornerRadius(5)
.padding(.horizontal, 20)
}
}
ImageStepView.swift
struct ImageStepView: View {
var data: ImageDataModel
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
VStack{
Image(data.image)
.resizable()
.border(Color.white, width: 5)
.overlay(
Rectangle().stroke(Color.white, lineWidth: 4))
.shadow(color: Color.gray, radius: 10, x: 10, y: 10)
.scaledToFit()
.frame(height: geometry.size.height-110)
} .padding()
VStack{
VStack(alignment: .leading) {
HStack{...}
HStack {...}
}
Spacer()
.frame(height: 50)
VStack(alignment: .leading){
VStack{
HStack {...}
HStack {...}
HStack {...}
}
}
}.padding()
}.background(Color("Color").ignoresSafeArea(.all))
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
ImageStepView has a ScroolView, that's why Button not appears on the end of ScroolView, it appears on bottom of the screen.
What I want is to show The Button not on bottom of the screen but end of the ImageStepView.
You can make the ImageStepView accept a generic parameter - a view to be injected:
struct ImageStepView<Injected: View>: View {
var data: ImageDataModel
var injectedView: () -> Injected
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
// ScrollView contents
injectedView()
}
.background(Color("Color").ignoresSafeArea(.all))
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
and pass the injected view to the ImageStepView:
ImageStepView(data: self.data[randomImageNum]) {
Button(action: { self.showFavorites = true }) { ... }
}
or
ImageStepView(data: self.data[randomImageNum]) { EmptyView() }

Why are Image objects not showing up from a SwiftUI tutorial?

I've been trying to change the layout to the WatchLandmarks tutorial that Apple provides for making SwiftUI apps for the watch (https://developer.apple.com/tutorials/swiftui/creating-a-watchos-app) and I can't figure out why the images aren't loading for me. It works just fine in the completed tutorial code, and the only differences are in my changes to the layout of LandmarkList and LandmarkRow.
changes to LandmarkList:
struct LandmarkList<DetailView: View>: View {
#EnvironmentObject private var userData: UserData
let detailViewProducer: (Landmark) -> DetailView
var body: some View {
VStack(alignment: .leading){
Text(" Landmarks")
.font(.system(size: 16))
ScrollView(.horizontal, showsIndicators:false){
HStack(alignment: .center){
ForEach(userData.landmarks) { landmark in
// if !self.userData.showFavoritesOnly || landmark.isFavorite {
NavigationLink(
destination: self.detailViewProducer(landmark).environmentObject(self.userData)) {
LandmarkRow(landmark: landmark)
.frame( maxWidth: 70, maxHeight: 70, alignment: .leading)
}
// }
}
}
}.frame(minWidth: 0, maxWidth: .infinity, maxHeight: 100, alignment: .center)//.frame(width:150, height: 100)
Text(self.userData.landmarks[0].name)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 24, alignment: .center)
HStack(spacing: 32){
Button(action: { print("first button") }) {
Text("P")
}.frame(minWidth: 20, maxWidth:50, minHeight: 20, maxHeight:50)
.background(Color.orange)
.clipShape(Circle())
.overlay(Circle().stroke(Color.clear, lineWidth: 4))
.shadow(radius: 10)
Button(action: { print("second button") }) {
Text("S")
}.frame(minWidth: 20, maxWidth:50, minHeight: 20, maxHeight:50)
.background(Color.orange)
.clipShape(Circle())
.overlay(Circle().stroke(Color.clear, lineWidth: 4))
.shadow(radius: 10)
}.frame(minWidth: 0, maxWidth: .infinity, maxHeight: 100, alignment: .center)
// List {
// Toggle(isOn: $userData.showFavoritesOnly) {
// Text("Show Favorites Only")
// }
//
// ForEach(userData.landmarks) { landmark in
// if !self.userData.showFavoritesOnly || landmark.isFavorite {
// NavigationLink(
// destination: self.detailViewProducer(landmark).environmentObject(self.userData)) {
// LandmarkRow(landmark: landmark)
// }
// }
// }
// }
// .navigationBarTitle(Text("Landmarks"))
}.frame(maxWidth: .infinity, minHeight:200, maxHeight: .infinity, alignment: .leading)
}
}
changes to LandmarkRow
struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
HStack {
landmark.image
.resizable()
.frame(width: 50, height: 50)
.cornerRadius(8.0)
// Text(landmark.name)
// Spacer()
//
// if landmark.isFavorite {
// Image(systemName: "star.fill")
// .imageScale(.medium)
// .foregroundColor(.yellow)
// }
}
}
}
What am I doing wrong?
Try to call .renderingMode(.original) on the image.
There are two types of rendering modes for your image assets:
Original
Template
Original mode will portray the image asset as it is, while the template mode will turn all the non-transparent parts of the image into one color that you can set. The default color is black.
Note: No overview available is available in Apple documentation.
In this tutorial, you’ll learn what rendering mode in SwiftUI is and how to use it.

SwiftUI - Button with Image is clickable outside

I have a ScrollView with multiple Buttons. A Button contains a Image and a Text underneath.
As the images are pretty large I am using .scaledToFill and .clipped. And it seems that the 'clipped' part of the image is still clickable even if it's not shown.
In the video you see I am clicking on button 1 but button 2 is triggered.
This is my Coding. The Image is inside the View Card.
struct ContentView: View {
#State var useWebImage = false
#State var isSheetShowing = false
#State var selectedIndex = 0
private let images = [
"https://images.unsplash.com/photo-1478368499690-1316c519df07?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2706&q=80",
"https://images.unsplash.com/photo-1507154258-c81e5cca5931?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2600&q=80",
"https://images.unsplash.com/photo-1513310719763-d43889d6fc95?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2734&q=80",
"https://images.unsplash.com/photo-1585766765962-28aa4c7d719c?ixlib=rb-1.2.1&auto=format&fit=crop&w=2734&q=80",
"https://images.unsplash.com/photo-1485970671356-ff9156bd4a98?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2734&q=80",
"https://images.unsplash.com/photo-1585607666104-4d5b201d6d8c?ixlib=rb-1.2.1&auto=format&fit=crop&w=2700&q=80",
"https://images.unsplash.com/photo-1577702066866-6c8897d06443?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2177&q=80",
"https://images.unsplash.com/photo-1513809491260-0e192158ae44?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2736&q=80",
"https://images.unsplash.com/photo-1582092723055-ad941d1db0d4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2700&q=80",
"https://images.unsplash.com/photo-1478264635837-66efba4b74ba?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjF9&auto=format&fit=crop&w=2682&q=80"
]
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 40) {
Text(useWebImage ? "WebImage is used." : "SwiftUI Image is used")
.font(.system(size: 18))
.bold()
.kerning(0.5)
.padding(.top, 20)
Toggle(isOn: $useWebImage) {
Text("Use WebImage")
.font(.system(size: 18))
.bold()
.kerning(0.5)
.padding(.top, 20)
}
ForEach(0..<images.count) { index in
Button(action: {
self.selectedIndex = index
self.isSheetShowing.toggle()
}) {
Card(imageUrl: self.images[index], index: index, useWebImage: self.$useWebImage)
}
.buttonStyle(PlainButtonStyle())
}
}
.padding(.horizontal, 20)
.sheet(isPresented: self.$isSheetShowing) {
DestinationView(imageUrl: self.images[self.selectedIndex], index: self.selectedIndex, useWebImage: self.$useWebImage)
}
}
.navigationBarTitle("Images")
}
}
}
struct Card: View {
let imageUrl: String
let index: Int
#Binding var useWebImage: Bool
var body: some View {
VStack {
if useWebImage {
WebImage(url: URL(string: imageUrl))
.resizable()
.indicator(.activity)
.animation(.easeInOut(duration: 0.5))
.transition(.fade)
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
.cornerRadius(12)
.clipped()
} else {
Image("image\(index)")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
.cornerRadius(12)
.clipped()
}
HStack {
Text("Image #\(index + 1) (\(useWebImage ? "WebImage" : "SwiftUI Image"))")
.font(.system(size: 18))
.bold()
.kerning(0.5)
Spacer()
}
}
.padding(2)
.border(Color(.systemRed), width: 2)
}
}
Do you have an idea how to fix this issue?
I already tried to use .resizable(resizingMode: .tile) but I need to shrink the image before I could use just a tile.
For detailed information you can also find the project on GitHub GitHub Project
I would appreciate your help a lot.
The .clipped affects only drawing, and by-default Button has all content clickable not depending what it is.
So if you want make your button clickable only in image area, you have to limit hit testing only to its rect explicitly and disable everything else.
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4.
Demo code (simplified variant of your snapshot):
struct ButtonCard: View {
var body: some View {
VStack {
Image("sea")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
.cornerRadius(12)
.contentShape(Rectangle()) // << define clickable rect !!
.clipped()
HStack {
Text("Image #1")
.font(.system(size: 18))
.bold()
.kerning(0.5)
Spacer()
}.allowsHitTesting(false) // << disable label area !!
}
.padding(2)
.border(Color(.systemRed), width: 2)
}
}
struct TestClippedButton: View {
var body: some View {
Button(action: { print(">> tapped") }) {
ButtonCard()
}.buttonStyle(PlainButtonStyle())
}
}

Resources