diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 4f50717..0f8ed83 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -24,7 +24,7 @@ F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE42966E160001D9973 /* Color+SystemColors.swift */; }; F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE62966E1D1001D9973 /* Color+Assets.swift */; }; F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */; }; - F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */; }; + F8341F90295C636C009C8EE6 /* Data+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F8F295C636C009C8EE6 /* Data+Exif.swift */; }; F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F91295C63BB009C8EE6 /* ImageStatus.swift */; }; F83901A6295D8EC000456AE2 /* LabelIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83901A5295D8EC000456AE2 /* LabelIcon.swift */; }; F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D4970296402DC00751DF7 /* AuthorizationService.swift */; }; @@ -75,6 +75,8 @@ F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; }; F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; }; F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */; }; + F8C14392296AF0B3001FE31D /* String+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C14391296AF0B3001FE31D /* String+Exif.swift */; }; + F8C14394296AF21B001FE31D /* Double+Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C14393296AF21B001FE31D /* Double+Round.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -92,7 +94,7 @@ F8210DE42966E160001D9973 /* Color+SystemColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+SystemColors.swift"; sourceTree = ""; }; F8210DE62966E1D1001D9973 /* Color+Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Assets.swift"; sourceTree = ""; }; F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatePlaceholderModifier.swift; sourceTree = ""; }; - F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Exif.swift"; sourceTree = ""; }; + F8341F8F295C636C009C8EE6 /* Data+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Exif.swift"; sourceTree = ""; }; F8341F91295C63BB009C8EE6 /* ImageStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStatus.swift; sourceTree = ""; }; F83901A5295D8EC000456AE2 /* LabelIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelIcon.swift; sourceTree = ""; }; F85D4970296402DC00751DF7 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = ""; }; @@ -145,6 +147,8 @@ F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = ""; }; F8A93D7F2965FED4001D8331 /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = ""; }; F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonClientAuthenticated+Account.swift"; sourceTree = ""; }; + F8C14391296AF0B3001FE31D /* String+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Exif.swift"; sourceTree = ""; }; + F8C14393296AF21B001FE31D /* Double+Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Round.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -192,13 +196,15 @@ F8341F94295C63FE009C8EE6 /* Extensions */ = { isa = PBXGroup; children = ( - F8341F8F295C636C009C8EE6 /* UIImage+Exif.swift */, + F8341F8F295C636C009C8EE6 /* Data+Exif.swift */, F85D4980296417F700751DF7 /* MastodonClientAuthenticated+Context.swift */, F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */, F85D49862964334100751DF7 /* String+Date.swift */, + F8C14391296AF0B3001FE31D /* String+Exif.swift */, F8210DE22966D256001D9973 /* Status+StatusData.swift */, F8210DE42966E160001D9973 /* Color+SystemColors.swift */, F8210DE62966E1D1001D9973 /* Color+Assets.swift */, + F8C14393296AF21B001FE31D /* Double+Round.swift */, ); path = Extensions; sourceTree = ""; @@ -452,7 +458,7 @@ F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */, F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */, F897978D2968369600B22335 /* HapticService.swift in Sources */, - F8341F90295C636C009C8EE6 /* UIImage+Exif.swift in Sources */, + F8341F90295C636C009C8EE6 /* Data+Exif.swift in Sources */, F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */, F85D4981296417F700751DF7 /* MastodonClientAuthenticated+Context.swift in Sources */, F88C246E295C37B80006098B /* MainView.swift in Sources */, @@ -469,6 +475,7 @@ F88ABD9429687CA4004EF61E /* ComposeView.swift in Sources */, F85D497D29640D5900751DF7 /* InteractionRow.swift in Sources */, F866F6A729604629002E8F88 /* SignInView.swift in Sources */, + F8C14392296AF0B3001FE31D /* String+Exif.swift in Sources */, F88C246C295C37B80006098B /* VernissageApp.swift in Sources */, F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */, F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */, @@ -477,6 +484,7 @@ F897978F29684BCB00B22335 /* LoadingView.swift in Sources */, F88FAD2D295F4AD7009B20C9 /* ApplicationState.swift in Sources */, F866F6A1296040A8002E8F88 /* ApplicationSettings+CoreDataProperties.swift in Sources */, + F8C14394296AF21B001FE31D /* Double+Round.swift in Sources */, F866F6AE29606367002E8F88 /* ApplicationViewMode.swift in Sources */, F8A93D802965FED4001D8331 /* AccountService.swift in Sources */, F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */, diff --git a/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift b/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift index 69382df..ab3d45c 100644 --- a/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift +++ b/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift @@ -3,13 +3,10 @@ // Copyright © 2023 Marcin Czachurski and the repository contributors. // Licensed under the MIT License. // - -// import Foundation import CoreData - extension AttachmentData { @nonobjc public class func fetchRequest() -> NSFetchRequest { @@ -25,10 +22,13 @@ extension AttachmentData { @NSManaged public var text: String? @NSManaged public var type: String @NSManaged public var url: URL + @NSManaged public var exifCamera: String? + @NSManaged public var exifLens: String? + @NSManaged public var exifExposure: String? + @NSManaged public var exifCreatedDate: String? @NSManaged public var statusRelation: StatusData? } extension AttachmentData : Identifiable { - } diff --git a/Vernissage/Extensions/Data+Exif.swift b/Vernissage/Extensions/Data+Exif.swift new file mode 100644 index 0000000..cc562c0 --- /dev/null +++ b/Vernissage/Extensions/Data+Exif.swift @@ -0,0 +1,100 @@ +// +// https://mczachurski.dev +// Copyright © 2022 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + + +import Foundation +import UIKit + +public extension Dictionary { + func getExifValue(_ key: String) -> String? { + if let value = self[key] as? String { + return value + } + + if let dictionary = self[key] as? [String: Any], let value = dictionary[key] { + return value as? String + } + + return nil + } +} + +public extension Data { + func getExifData() -> [String: Any]? { + var imageInfo: [String: Any]? = nil + + guard let imageSource = CGImageSourceCreateWithData(self as CFData, nil), + let metadata = CGImageSourceCopyMetadataAtIndex(imageSource, 0, nil), + let tags = CGImageMetadataCopyTags(metadata) else { + return nil + } + + imageInfo = self.readMetadataTagArr(tagArr: tags) + return imageInfo + } + + /// Reads the Arrays of tags and convert them into a dictionary of [String: Any]. + private func readMetadataTagArr(tagArr: CFArray) -> [String: Any]? { + var result = [String: Any]() + + for (_, tag) in (tagArr as NSArray).enumerated() { + let tagMetadata = tag as! CGImageMetadataTag + if let cfName = CGImageMetadataTagCopyName(tagMetadata) { + let name = String(cfName) + result[name] = self.readMetadataTag(metadataTag: tagMetadata) + } + } + return result + } + + /// Convert CGImageMetadataTag to a dictionary of [String: Any]. + private func readMetadataTag(metadataTag: CGImageMetadataTag) -> [String: Any] { + var result = [String: Any]() + guard let cfName = CGImageMetadataTagCopyName(metadataTag) else { return result } + let name = String(cfName) + let value = CGImageMetadataTagCopyValue(metadataTag) + + /// checking the type of `value` object and then performing respective operation on `value` + if CFGetTypeID(value) == CFStringGetTypeID() { + let valueStr = String(value as! CFString) + result[name] = valueStr + } else if CFGetTypeID(value) == CFDictionaryGetTypeID() { + let nsDict: NSDictionary = value as! CFDictionary + result[name] = self.getDictionary(from: nsDict) + } else if CFGetTypeID(value) == CFArrayGetTypeID() { + let valueArr: NSArray = value as! CFArray + for (_, item) in valueArr.enumerated() { + let tagMetadata = item as! CGImageMetadataTag + result[name] = self.readMetadataTag(metadataTag: tagMetadata) + } + } else { + // when the data was of some other type + let descriptionString: CFString = CFCopyDescription(value); + let str = String(descriptionString) + result[name] = str + } + return result + } + + /// Converting CGImage Metadata dictionary to [String: Any] + private func getDictionary(from nsDict: NSDictionary) -> [String: Any] { + var subDictionary = [String: Any]() + for (key, val) in nsDict { + guard let key = key as? String else { continue } + let tempDict: [String: Any] = [key: val] + if JSONSerialization.isValidJSONObject(tempDict) { + subDictionary[key] = val + } else { + let mData = val as! CGImageMetadataTag + let tempDict: [String: Any] = [key: self.readMetadataTag(metadataTag: mData)] + if JSONSerialization.isValidJSONObject(tempDict) { + subDictionary[key] = tempDict + } + } + } + return subDictionary + } +} diff --git a/Vernissage/Extensions/Double+Round.swift b/Vernissage/Extensions/Double+Round.swift new file mode 100644 index 0000000..8aa44a1 --- /dev/null +++ b/Vernissage/Extensions/Double+Round.swift @@ -0,0 +1,14 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +extension Double { + func rounded(toPlaces places:Int) -> Double { + let divisor = pow(10.0, Double(places)) + return (self * divisor).rounded() / divisor + } +} diff --git a/Vernissage/Extensions/Status+StatusData.swift b/Vernissage/Extensions/Status+StatusData.swift index e8a855d..d5dde78 100644 --- a/Vernissage/Extensions/Status+StatusData.swift +++ b/Vernissage/Extensions/Status+StatusData.swift @@ -26,7 +26,28 @@ extension Status { attachmentData.statusId = statusData.id attachmentData.data = imageData - // TODO: read exif informatio + // Read exif information. + if let exifProperties = imageData.getExifData() { + if let make = exifProperties.getExifValue("Make"), let model = exifProperties.getExifValue("Model") { + attachmentData.exifCamera = "\(make) \(model)" + } + + // "Lens" or "Lens Model" + if let lens = exifProperties.getExifValue("Lens") { + attachmentData.exifLens = lens + } + + if let createData = exifProperties.getExifValue("CreateDate") { + attachmentData.exifCreatedDate = createData + } + + if let focalLenIn35mmFilm = exifProperties.getExifValue("FocalLenIn35mmFilm"), + let fNumber = exifProperties.getExifValue("FNumber")?.calculateExifNumber(), + let exposureTime = exifProperties.getExifValue("ExposureTime"), + let photographicSensitivity = exifProperties.getExifValue("PhotographicSensitivity") { + attachmentData.exifExposure = "\(focalLenIn35mmFilm)mm, f/\(fNumber), \(exposureTime)s, ISO \(photographicSensitivity)" + } + } attachmentData.statusRelation = statusData statusData.addToAttachmentRelation(attachmentData) diff --git a/Vernissage/Extensions/String+Exif.swift b/Vernissage/Extensions/String+Exif.swift new file mode 100644 index 0000000..9ddea17 --- /dev/null +++ b/Vernissage/Extensions/String+Exif.swift @@ -0,0 +1,27 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +extension String { + func calculateExifNumber() -> String? { + guard self.contains("/") else { + return self + } + + let parts = self.split(separator: "/") + guard parts.count == 2 else { + return nil + } + + if let first = Int(parts[0]), let second = Int(parts[1]) { + let calculated = Double(first) / Double(second) + return String(calculated.rounded(toPlaces: 2)) + } + + return nil + } +} diff --git a/Vernissage/Extensions/UIImage+Exif.swift b/Vernissage/Extensions/UIImage+Exif.swift deleted file mode 100644 index bcb8475..0000000 --- a/Vernissage/Extensions/UIImage+Exif.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2022 Marcin Czachurski and the repository contributors. -// Licensed under the MIT License. -// - - -import Foundation -import UIKit - -public extension UIImage { - func getExifData() -> CFDictionary? { - var exifData: CFDictionary? = nil - - if let data = self.jpegData(compressionQuality: 1.0) { - data.withUnsafeBytes { - let bytes = $0.baseAddress?.assumingMemoryBound(to: UInt8.self) - if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.count), - let source = CGImageSourceCreateWithData(cfData, nil) { - exifData = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) - } - } - } - - return exifData - } -} diff --git a/Vernissage/Services/AuthorizationService.swift b/Vernissage/Services/AuthorizationService.swift index 791b23c..a6dde64 100644 --- a/Vernissage/Services/AuthorizationService.swift +++ b/Vernissage/Services/AuthorizationService.swift @@ -48,7 +48,7 @@ public class AuthorizationService { // Create application (we will get clientId amd clientSecret). let oAuthApp = try await client.createApp( - named: "Photofed", + named: "Vernissage", redirectUri: "oauth-vernissage://oauth-callback/mastodon", scopes: Scopes(["read", "write", "follow", "push"]), website: baseUrl) diff --git a/Vernissage/Services/TimelineService.swift b/Vernissage/Services/TimelineService.swift index e4cc280..1b8479d 100644 --- a/Vernissage/Services/TimelineService.swift +++ b/Vernissage/Services/TimelineService.swift @@ -91,13 +91,34 @@ public class TimelineService { // Save attachment in database. let attachmentData = statusData.attachments().first { item in item.id == attachment.id } - ?? AttachmentDataHandler.shared.createAttachmnentDataEntity(viewContext: backgroundContext) + ?? AttachmentDataHandler.shared.createAttachmnentDataEntity(viewContext: backgroundContext) attachmentData.copyFrom(attachment) attachmentData.statusId = statusData.id attachmentData.data = imageData - // TODO: read exif information + // Read exif information. + if let exifProperties = imageData.getExifData() { + if let make = exifProperties.getExifValue("Make"), let model = exifProperties.getExifValue("Model") { + attachmentData.exifCamera = "\(make) \(model)" + } + + // "Lens" or "Lens Model" + if let lens = exifProperties.getExifValue("Lens") { + attachmentData.exifLens = lens + } + + if let createData = exifProperties.getExifValue("CreateDate") { + attachmentData.exifCreatedDate = createData + } + + if let focalLenIn35mmFilm = exifProperties.getExifValue("FocalLenIn35mmFilm"), + let fNumber = exifProperties.getExifValue("FNumber")?.calculateExifNumber(), + let exposureTime = exifProperties.getExifValue("ExposureTime"), + let photographicSensitivity = exifProperties.getExifValue("PhotographicSensitivity") { + attachmentData.exifExposure = "\(focalLenIn35mmFilm)mm, f/\(fNumber), \(exposureTime)s, ISO \(photographicSensitivity)" + } + } if attachmentData.isInserted { attachmentData.statusRelation = statusData diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents b/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents index 5b5612d..2e42a0d 100644 --- a/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents +++ b/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents @@ -27,6 +27,10 @@ + + + + diff --git a/Vernissage/VernissageApp.swift b/Vernissage/VernissageApp.swift index 4767677..9b5989c 100644 --- a/Vernissage/VernissageApp.swift +++ b/Vernissage/VernissageApp.swift @@ -33,7 +33,7 @@ struct VernissageApp: App { .environmentObject(applicationState) } } - .task { + .task { await AuthorizationService.shared.verifyAccount({ accountData in guard let accountData = accountData else { self.applicationViewMode = .signIn @@ -43,9 +43,6 @@ struct VernissageApp: App { self.applicationState.accountData = accountData self.applicationViewMode = .mainView }) - - URLCache.shared.memoryCapacity = 10_000_000 // ~10 MB memory space - URLCache.shared.diskCapacity = 1_000_000_000 // ~1GB disk cache space } .navigationViewStyle(.stack) .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in diff --git a/Vernissage/Views/SignInView.swift b/Vernissage/Views/SignInView.swift index 75e7223..9b51961 100644 --- a/Vernissage/Views/SignInView.swift +++ b/Vernissage/Views/SignInView.swift @@ -30,8 +30,10 @@ struct SignInView: View { Button("Go") { Task { try await AuthorizationService.shared.signIn(serverAddress: serverAddress, { accountData in - self.applicationState.accountData = accountData - onSignInStateChenge(.mainView) + DispatchQueue.main.async { + self.applicationState.accountData = accountData + onSignInStateChenge(.mainView) + } }) } } diff --git a/Vernissage/Views/StatusView.swift b/Vernissage/Views/StatusView.swift index 570209c..7f54461 100644 --- a/Vernissage/Views/StatusView.swift +++ b/Vernissage/Views/StatusView.swift @@ -15,11 +15,18 @@ struct StatusView: View { @State private var showCompose = false @State private var statusData: StatusData? + @State private var exifCamera: String? + @State private var exifExposure: String? + @State private var exifCreatedDate: String? + @State private var exifLens: String? + var body: some View { ScrollView { if let statusData = self.statusData { - VStack (alignment: .leading) { - ImagesCarousel(attachments: statusData.attachments()) + VStack (alignment: .leading) { + ImagesCarousel(attachments: statusData.attachments()) { attachmentData in + self.setAttachment(attachmentData) + } VStack(alignment: .leading) { NavigationLink(destination: UserProfileView( @@ -36,10 +43,10 @@ struct StatusView: View { .padding(.leading, -4) VStack (alignment: .leading) { - LabelIcon(iconName: "camera", value: "SONY ILCE-7M3") - LabelIcon(iconName: "camera.aperture", value: "Viltrox 24mm F1.8 E") - LabelIcon(iconName: "timelapse", value: "24.0 mm, f/1.8, 1/640s, ISO 100") - LabelIcon(iconName: "calendar", value: "2 Oct 2022") + LabelIcon(iconName: "camera", value: self.exifCamera) + LabelIcon(iconName: "camera.aperture", value: self.exifLens) + LabelIcon(iconName: "timelapse", value: self.exifExposure) + LabelIcon(iconName: "calendar", value: self.exifCreatedDate?.toDate(.isoDateTimeSec)?.formatted()) } .padding(.bottom, 2) .foregroundColor(.lightGrayColor) @@ -127,6 +134,13 @@ struct StatusView: View { } } } + + private func setAttachment(_ attachmentData: AttachmentData) { + exifCamera = attachmentData.exifCamera + exifExposure = attachmentData.exifExposure + exifCreatedDate = attachmentData.exifCreatedDate + exifLens = attachmentData.exifLens + } } struct StatusView_Previews: PreviewProvider { diff --git a/Vernissage/Widgets/ImagesCarousel.swift b/Vernissage/Widgets/ImagesCarousel.swift index d384dae..8df6172 100644 --- a/Vernissage/Widgets/ImagesCarousel.swift +++ b/Vernissage/Widgets/ImagesCarousel.swift @@ -9,20 +9,30 @@ import SwiftUI struct ImagesCarousel: View { @State public var attachments: [AttachmentData] @State private var height: Double = 0.0 - + @State private var selectedAttachmentId = "" + + var onAttachmentChange: (_ attachmentData: AttachmentData) -> Void? + var body: some View { - TabView { - ForEach(attachments, id: \.self) { attachment in + TabView(selection: $selectedAttachmentId) { + ForEach(attachments, id: \.id) { attachment in if let image = UIImage(data: attachment.data) { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) + .tag(attachment.id) } } } .frame(height: CGFloat(self.height)) .tabViewStyle(PageTabViewStyle()) + .onChange(of: selectedAttachmentId, perform: { index in + if let attachment = attachments.first(where: { item in item.id == index }) { + onAttachmentChange(attachment) + } + }) .onAppear { + self.selectedAttachmentId = self.attachments.first?.id ?? "" self.calculateImageHeight() } } @@ -47,6 +57,8 @@ struct ImagesCarousel: View { struct ImagesCarousel_Previews: PreviewProvider { static var previews: some View { - ImagesCarousel(attachments: []) + ImagesCarousel(attachments: []) { attachmentData in + + } } } diff --git a/Vernissage/Widgets/LabelIcon.swift b/Vernissage/Widgets/LabelIcon.swift index 818fb7e..f7c5cbc 100644 --- a/Vernissage/Widgets/LabelIcon.swift +++ b/Vernissage/Widgets/LabelIcon.swift @@ -8,16 +8,20 @@ import SwiftUI struct LabelIcon: View { let iconName: String - let value: String + let value: String? var body: some View { - HStack(alignment: .center) { - Image(systemName: iconName) - .frame(width: 30, alignment: .leading) - Text(value) - .font(.footnote) + if let value { + HStack(alignment: .center) { + Image(systemName: iconName) + .frame(width: 30, alignment: .leading) + Text(value) + .font(.footnote) + } + .padding(.vertical, 2) + } else { + EmptyView() } - .padding(.vertical, 2) } }