Skip to content
This repository has been archived by the owner on Sep 24, 2024. It is now read-only.

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadowTourist committed Aug 27, 2021
1 parent 4bcc0c3 commit e0ceae7
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 20 deletions.
7 changes: 7 additions & 0 deletions CAITestApp/CAITestApp/MockData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ struct MockData {
// image (bot)
arr.append(CAIResponseMessageData(imageName: "https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2019-mustang-shelby-gt350-101-1528733363.jpg?crop=0.817xw:1.00xh;0.149xw,0&resize=640:*"))

// text message (user)
arr.append(CAIResponseMessageData(text: "give me some pics", false))

// image (bot)
arr.append(CAIResponseMessageData(imageName: "https://via.placeholder.com/800x100.jpg"))
arr.append(CAIResponseMessageData(imageName: "https://via.placeholder.com/100x800.jpg"))

// text message (user)
arr.append(CAIResponseMessageData(text: "Please show me a gif", false))

Expand Down
8 changes: 2 additions & 6 deletions Sources/SAPCAI/UI/Common/SwiftUI/CarouselImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@ struct CarouselImageView: View {
var body: some View {
Group {
if let mediaItem = media, let sourceUrl = mediaItem.sourceUrl {
WebImage(url: sourceUrl)
.placeholder {
mediaItem.placeholder
}
.resizable()
.aspectRatio(contentMode: .fill)
ImageViewWrapper(url: sourceUrl)
.scaledToFill()
.frame(width: self.itemWidth, height: self.vSizeClass == .regular ? 180 : 80)
.clipped()
} else {
Expand Down
21 changes: 13 additions & 8 deletions Sources/SAPCAI/UI/Common/SwiftUI/ImageUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,23 @@ struct ImageUIView: View {
self.fallback = fallback
}

var boundingBox: BoundingBox {
BoundingBox(minWidth: 44,
minHeight: 44,
maxWidth: self.hSizeClass == .regular ? 480 : self.geometry.size.width * 0.75,
maxHeight: self.vSizeClass == .regular ? 400 : 240)
}

@State var imgSize: CGSize = .zero
// :nodoc:
var body: some View {
Group {
if let sourceUrl = media?.sourceUrl {
WebImage(url: sourceUrl)
.resizable()
.aspectRatio(contentMode: .fill)
.clipped()
.frame(minWidth: 44,
maxWidth: self.hSizeClass == .regular ? 480 : self.geometry.size.width * 0.75,
minHeight: 44,
maxHeight: self.vSizeClass == .regular ? 400 : 240)
ImageViewWrapper(url: sourceUrl)
.sizeInto(box: boundingBox)
.scaledToFill()
.frame(width: imgSize.width, height: imgSize.height)
.onPreferenceChange(ImageSizeInfoPrefKey.self) { self.imgSize = $0 }
} else {
fallback
}
Expand Down
8 changes: 2 additions & 6 deletions Sources/SAPCAI/UI/Common/SwiftUI/ImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ struct ImageView: View {
if imageUrl.absoluteString.range(of: "sap-icon") != nil {
IconImageView(iconUrl: imageUrl.absoluteString, iconSize: CGSize(width: 50, height: 50))
} else {
WebImage(url: imageUrl, options: .delayPlaceholder)
.placeholder {
Image(systemName: "photo")
}
.resizable()
.aspectRatio(contentMode: .fit)
ImageViewWrapper(url: imageUrl)
.scaledToFit()
.cornerRadius(8)
}
}
Expand Down
198 changes: 198 additions & 0 deletions Sources/SAPCAI/UI/Common/SwiftUI/ImageViewWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import SDWebImage
import SwiftUI

class ImageManager: ObservableObject {
enum ImageState {
case initial
case error
case completed(image: UIImage)
}

@Published var state: ImageState = .initial
@Published var contentMode: UIView.ContentMode = .scaleAspectFit
@Published var box: BoundingBox? = nil

var displaySize: CGSize {
switch state {
case .completed(image: let image):
if let box = box, image.size != .zero {
return applySizeConstraints(from: image.size, to: box)
} else {
return .zero
}
case .initial, .error:
return .zero
}
}
}

struct ImageViewWrapper: View {
@ObservedObject var imageManager: ImageManager

let url: URL?
let imageView: ImageViewRepresentable

init(url: URL?) {
self.init(url: url, manager: ImageManager())
}

private init(url: URL?, manager: ImageManager) {
self.url = url
self.imageManager = manager
self.imageView = ImageViewRepresentable(url: url, imageManager: manager)
}

var body: some View {
switch self.imageManager.state {
case .initial, .error:
imageView
case .completed(image: _):
if imageManager.box != nil {
imageView
.preference(key: ImageSizeInfoPrefKey.self, value: imageManager.displaySize)
} else {
imageView
}
}
}

func scaledToFit() -> Self {
self.imageManager.contentMode = .scaleAspectFit
return self
}

func scaledToFill() -> Self {
self.imageManager.contentMode = .scaleAspectFill
return self
}

func sizeInto(box: BoundingBox) -> Self {
self.imageManager.box = box
return self
}

func placeholder(_ placeholder: Image) -> Self {
// TODO: add placeholder
self
}
}

struct ImageViewRepresentable: UIViewRepresentable {
var url: URL?
@ObservedObject var imageManager: ImageManager

func makeUIView(context: Self.Context) -> UIImageViewWrapper {
let wrapper = UIImageViewWrapper()
wrapper.imageView.sd_setImage(with: self.url,
placeholderImage: nil) { image, error, _, _ in
if error == nil, let image = image {
imageManager.state = .completed(image: image)
} else {
imageManager.state = .error
}
}

return wrapper
}

func updateUIView(_ uiView: UIImageViewWrapper, context: UIViewRepresentableContext<ImageViewRepresentable>) {
uiView.imageView.contentMode = self.imageManager.contentMode
}
}

class UIImageViewWrapper: UIView {
lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
imageView.setContentHuggingPriority(.defaultLow, for: .vertical)
imageView.setContentHuggingPriority(.defaultLow, for: .horizontal)
return imageView
}()

override init(frame: CGRect) {
super.init(frame: frame)
addSubview(self.imageView)
self.imageView.bindFrameToSuperviewBounds()
}

required init?(coder: NSCoder) {
super.init(coder: coder)
addSubview(self.imageView)
self.imageView.bindFrameToSuperviewBounds()
}
}

extension UIView {
func bindFrameToSuperviewBounds() {
guard let superview = self.superview else {
print("Error! `superview` was nil – call `addSubview(view: UIView)` before calling `bindFrameToSuperviewBounds()` to fix this.")
return
}
self.translatesAutoresizingMaskIntoConstraints = false
self.topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
self.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0).isActive = true
self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: 0).isActive = true
}
}

extension ImageManager {
func applySizeConstraints(from size: CGSize, to box: BoundingBox) -> CGSize {
let minWidth = box.minWidth
let minHeight = box.minHeight
let maxWidth = box.maxWidth
let maxHeight = box.maxHeight

guard size.width > 0, size.height > 0 else {
assertionFailure("size is zero")
return size
}

// all sides in range
if size.width >= minWidth, size.width <= maxWidth,
size.height >= minHeight, size.height <= maxHeight
{
return size
}
// only width in range
else if size.width >= minWidth, size.width <= maxWidth {
if size.height < minHeight {
return CGSize(width: min(maxWidth, size.width * (minHeight / size.height)), height: minHeight)
} else if size.height > maxHeight {
return CGSize(width: max(minWidth, size.width * (maxHeight / size.height)), height: maxHeight)
}
}
// only height in range
else if size.height >= minHeight, size.height <= maxHeight {
if size.width < minWidth {
return CGSize(width: minWidth, height: min(maxHeight, size.height * (minWidth / size.width)))
} else if size.width > maxWidth {
return CGSize(width: maxWidth, height: max(minHeight, size.height * (maxWidth / size.width)))
}
}

// then no sides are in range

else if size.width < minWidth, size.height > maxHeight {
return CGSize(width: minWidth, height: maxHeight)
} else if size.width > maxWidth, size.height < minHeight {
return CGSize(width: maxWidth, height: minHeight)
}

// probably the most common use case, both sides are too large
else if size.width > maxWidth, size.height > maxHeight {
let r = max(size.width / maxWidth, size.height / maxHeight)
return CGSize(width: size.width / r, height: size.height / r)
}

// both sides are too small
else if size.width < minWidth, size.height < minHeight {
let r = min(size.width / minWidth, size.height / minHeight)
return CGSize(width: size.width / r, height: size.height / r)
}

assertionFailure("it should never end up here, check conditions.")
return size
}
}

0 comments on commit e0ceae7

Please sign in to comment.