I have a goal to convert View to Image using SwiftUI.
From the first image I have a look that is exactly what I want.
Link picture View will be convert to an Image:
but when I convert the display to an image, the background object and the sticker (Santa's hat) size and position are messed up, even though I've made it neat and fit exactly what I want.
the following is the result of the image:
View results that have been converted into an Image:
This is my Code View
import SwiftUI
struct SnapshotView: View {
#ObservedObject var effectData: EffectViewModel
#ObservedObject var eraseModel: EraseViewModel
#ObservedObject var stickerData: StickerViewModel
#Binding var image: UIImage?
#Binding var isActive: Bool
#Binding var isEffectAvailable: Bool
#Binding var sliderValueBlur: Double
init(image: Binding<UIImage?>, isActive: Binding<Bool>,
isEffectAvailable: Binding<Bool>,
sliderValueBlur: Binding<Double>,
effectData: ObservedObject<EffectViewModel>,
eraseModel: ObservedObject<EraseViewModel>,
stickerData: ObservedObject<StickerViewModel>) {
_image = image
_isActive = isActive
_isEffectAvailable = isEffectAvailable
_sliderValueBlur = sliderValueBlur
_effectData = effectData
_eraseModel = eraseModel
_stickerData = stickerData
}
var body: some View {
GeometryReader { geo in
VStack {
ZStack(alignment: .center) {
if isEffectAvailable {
VStack {
Image(uiImage: self.effectData.modelEffect!.image_effect)
.resizable()
.scaledToFit()
.frame(width: geo.size.width, alignment: .center)
.position(self.effectData.modelEffect!.position_effect)
.scaleEffect(self.effectData.modelEffect!.currentScale)
.mask(
Image(uiImage: image!)
.resizable()
.scaledToFit()
.frame(width: geo.size.width, alignment: .center)
)
.blur(radius: CGFloat(self.effectData.modelEffect!.valueBlur))
.opacity(self.effectData.modelEffect!.opacity_effect)
}
.background(
ZStack {
Image(uiImage: image!)
.resizable()
.scaledToFit()
.frame(width: geo.size.width, alignment: .top)
.blur(radius: CGFloat(sliderValueBlur)*4)
}
.border(Color.black, width: 1)
)
} else {
ZStack {
Image(uiImage: image!)
.resizable()
.scaledToFit()
.frame(width: geo.size.width, alignment: .top)
.blur(radius: CGFloat(sliderValueBlur)*4)
}
.border(Color.black, width: 1)
}
Image(uiImage: self.eraseModel.inputImage)
.resizable()
.scaledToFit()
.frame(width: geo.size.width, alignment: .center)
.overlay(
VStack {
ForEach(stickerData.selectedSticker.indices, id: \.self) { index in
ZStack(alignment: .bottomTrailing) {
Image(uiImage: stickerData.selectedSticker[index].image)
.resizable()
.frame(width: stickerData.selectedSticker[index].width, height: stickerData.selectedSticker[index].height, alignment: .center)
.scaleEffect(stickerData.selectedSticker[index].scale)
.overlay(
Rectangle()
.stroke(Color.gray, lineWidth: stickerData.selectedSticker[index].isEditing ? 4 : 0)
)
}
.frame(width: 130, height: 130, alignment: .center)
.position(stickerData.selectedSticker[index].position)
.scaleEffect(stickerData.selectedSticker[index].scale)
}
}
)
}
}
}
}
}
And this is the function I use to convert View to Image
extension UIView {
var renderedImage: UIImage {
// rect of capure
let rect = self.bounds
// create the context of bitmap
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
let context: CGContext = UIGraphicsGetCurrentContext()!
self.layer.render(in: context)
// get a image from current context bitmap
let capturedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return capturedImage
}
}
extension View {
func takeScreenshot(origin: CGPoint, size: CGSize) -> UIImage {
let window = UIWindow(frame: CGRect(origin: origin, size: size))
let hosting = UIHostingController(rootView: self)
hosting.view.frame = window.frame
window.addSubview(hosting.view)
window.makeKeyAndVisible()
return hosting.view.renderedImage
}
}
Usage example:
Button(action: {
let viewSnapshot = SnapshotView(image: self.$image, isActive: self.$isActive, isEffectAvailable: self.$isEffectAvailable, sliderValueBlur: self.$sliderValueBlur, effectData: self._effectData, eraseModel: self._eraseModel, stickerData: self._stickerData)
let saveImage = viewSnapshot.takeScreenshot(origin: geo.frame(in: .global).origin, size: self.image!.size)
UIImageWriteToSavedPhotosAlbum(saveImage, nil, nil, nil)
}, label: {
Text("SAVE")
.foregroundColor(.white)
})
Maybe you can help me, what's wrong here
Related
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()
}
}
So I've been trying to create a foreach containing an asyncimage which will later navigate to the detailview when pressed, but when I press the images that don't match the order of the images I have, my images show them in random order. I don't know how to go to detailview with asyncimage according to the order of data in foreach in HistoryView()
import SwiftUI
struct HistoryView: View {
#EnvironmentObject var historyManager : HistoryManager
#State var isActive : Bool = false
var body: some View {
NavigationView{
let jsonDataList = historyManager.jsondata
ScrollView{
VStack{
if let response = jsonDataList{
//MARK: diberi reversed agar ketika update maka akan munculnya dari bawah
ForEach(response.dataJson.result.check_in.reversed(), id:\.self){ items in
ZStack(alignment: .leading){
//color changing when late is true
if (historyManager.warna == true){
Image("RoundedRectangle-red")
.resizable()
.frame(width: 370, height: 200)
}else{
Image("RoundedRectangle-green")
.resizable()
.frame(width: 370, height: 200)
}
//Image
NavigationLink(isActive : $isActive){
DetailImageHistory(isActive: $isActive, name: items.links)
} label: {
let url = URL(string: items.links)
AsyncImage(url: url){ image in
image
.resizable()
.scaledToFill()
.frame(width: 45, height: 45)
.clipShape(Circle())
} placeholder: {
ProgressView()
.progressViewStyle(.circular)
}
.frame(width: 45, height: 45)
.clipShape(Circle())
}.offset(x: 300, y: -70)
//Content
VStack(alignment:.leading, spacing: 2){
Text("Presensi Datang").bold().font(.system(size: 15))
Text("\(items.createdAts)").font(.system(size: 15))
if(historyManager.keteranganKehadiran == true){
Text("Tepat Waktu").fontWeight(.bold).foregroundColor(.white)
}else{
Text("Terlambat").fontWeight(.bold).foregroundColor(.white).font(.system(size: 15))
}
Text("\(items.places)").font(.system(size: 15))
Text("\(items.locations)").multilineTextAlignment(.leading).font(.system(size: 15))
Text("Keterangan: ").bold().font(.system(size: 15))
Text("\(items.tujuans)").bold().font(.system(size: 15))
}.padding(.horizontal)
}
}
}
}
}
}
}
}
and this is my DetailView
import SwiftUI
struct DetailImageHistory: View {
#EnvironmentObject var historyManager: HistoryManager
#Binding var isActive : Bool
var name : String
var body: some View {
let url = URL(string: name)
AsyncImage(url: url){ image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300, height: 300)
} placeholder: {
Color.gray
}
.frame(width: 300, height: 300)
}
}
UPDATE: my problem is solved i dont know why I'm not changing anything but somehow it is actually worked now lol
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()
}
}
)
}
}
}
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)
}
}
I want to make the QRimage load after the button is clicked in swiftUI . Right now I just pass the UI Image generated by the function straight away into the image frame. How do I present the function in the button action section?
Right now My code looks like this;
struct QRCodeView: View {
#State private var UUID = "usufuf321"
#State private var phoneNo = "09-012948"
let context = CIContext()
let filter = CIFilter.qrCodeGenerator()
var body: some View {
GroupBox{
VStack(alignment: .center, spacing: 10){
Image(uiImage: QRGenerator(from: "\(UUID)\n\(phoneNo)"))
.interpolation(.none)
.resizable()
.scaledToFit()
.frame(width: 250, height: 250, alignment: .center)
.clipped()
Button(action: {
}, label: {
Text ("GENERATE")
.scaledToFit()
.frame(width: 250, height: 60, alignment: .center)
.cornerRadius(12)
})
}
.frame(maxWidth:.infinity, alignment: .center)
}
.cornerRadius(20)
.padding(.all,10)
}
func QRGenerator(from string: String) -> UIImage {
let data = Data(string.utf8)
filter.setValue(data, forKey: "inputMessage")
if let outputImage = filter.outputImage {
if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
return UIImage(cgImage: cgimg)
}
}
return UIImage(systemName: "xmark.circle") ?? UIImage()
}
}
Use #State
struct QRCodeView: View {
#State private var qrImage: Image?
var body: some View {
GroupBox{
VStack(alignment: .center, spacing: 10){
if let qrImage = qrImage {
qrImage
.interpolation(.none)
.resizable()
.scaledToFit()
.frame(width: 250, height: 250, alignment: .center)
.clipped()
}
Button(action: {
qrImage = Image(uiImage: QRGenerator(from: "\(UUID)\n\(phoneNo)"))
}, label: {
Text ("GENERATE")
.scaledToFit()
.frame(width: 250, height: 60, alignment: .center)
.cornerRadius(12)
})
}
.frame(maxWidth:.infinity, alignment: .center)
}
}
}