Open AlexClarkQP opened 4 years ago
hey @AlexClarkQP i'm actually not sure ! have not yet explored it
https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit you can't find the answer here?
The interfacing with UIKit is fine. I think it's more an issue with how GiphyViewController presents itself. It appears to present itself in a tray over an existing view (rather than just being the entire view). So if you present it over your entire app it looks fine. However, if you present it in a sheet (the new preferred modal style in iOS 13) it looks odd. I've attached a screenshot. I was wondering if there's a setting to have GiphyViewController just show itself in a full screen view rather than in a tray. I've tried trayHeightMultiplier but it doesn't fill the screen.
GiphyViewController is still useless in SwiftUI. I've used GiphyGridController as a fallback, but it's quite limited in comparison.
For example, SCSDKBitmojiStickerPickerViewController from Snapchat has similar controls (search + stickers), but doesn't hijack whole screen and works well as UIViewControllerRepresentable inside SwiftUI.
Simple GiphyGridController as UIViewControllerRepresentable implementation (no search, just trending GIFs):
struct GiphySheet: UIViewControllerRepresentable {
typealias UIViewControllerType = GiphyGridController
let pickedImageCallback: (String) -> Void
func makeUIViewController(context: Context) -> GiphyGridController {
let giphyVC = GiphyGridController()
giphyVC.delegate = context.coordinator
giphyVC.content = GPHContent.trending(mediaType: .gif)
giphyVC.update()
return giphyVC
}
func updateUIViewController(_ uiViewController: GiphyGridController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, GPHGridDelegate {
var parent: GiphySheet
init(_ giphySheet: GiphySheet) {
self.parent = giphySheet
}
func contentDidUpdate(resultCount: Int) {
//
}
func didSelectMedia(media: GPHMedia, cell: UICollectionViewCell) {
parent.pickedImageCallback(media.url)
}
}
}
+1 on this! Am using SwiftUI, and this would be very helpful 👍
Just a note to @ealeksandrov and anyone who finds the info useful: contrary to the above, GiphyViewController is useful in SwiftUI. Here's how to get it working. I am wrapping it inside a UIVIewControllerRepresentable and presenting it atop a live camera view by using a ZStack. I'm placing the view atop a semi-transparent black background, and providing the top and bottom of the view with some padding. To make GiphyViewController enter the screen by filling the full height of its tray, use the static method call GiphyViewController.trayHeightMultiplier = 1.0
when initializing it (see below). This line prevents the issue shown above where it appears to fill just some of its tray's height. @AlexClarkQP - I think the last line was your issue.
It would be nice to have some extra SwiftUI support without having to wrap inside a UIViewControllerRepresentable, of course, but it's not much work to do it ourselves. It would also be nice, Giphy team, to have SwiftUI-friendly ways to render the returned media (GiphyYYImage). It's still a very useful controller to drop in, though. Thanks for providing it.
Here's a minimal example which accomplishes the above. The end of it contains an excerpt from a SwiftUI view which uses the UIViewControllerRepresentable. I haven't made the latter portion clean or re-usable yet, but it should get the idea across:
import Foundation
import SwiftUI
import UIKit
import GiphyUISDK
struct GiphyVCRepresentable : UIViewControllerRepresentable {
public typealias UIViewControllerType = GiphyViewController
// gif, width, height
var onSelectedGif: (GiphyYYImage, CGFloat, CGFloat) -> Void
var onShouldDismissGifPicker: () -> Void
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
public func makeUIViewController(context: UIViewControllerRepresentableContext<GiphyVCRepresentable>) -> GiphyViewController {
let gvc = GiphyViewController()
gvc.delegate = context.coordinator
//gvc.mediaTypeConfig = [] // TODO: can filter by media type
gvc.theme = CustomGiphyTheme()
GiphyViewController.trayHeightMultiplier = 1.0 // This causes the tray to start at the screen's full height
return gvc
}
public func updateUIViewController(_ giphyViewController: GiphyViewController, context: UIViewControllerRepresentableContext<GiphyVCRepresentable>) {
// TODO:
}
class Coordinator: NSObject, GiphyDelegate {
let parent: GiphyVCRepresentable
init(_ parent: GiphyVCRepresentable) {
self.parent = parent
}
func didSearch(for term: String) {
print("GiphyDelegate: the user made searched for: ", term)
}
func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) {
giphyViewController.dismiss(animated: true, completion: { [weak self] in
let url = media.url(rendition: giphyViewController.renditionType, fileType: .webp) ?? ""
GPHCache.shared.downloadAsset(url) { (image, error) in
DispatchQueue.main.async {
//let imageView = GiphyYYAnimatedImageView()
let width = CGFloat(media.images?.fixedWidth?.width ?? 0)
let height = CGFloat(media.images?.fixedWidth?.height ?? 0)
if let _giphyYYImage = image, let _coordinator = self {
_coordinator.parent.onSelectedGif(_giphyYYImage, width, height)
}
}
}
})
}
func didDismiss(controller: GiphyViewController?) {
self.parent.onShouldDismissGifPicker()
}
}
}
public class CustomGiphyTheme: GPHTheme {
public override init() {
super.init()
self.type = .darkBlur
}
public override var textFieldFont: UIFont? {
return UIFont(name: "Futura-Medium", size: 15)
}
public override var textColor: UIColor {
return .white
}
}
// Here's a usage example excerpted from the middle of a SwiftUI View, where the ZStack below is placed inside of another ZStack from that containing view, and sits on top of the other view's content in that ZStack.
// ... inside some SwiftUI View ...
@State private var isShowingGifPicker = false
// ... somewhere inside the body of that SwiftUI VIew ...
if self.isShowingGifPicker {
ZStack {
VStack(spacing: 0) {
// Most of the screen's background becomes a partially transparent
// black view. Tapping anywhere on it dismisses the Giphy view.
Rectangle()
.fill(Color.init(white: 0.0, opacity: 0.5))
.onTapGesture {
withAnimation {
self.isShowingGifPicker = false
}
// This little bit covers the bottom area between the GiphyVC and
// the remaining sliver near the screen's bottom safe area
Rectangle()
.fill(Color.init(white: (38.0/255.0)))
.frame(height: 100)
}
GiphyVCRepresentable(onSelectedGif: { _giphyYYImage, width, height in
// TODO: do something with _giphyYYImage for your app
withAnimation {
self.isShowingGifPicker = false
}
}, onShouldDismissGifPicker: {
withAnimation {
self.isShowingGifPicker = false
}
})
.padding(.bottom, 20.0)
.padding(.top, 90.0)
.transition(.move(edge: .bottom))
}
}
Thank you for sharing your solution @schazers! This is a viable option for full-screen popover (but I haven't fully tested it).
Unfortunately it won't fit some use cases, like showing GIFs picker below the input field, in place of keyboard:
As you can see - SCSDKBitmojiStickerPickerViewController
works fine, because it doesn't expect to handle whole screen interaction, it just works anywhere you place it. GiphyGridController
example also works fine, but lacks some features like search.
IMO, the best way of doing it is using Introspect:
.introspectViewController { controller in
guard presentingGiphy else { return }
let giphyController = GiphyViewController()
giphyController.delegate = myViewModel
controller.present(giphyController, animated: true, completion: nil)
}
Code doesn't look great, but at least this way GiphyViewController works properly
Still no better support for swiftui in 2022? Solutions from @schazers cause memory leak and 100% CPU usage.
// 2022 solution.
I wrapped GiphyViewController inside custom UIViewControllerRepresentable
First create GiphyView
struct GifPicker: UIViewControllerRepresentable {
@State var theme: GPHThemeType = .dark
var completion: ((String) -> Void)
var onShouldDismissGifPicker: () -> Void
func makeUIViewController(context: Context) -> GiphyViewController {
Giphy.configure(apiKey: "jdD6Mf0lensylI78b48ce701AkxfMglw")
let controller = GiphyViewController()
controller.swiftUIEnabled = true
controller.mediaTypeConfig = [.gifs, .stickers, .recents]
controller.delegate = context.coordinator
controller.navigationController?.isNavigationBarHidden = true
controller.navigationController?.setNavigationBarHidden(true, animated: false)
GiphyViewController.trayHeightMultiplier = 1.0
controller.theme = GPHTheme(type: theme)
return controller
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
func makeCoordinator() -> Coordinator {
return GifPicker.Coordinator(parent: self)
}
class Coordinator: NSObject, GiphyDelegate {
var parent: GifPicker
init(parent: GifPicker) {
self.parent = parent
}
func didDismiss(controller: GiphyViewController?) {
self.parent.onShouldDismissGifPicker()
}
func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) {
// retrieving url
let url = media.url(rendition: .fixedWidth, fileType: .gif)
DispatchQueue.main.async {
self.parent.completion(url ?? "")
}
}
}
}
Now create our custom halfSheet
private struct GifSheet<SheetView: View>: UIViewControllerRepresentable{
var sheetView: SheetView
@Binding var isPresented: Bool
let controller = UIViewController()
func makeUIViewController(context: Context) -> some UIViewController {
controller.view.backgroundColor = .clear
return controller
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
if isPresented {
let sheetController = GifCustomHostingController(rootView: sheetView)
sheetController.view.backgroundColor = .clear
uiViewController.present(sheetController, animated: true) {
DispatchQueue.main.async {
self.isPresented.toggle()
}
}
}
}
}
class GifCustomHostingController<Content: View>: UIHostingController<Content> {
override func viewDidLoad() {
if let presentationController = presentationController as? UISheetPresentationController {
presentationController.detents = [
.medium(),
.large()
]
presentationController.prefersGrabberVisible = false
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.invalidateIntrinsicContentSize()
}
}
extension View {
func gifSheet<SheetView: View>(isPresented: Binding<Bool>, @ViewBuilder sheetView: @escaping ()-> SheetView )-> some View {
return self
.background(
GifSheet(sheetView: sheetView(), isPresented: isPresented)
)
}
}
Usage in SwiftUI
.gifSheet(isPresented: $chatLogViewModel.isGifPickerVisible) {
GifPicker(theme: colorScheme == .light ? .lightBlur : .darkBlur) { url in
chatLogViewModel.textMessage = "/img \(url)"
chatLogViewModel.sendMessage()
chatLogViewModel.isGifPickerVisible = false
} onShouldDismissGifPicker: {
chatLogViewModel.isGifPickerVisible = false
}
.ignoresSafeArea()
}
Result
You have full functionality with medium or full size sheet size and also you can use blur theme because our custom sheet wrapper does not have any background.
Do you have any documentation for SwiftUI integration? Specifically I’d like to show the GiphyViewController in sheet but it’s showing a sheet within a sheet which looks a little awkward.
Here's the code I have so far:
and in my view: