Hi I am struggling with getting football data api from rapidapi to work in swift ui . here is the code below
The errors is get are "self.team = decodedTeams" Cannot find 'self' in scope"
and in my content view i get for " $network.getTeams"
Value of type 'EnvironmentObject.Wrapper' has no dynamic member 'getTeams' using key path from root type 'Network'
I have set out what i have in 2 pages of my swiftui code below
any help would be appreciated, I am really struggling with this one
// Network.swift
// Football Scores
//
//
import Foundation
class Network: ObservableObject {
#Published var teams: [Team] = []
}
func getTeams() {
let headers = [
"X-RapidAPI-Key": "MY API KEY",
"X-RapidAPI-Host": "api-football-v1.p.rapidapi.com"
]`
let request = NSMutableURLRequest(url: NSURL(string: "https://api-football-v1.p.rapidapi.com/v3/standings?season=2022&league=39")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
DispatchQueue.main.async {
do {
let decodedTeams = try JSONDecoder().decode([Team].self, from: data)
self.team = decodedTeams
} catch let error {
print("Error decoding: ", error)
}
}
}
})
dataTask.resume()
}
and
//
// Team.swift
// Football Scores
//
//
import Foundation
struct Team: Identifiable, Decodable {
var id: Int
var name: String
var logo: String
var points: String
var goaldif: String
}
and
// Football_ScoresApp.swift
// Football Scores
//
import SwiftUI
import Foundation
#main
struct Football_ScoresApp: App {
var network = Network()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(network)
}
}
}
and
import SwiftUI
import CoreData
struct ContentView: View {
#EnvironmentObject var network: Network
var body: some View {
ScrollView {
Text("All teams")
.font(.title).bold()
}
.onAppear {
network.getTeams()
}
VStack(alignment: .leading) {
ForEach(network.teams) { team in
HStack(alignment:.top) {
Text("\(team.id)")
VStack(alignment: .leading) {
Text(team.name)
.bold()
}
}
.frame(width: 300, alignment: .leading)
.padding()
.background(Color(#colorLiteral(red: 0.6667672396, green: 0.7527905703, blue: 1, alpha: 0.2662717301)))
.cornerRadius(20)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Network())
}
}
To display the teams from your api request, first you need to create a set of struct models that
represent the json data that the server is sending. From the server response, you need to
extract the teams information you want to display. Here is my code
that shows how to do it, works well for me, pay attention to the details:
struct ContentView: View {
#StateObject var network = Network() // <-- for testing
var body: some View {
List {
VStack(alignment: .leading) {
ForEach(network.teams) { team in
HStack(alignment:.top) {
Text("\(team.id)")
Text(team.name).bold()
}
.frame(width: 300, alignment: .leading)
.padding()
.background(Color(#colorLiteral(red: 0.6667672396, green: 0.7527905703, blue: 1, alpha: 0.2662717301)))
.cornerRadius(20)
}
}
}
.onAppear {
network.getTeams()
}
}
}
class Network: ObservableObject {
#Published var teams: [Team] = []
func getTeams() {
let token = "your-key" // <--- here your api key
guard let url = URL(string: "https://api-football-v1.p.rapidapi.com/v3/standings?season=2022&league=39") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "X-RapidAPI-Key")
request.setValue("api-football-v1.p.rapidapi.com", forHTTPHeaderField: "X-RapidAPI-Host")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
do {
let results = try JSONDecoder().decode(FootyResponse.self, from: data)
// extract just the teams
for response in results.response {
for stand in response.league.standings {
for league in stand {
self.teams.append(league.team)
}
}
}
} catch {
print(error) // <-- here important
}
}
}.resume()
}
}
// MARK: - FootyResponse
struct FootyResponse: Codable {
let welcomeGet: String
let parameters: Parameters
let errors: [String]
let results: Int
let paging: Paging
let response: [Response]
enum CodingKeys: String, CodingKey {
case welcomeGet = "get"
case parameters, errors, results, paging, response
}
}
// MARK: - Paging
struct Paging: Codable {
let current, total: Int
}
// MARK: - Parameters
struct Parameters: Codable {
let league, season: String
}
// MARK: - Response
struct Response: Codable {
let league: League
}
// MARK: - League
struct League: Codable {
let id: Int
let name: String
let country: String
let logo: String
let flag: String
let season: Int
let standings: [[Standing]]
}
// MARK: - Standing
struct Standing: Codable {
let rank: Int
let team: Team
let points, goalsDiff: Int
let group: String
let form: String
let status: String
let standingDescription: String?
let all, home, away: All
let update: String // <-- Date
enum CodingKeys: String, CodingKey {
case rank, team, points, goalsDiff, group, form, status
case standingDescription = "description"
case all, home, away, update
}
}
// MARK: - All
struct All: Codable {
let played, win, draw, lose: Int
let goals: Goals
}
// MARK: - Goals
struct Goals: Codable {
let goalsFor, against: Int
enum CodingKeys: String, CodingKey {
case goalsFor = "for"
case against
}
}
// MARK: - Team
struct Team: Identifiable, Codable {
let id: Int
let name: String
let logo: String
}
EDIT-1:
to get the points of each team, you need to use the Standing struct. Here is an example code to do that.
struct ContentView: View {
#StateObject var network = Network()
var body: some View {
List {
VStack(alignment: .leading) {
ForEach(network.stand) { stand in // <-- here
HStack(alignment:.top) {
Text(stand.team.name).bold() // <-- here
Text("\(stand.points) points") // <-- here
}
.frame(width: 300, alignment: .leading)
.padding()
.background(Color(#colorLiteral(red: 0.6667672396, green: 0.7527905703, blue: 1, alpha: 0.2662717301)))
.cornerRadius(20)
}
}
}
.onAppear {
network.getTeams()
}
}
}
class Network: ObservableObject {
#Published var teams: [Team] = []
#Published var stand: [Standing] = [] // <-- here
func getTeams() {
let token = "your-key" // <--- here your api key
guard let url = URL(string: "https://api-football-v1.p.rapidapi.com/v3/standings?season=2022&league=39") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "X-RapidAPI-Key")
request.setValue("api-football-v1.p.rapidapi.com", forHTTPHeaderField: "X-RapidAPI-Host")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return }
DispatchQueue.main.async {
do {
let results = try JSONDecoder().decode(FootyResponse.self, from: data)
// extract the teams and the standings
results.response.forEach{ response in
response.league.standings.forEach{ stand in
self.stand = stand // <--- here
stand.forEach{ league in
self.teams.append(league.team)
}
}
}
} catch {
print(error) // <-- here important
}
}
}.resume()
}
}
// MARK: - Standing
struct Standing: Identifiable, Codable {
let id = UUID() // <-- here
let rank: Int
let team: Team
let points, goalsDiff: Int
let group: String
let form: String
let status: String
let standingDescription: String?
let all, home, away: All
let update: String // <-- Date
enum CodingKeys: String, CodingKey {
case rank, team, points, goalsDiff, group, form, status
case standingDescription = "description"
case all, home, away, update
}
}
I want to let the user to be able to share a location but I don't know how to show UIActivityViewController in SwiftUI.
The basic implementation of UIActivityViewController in SwiftUI is
import UIKit
import SwiftUI
struct ActivityViewController: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}
}
And here is how to use it.
struct MyView: View {
#State private var isSharePresented: Bool = false
var body: some View {
Button("Share app") {
self.isSharePresented = true
}
.sheet(isPresented: $isSharePresented, onDismiss: {
print("Dismiss")
}, content: {
ActivityViewController(activityItems: [URL(string: "https://www.apple.com")!])
})
}
}
Based on Tikhonov's, the following code added a fix to make sure the activity sheet is dismissed properly (if not subsequently the sheet will not be presented).
struct ActivityViewController: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
#Environment(\.presentationMode) var presentationMode
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
self.presentationMode.wrappedValue.dismiss()
}
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}
}
It's a one time thing currently. .sheet will show it as a sheet, but bringing it up again from the same view will have stale data. Those subsequent shows of the sheet will also not trigger any completion handlers. Basically, makeUIViewController is called only once which is the only way to get the data to share into the UIActivityViewController. updateUIViewController has no way to update the data in your activityItems or reset the controller because those are not visible from an instance of UIActivityViewController.
Note that it doesn't work with UIActivityItemSource or UIActivityItemProvider either. Using those is even worse. The placeholder value doesn't show.
I hacked around some more and decided that maybe the problem with my solution was a sheet that was presenting another sheet, and when one went away then the other stayed.
This indirect way of having a ViewController do the presentation when it appears made it work for me.
class UIActivityViewControllerHost: UIViewController {
var message = ""
var completionWithItemsHandler: UIActivityViewController.CompletionWithItemsHandler? = nil
override func viewDidAppear(_ animated: Bool) {
share()
}
func share() {
// set up activity view controller
let textToShare = [ message ]
let activityViewController = UIActivityViewController(activityItems: textToShare, applicationActivities: nil)
activityViewController.completionWithItemsHandler = completionWithItemsHandler
activityViewController.popoverPresentationController?.sourceView = self.view // so that iPads won't crash
// present the view controller
self.present(activityViewController, animated: true, completion: nil)
}
}
struct ActivityViewController: UIViewControllerRepresentable {
#Binding var text: String
#Binding var showing: Bool
func makeUIViewController(context: Context) -> UIActivityViewControllerHost {
// Create the host and setup the conditions for destroying it
let result = UIActivityViewControllerHost()
result.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
// To indicate to the hosting view this should be "dismissed"
self.showing = false
}
return result
}
func updateUIViewController(_ uiViewController: UIActivityViewControllerHost, context: Context) {
// Update the text in the hosting controller
uiViewController.message = text
}
}
struct ContentView: View {
#State private var showSheet = false
#State private var message = "a message"
var body: some View {
VStack {
TextField("what to share", text: $message)
Button("Hello World") {
self.showSheet = true
}
if showSheet {
ActivityViewController(text: $message, showing: $showSheet)
.frame(width: 0, height: 0)
}
Spacer()
}
.padding()
}
}
May be its not recommended, but it is really easy and two line of code (was for iPhone) to share text
Button(action: {
let shareActivity = UIActivityViewController(activityItems: ["Text To Share"], applicationActivities: nil)
if let vc = UIApplication.shared.windows.first?.rootViewController{
shareActivity.popoverPresentationController?.sourceView = vc.view
//Setup share activity position on screen on bottom center
shareActivity.popoverPresentationController?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height, width: 0, height: 0)
shareActivity.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
vc.present(shareActivity, animated: true, completion: nil)
}
}) {
Text("Share")
}
EDIT: Now works fine on iPad (tested on iPad Pro (9.7 -inch) Simulator)
I want to suggest another implementation that looks more native (half screen height without white gap bottom).
import SwiftUI
struct ActivityView: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> ActivityViewWrapper {
ActivityViewWrapper(activityItems: activityItems, applicationActivities: applicationActivities, isPresented: $isPresented)
}
func updateUIViewController(_ uiViewController: ActivityViewWrapper, context: Context) {
uiViewController.isPresented = $isPresented
uiViewController.updateState()
}
}
class ActivityViewWrapper: UIViewController {
var activityItems: [Any]
var applicationActivities: [UIActivity]?
var isPresented: Binding<Bool>
init(activityItems: [Any], applicationActivities: [UIActivity]? = nil, isPresented: Binding<Bool>) {
self.activityItems = activityItems
self.applicationActivities = applicationActivities
self.isPresented = isPresented
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
updateState()
}
fileprivate func updateState() {
guard parent != nil else {return}
let isActivityPresented = presentedViewController != nil
if isActivityPresented != isPresented.wrappedValue {
if !isActivityPresented {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
controller.completionWithItemsHandler = { (activityType, completed, _, _) in
self.isPresented.wrappedValue = false
}
present(controller, animated: true, completion: nil)
}
else {
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
}
}
struct ActivityViewTest: View {
#State private var isActivityPresented = false
var body: some View {
Button("Preset") {
self.isActivityPresented = true
}.background(ActivityView(activityItems: ["Hello, World"], isPresented: $isActivityPresented))
}
}
struct ActivityView_Previews: PreviewProvider {
static var previews: some View {
ActivityViewTest()
}
}
I got it to work now using
.sheet(isPresented: $isSheet, content: { ActivityViewController() }
.presentation is deprecated
It takes up the whole screen iOS 13 style.
If you need more granular control over the content displayed in the share sheet, you will probably end implementing UIActivityItemSource.
I tried using Mike W.'s code above but it didn't work at first (the delegate functions weren't being called). The fix was changing the initialisation of UIActivityController within makeUIViewController as follows, now passing [context.coordinator] as activityItems:
let controller = UIActivityViewController(activityItems: [context.coordinator], applicationActivities: applicationActivities)
Also, I wanted to be able to set the icon, title and subtitle in the share sheet, so I have implemented func activityViewControllerLinkMetadata in the Coordinator class.
The following is the complete expanded version of Mike W.'s answer. Please note you will need to add import LinkPresentation to the code.
ActivityViewController
import SwiftUI
import LinkPresentation
struct ActivityViewController: UIViewControllerRepresentable {
var shareable : ActivityShareable?
var applicationActivities: [UIActivity]? = nil
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: [context.coordinator], applicationActivities: applicationActivities)
controller.modalPresentationStyle = .automatic
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}
func makeCoordinator() -> ActivityViewController.Coordinator {
Coordinator(self.shareable)
}
class Coordinator : NSObject, UIActivityItemSource {
private let shareable : ActivityShareable?
init(_ shareable: ActivityShareable?) {
self.shareable = shareable
super.init()
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
guard let share = self.shareable else { return "" }
return share.getPlaceholderItem()
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
guard let share = self.shareable else { return "" }
return share.itemForActivityType(activityType: activityType)
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
guard let share = self.shareable else { return "" }
return share.subjectForActivityType(activityType: activityType)
}
func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
guard let share = self.shareable else { return nil }
let metadata = LPLinkMetadata()
// share sheet preview title
metadata.title = share.shareSheetTitle()
// share sheet preview subtitle
metadata.originalURL = URL(fileURLWithPath: share.shareSheetSubTitle())
// share sheet preview icon
if let image = share.shareSheetIcon() {
let imageProvider = NSItemProvider(object: image)
metadata.imageProvider = imageProvider
metadata.iconrovider = imageProvider
}
return metadata
}
}
}
Protocol ActivityShareable
protocol ActivityShareable {
func getPlaceholderItem() -> Any
func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any?
func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String
func shareSheetTitle() -> String
func shareSheetSubTitle() -> String
func shareSheetIcon() -> UIImage?
}
In my case I am using the share sheet to export text, so I created a struct called ActivityShareableText that conforms to ActivityShareable:
struct ActivityShareableText: ActivityShareable {
let text: String
let title: String
let subTitle: String
let icon: UIImage?
func getPlaceholderItem() -> Any {
return text
}
func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any? {
return text
}
func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String {
return "\(title): \(subTitle)"
}
func shareSheetTitle() -> String {
return title
}
func shareSheetSubTitle() -> String {
return subTitle
}
func shareSheetIcon() -> UIImage? {
return icon
}
}
In my code, I call the share sheet as follows:
ActivityViewController(shareable: ActivityShareableText(
text: myShareText(),
title: myShareTitle(),
subTitle: myShareSubTitle(),
icon: UIImage(named: "myAppLogo")
))
FWIW - Providing a slight improvement to answers that includes an implementation for UIActivityItemSource. Code simplified for brevity, specifically around the default return on itemForActivityType and activityViewControllerPlaceholderItem, they must always return the same type.
ActivityViewController
struct ActivityViewController: UIViewControllerRepresentable {
var activityItems: [Any]
var shareable : ActivityShareable?
var applicationActivities: [UIActivity]? = nil
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
controller.modalPresentationStyle = .automatic
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}
func makeCoordinator() -> ActivityViewController.Coordinator {
Coordinator(self.shareable)
}
class Coordinator : NSObject, UIActivityItemSource {
private let shareable : ActivityShareable?
init(_ shareable: ActivityShareable?) {
self.shareable = shareable
super.init()
}
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
guard let share = self.shareable else { return "" }
return share.getPlaceholderItem()
}
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
guard let share = self.shareable else { return "" }
return share.itemForActivityType(activityType: activityType)
}
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
guard let share = self.shareable else { return "" }
return share.subjectForActivityType(activityType: activityType)
}
}
}
ActivityShareable
protocol ActivityShareable {
func getPlaceholderItem() -> Any
func itemForActivityType(activityType: UIActivity.ActivityType?) -> Any?
/// Optional
func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String
}
extension ActivityShareable {
func subjectForActivityType(activityType: UIActivity.ActivityType?) -> String {
return ""
}
}
You could pass in the reference for ActivityViewController or the underlying UIActivityViewController but that feels unnecessary.
You could try porting UIActivityViewController to SwiftUI as follows:
struct ActivityView: UIViewControllerRepresentable {
let activityItems: [Any]
let applicationActivities: [UIActivity]?
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
return UIActivityViewController(activityItems: activityItems,
applicationActivities: applicationActivities)
}
func updateUIViewController(_ uiViewController: UIActivityViewController,
context: UIViewControllerRepresentableContext<ActivityView>) {
}
}
but the app will crash when you try to display it.
I tried: Modal, Popover and NavigationButton.
To test it:
struct ContentView: View {
var body: some Body {
EmptyView
.presentation(Modal(ActivityView()))
}
}
It doesn't seem to be usable from SwiftUI.
Extending upon #Shimanski Artem solution. I think we can write that code more concise. So I basically embed my ActivityViewController in a blank UIViewController and present it from there. This way we don't get the full 'overlay' sheet and you get the native behaviour. Just like #Shimanski Artem did.
struct UIKitActivityView: UIViewControllerRepresentable {
#Binding var isPresented: Bool
let data: [Any]
func makeUIViewController(context: Context) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
let activityViewController = UIActivityViewController(
activityItems: data,
applicationActivities: nil
)
if isPresented && uiViewController.presentedViewController == nil {
uiViewController.present(activityViewController, animated: true)
}
activityViewController.completionWithItemsHandler = { (_, _, _, _) in
isPresented = false
}
}
}
Usage
struct ActivityViewTest: View {
#State private var isActivityPresented = false
var body: some View {
Button("Preset") {
self.isActivityPresented = true
}
.background(
UIKitActivityView(
isPresented: $viewModel.showShareSheet,
data: ["String"]
)
)
}
}
Example using SwiftUIX
There is a library called SwiftUIX that already has a wrapper for UIActivityViewController. See quick skeleton of how to present it via .sheet() which should be placed somewhere in the var body: some View {}.
import SwiftUIX
/// ...
#State private var showSocialsInviteShareSheet: Bool = false
// ...
.sheet(isPresented: $showSocialsInviteShareSheet, onDismiss: {
print("Dismiss")
}, content: {
AppActivityView(activityItems: [URL(string: "https://www.apple.com")!])
})
Suggest another way to solve it 🤔
You can create the Empty View Controller to present the sheet
struct ShareSheet: UIViewControllerRepresentable {
// To setup the share sheet
struct Config {
let activityItems: [Any]
var applicationActivities: [UIActivity]?
var excludedActivityTypes: [UIActivity.ActivityType]?
}
// Result object
struct Result {
let error: Error?
let activityType: UIActivity.ActivityType?
let completed: Bool
let returnedItems: [Any]?
}
#Binding var isPresented: Bool
private var handler: ((Result) -> Void)?
private let shareSheet: UIActivityViewController
init(
isPresented: Binding<Bool>,
config: Config,
onEnd: ((Result) -> Void)? = nil
) {
self._isPresented = isPresented
shareSheet = UIActivityViewController(
activityItems: config.activityItems,
applicationActivities: config.applicationActivities
)
shareSheet.excludedActivityTypes = config.excludedActivityTypes
shareSheet.completionWithItemsHandler = { activityType, completed, returnedItems, error in
onEnd?(
.init(
error: error,
activityType: activityType,
completed: completed,
returnedItems: returnedItems
)
)
// Set isPresented to false after complete
isPresented.wrappedValue = false
}
}
func makeUIViewController(context: Context) -> UIViewController {
UIViewController()
}
func updateUIViewController(
_ uiViewController: UIViewController,
context: Context
) {
if isPresented, shareSheet.view.window == nil {
uiViewController.present(shareSheet, animated: true, completion: nil)
} else if !isPresented, shareSheet.view.window != nil {
shareSheet.dismiss(animated: true)
}
}
}
You can also create the operator in the view extension
extension View {
func shareSheet(
isPresented: Binding<Bool>,
config: ShareSheet.Config,
onEnd: ((ShareSheet.Result) -> Void)? = nil
) -> some View {
self.background(
ShareSheet(isPresented: isPresented, config: config, onEnd: onEnd)
)
}
}
Thanks for the helpful answers in this thread.
I tried to solve the stale data problem. The issue from not not implementing updateUIViewController in UIViewControllerRepresentable. SwiftUI calls makeUIViewController only once to create the view controller. The method updateUIViewController is responsible to make changes to view controller based on changes of the SwiftUI view.
As UIActivityViewController does not allow to change activityItems and applicationActivities, I used a wrapper view controller. UIViewControllerRepresentable will update the wrapper and the wrapper will create a new UIActivityViewController as needed to perform the update.
Below my code to implement a "share" button in my application. The code is tested on iOS 13.4 beta, which has fixed several SwiftUI bugs - not sure if it works on earlier releases.
struct Share: View {
var document: ReaderDocument // UIDocument subclass
#State var showShareSheet = false
var body: some View {
Button(action: {
self.document.save(to: self.document.fileURL, for: .forOverwriting) { success in
self.showShareSheet = true
}
}) {
Image(systemName: "square.and.arrow.up")
}.popover(isPresented: $showShareSheet) {
ActivityViewController(activityItems: [ self.document.text, self.document.fileURL,
UIPrintInfo.printInfo(), self.printFormatter ])
.frame(minWidth: 320, minHeight: 500) // necessary for iPad
}
}
var printFormatter: UIPrintFormatter {
let fontNum = Preferences.shared.readerFontSize.value
let fontSize = ReaderController.readerFontSizes[fontNum < ReaderController.readerFontSizes.count ? fontNum : 1]
let printFormatter = UISimpleTextPrintFormatter(text: self.document.text)
printFormatter.startPage = 0
printFormatter.perPageContentInsets = UIEdgeInsets(top: 72, left: 72, bottom: 72, right: 72)
return printFormatter
}
}
struct ActivityViewController: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
#Environment(\.presentationMode) var presentationMode
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>)
-> WrappedViewController<UIActivityViewController> {
let controller = WrappedViewController(wrappedController: activityController)
return controller
}
func updateUIViewController(_ uiViewController: WrappedViewController<UIActivityViewController>,
context: UIViewControllerRepresentableContext<ActivityViewController>) {
uiViewController.wrappedController = activityController
}
private var activityController: UIActivityViewController {
let avc = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
avc.completionWithItemsHandler = { (_, _, _, _) in
self.presentationMode.wrappedValue.dismiss()
}
return avc
}
}
class WrappedViewController<Controller: UIViewController>: UIViewController {
var wrappedController: Controller {
didSet {
if (wrappedController != oldValue) {
oldValue.removeFromParent()
oldValue.view.removeFromSuperview()
addChild(wrappedController)
view.addSubview(wrappedController.view)
wrappedController.view.frame = view.bounds
}
}
}
init(wrappedController: Controller) {
self.wrappedController = wrappedController
super.init(nibName: nil, bundle: nil)
addChild(wrappedController)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
view.addSubview(wrappedController.view)
wrappedController.view.frame = view.bounds
}
}
Just use introspect. Then you can easily code something like this:
YourView().introspectViewController { controller in
guard let items = viewModel.inviteLinkParams, viewModel.isSharePresented else { return }
let activity = UIActivityViewController(activityItems: items, applicationActivities: nil)
controller.present(activity, animated: true, completion: nil)
}
Swift 5 / SwiftUI Native
Simple, with completion call-back and native SwiftUI #Binding
import SwiftUI
struct ShareSheet: UIViewControllerRepresentable {
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
#Binding var isPresented: Bool
#Binding var activityItem: String
let applicationActivities: [UIActivity]? = nil
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
let callback: Callback?
func makeUIViewController(context: Context) -> UIActivityViewController {
let controller = UIActivityViewController(
activityItems: [activityItem],
applicationActivities: applicationActivities)
controller.excludedActivityTypes = excludedActivityTypes
controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in
callback?(activityType, completed, returnedItems, error)
isPresented = false
}
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
}
}
example usage:
ShareSheet(isPresented: $showShareSheet, activityItem: $sharingUrl, callback: { activityType, completed, returnedItems, error in
print("ShareSheet dismissed: \(activityType) \(completed) \(returnedItems) \(error)")
})
Solved
I am trying to populate Table View Cell from sample JSON in this link https://api.myjson.com/bins/13axs1, but getting error in cellForRowAt function when trying to assign textLabel.
type '[City]?' has no subscript members
JSON
[
{
"name": "New York",
"information": "Later will be added",
"imageUrl": "later"
},
{
"name": "New York",
"information": "Later will be added",
"imageUrl": "later"
}
]
The code in ViewController:
struct City: Codable {
let name: String?
let information: String?
let imageUrl: String?
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var citiesList: [City] = []
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "https://api.myjson.com/bins/13axs1"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let cities = try JSONDecoder().decode([City].self, from: data)
if cities != nil{
self.citiesList = cities
}else {
print("nothing to display")
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return citiesList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = citiesList[indexPath.row].name // here I am getting error type '[City]? has no subscript members
return cell
}
}
Sorry, but I don't know why not all of the code shows clearly after I added it, but hope it will be clear for you guys to understand, thanks a lot.
You are missing the reload method (reloadData()):
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let cities = try JSONDecoder().decode([City].self, from: data)
self.citiesList = cities
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error serializing json:", error)
}
}.resume()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.deque...
cell.textLabel?.text = citiesList?[indexPath.row].name
return cell
}