In my code the drag&drop function works fine with URL objects. But the exact same code for String objects does not. I have tried with countless casts and loadItem instead of loadObject ... no luck so far.
Can anyone help me here? It would be very much appreciated.
code for URL objects – Working
.onDrag {
return NSItemProvider(object: item.url as NSURL)
}
.onDrop(of: [UTType.url], isTargeted: $isDropping) { providers in
_ = providers.first?.loadObject(ofClass: URL.self) { url, error in
if let error = error { print(error.localizedDescription) }
if let url = url {
DispatchQueue.main.async {
self.array2.insert(Item(title: "new", url: url), at: 0)
}
}
}
return true
}
the same with String does not:
.onDrag {
return NSItemProvider(object: item.title as NSString)
}
.onDrop(of: [UTType.text], isTargeted: $isDropping) { providers in
_ = providers.first?.loadObject(ofClass: String.self) { string, error in
if let error = error { print(error.localizedDescription) }
if let string = string {
DispatchQueue.main.async {
self.array2.insert(Item(title: string, url: URL(string: "http://www.apple.com")!), at: 0)
}
}
}
return true
}
full MRE code:
import SwiftUI
import UniformTypeIdentifiers
struct Item: Identifiable {
let id = UUID()
var title: String
var url: URL
}
struct ContentView: View {
#State var array1: [Item] = [
Item(title: "One", url: URL(string: "http://www.amazon.com")!),
Item(title: "Two", url: URL(string: "http://www.apple.com")!),
Item(title: "Three", url: URL(string: "http://www.example.com")!),
]
#State var array2: [Item] = []
#State var isDropping = false
var body: some View {
HStack(alignment: .top) {
VStack(alignment: .leading) {
ForEach(array1) { item in
Text(item.title)
Text(item.url.absoluteString).foregroundColor(.secondary)
// DRAG
.onDrag {
return NSItemProvider(object: item.url as NSURL) // WORKS
// return NSItemProvider(object: item.title as NSString) // DOES NOT WORK
}
}
}
.frame(maxWidth: .infinity)
Divider()
VStack(alignment: .leading) {
ForEach(array2) { item in
Text(item.title)
Text(item.url.absoluteString).foregroundColor(.secondary)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(isDropping ? .green : .gray)
// DROP with url/NSURL -- WORKS
.onDrop(of: [UTType.url], isTargeted: $isDropping) { providers in
_ = providers.first?.loadObject(ofClass: URL.self) { url, error in
if let error = error { print(error.localizedDescription) }
if let url = url {
DispatchQueue.main.async {
self.array2.insert(Item(title: "new", url: url), at: 0)
}
}
}
return true
}
// DROP with text/NSString -- DOES NOT WORK
.onDrop(of: [UTType.text], isTargeted: $isDropping) { providers in
_ = providers.first?.loadObject(ofClass: String.self) { string, error in
if let error = error { print(error.localizedDescription) }
if let string = string {
DispatchQueue.main.async {
self.array2.insert(Item(title: string, url: URL(string: "http://www.apple.com")!), at: 0)
}
}
}
return true
}
}
}
}
As always thanks to #Asperi and his answer here:
SwiftUI: Not getting dropped NSString value in DropDelegate
This now works:
.onDrop(of: [UTType.utf8PlainText], isTargeted: $isDropping) { providers in
_ = providers.first?.loadItem(forTypeIdentifier: "public.utf8-plain-text") { data, error in
if let error = error { print(error.localizedDescription) }
if let data = data as? Data {
DispatchQueue.main.async {
let string = NSString(data: data, encoding: 4) ?? "failed"
print(string)
self.array2.insert(Item(title: string as String, url: URL(string: "http://www.apple.com")!), at: 0)
}
}
}
return true
}
Related
Hey all I have been trying to fix this issue for 2 days now and just can't seem to get what I am lookinhg for.
My working code:
struct User: Decodable {
let name: String
let description: String
let isOn: Bool
}
struct ContentView: View {
#State var users = [User]()
var body: some View {
List{
ForEach(users, id: \.name) { item in
HStack {
Toggle(isOn: .constant(true)) {
Label {
Text(item.description)
} icon: {
//list.img
}
}
}.padding(7)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://-----.---/jsontest.json") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {data, response, error in
if let data = data {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let decodedResponse = try?
decoder.decode([User].self, from: data) {
DispatchQueue.main.async {
self.users = decodedResponse
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
the Json its pulling looks like this:
{
"name": "J",
"description": "Echo Show",
"isOn": true
},...
Like I said above this code works as-is. Where the problem I am having comes into play is the toggle part. It doesn't seem to be happens with the item.isOn for seeing if its true or false. The only thing it likes is the .constant(). Naturally this will not work for my case due to me needing to change the toggles to either true or false.
If it's anything other than .constant it has the error of:
Cannot convert value of type 'Bool' to expected argument type 'Binding'
I can perhaps if I do this
#State var ison = false
var body: some View {
List{
ForEach(users, id: \.name) { item in
HStack {
Toggle(isOn: $ison)) {
That seems to take that error away.... but how can it get the value thats looping for isOn that way?
What am I missing here?
try this approach, making isOn a var in User, and using bindings, the $ in the ForEach and Toggle, works for me:
struct User: Decodable {
let name: String
let description: String
var isOn: Bool // <-- here
}
struct ContentView: View {
#State var users = [User]()
var body: some View {
List{
ForEach($users, id: \.name) { $item in // <-- here
HStack {
Toggle(isOn: $item.isOn) { // <-- here
Label {
Text(item.description)
} icon: {
//list.img
}
}
}.padding(7)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://-----.---/jsontest.json") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {data, response, error in
if let data = data {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let decodedResponse = try?
decoder.decode([User].self, from: data) {
DispatchQueue.main.async {
self.users = decodedResponse
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
I'm looking for a way to read a list of players from CoreData in order to select a single name with a menupickerstyle-picker. Displaying the entire list works fine.
this works fine:
Text("Number of players: (teams.count) ")
How con a display a single name?
Text("Players Name: (???????????) ")
Thx for any help.
Franz
//DataBase: teamsandscores Entity: Team, Attributes: id as UUID and player as String
class CoreDataManager {
let persistentContainer: NSPersistentContainer
init() {
persistentContainer = NSPersistentContainer(name: "teamsandscores")
persistentContainer.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Core Data Store failed \(error.localizedDescription)")
}
}
}
func updateTeam() {
do {
try persistentContainer.viewContext.save()
} catch{
persistentContainer.viewContext.rollback()
}
}
func deleteTeam(team: Team){
persistentContainer.viewContext.delete(team)
do {
try persistentContainer.viewContext.save()
} catch{
persistentContainer.viewContext.rollback()
print("Failed to save context \(error)")
}
}
func getAllTeams() -> [Team] {
let fetchRequest: NSFetchRequest<Team> = Team.fetchRequest()
do{
return try persistentContainer.viewContext.fetch(fetchRequest)
}
catch {
return []
}
}
func saveTeams(player: String){
let teamCD = Team(context: persistentContainer.viewContext)
teamCD.player = player
do {
try persistentContainer.viewContext.save()
} catch {
print("failed to save team name \(error)")
}
}
}
let coreDM: CoreDataManager
#State private var playerHome : String = ""
#State private var allPlayersCIndex = 1
#State private var teams: [Team] = [Team]()
private func populateTeams(){
teams = coreDM.getAllTeams()
}
var body: some View{
VStack{
HStack{
TextField("NewPlayer", text: $playerHome )
.textFieldStyle(RoundedBorderTextFieldStyle())
Button("Save") {
coreDM.saveTeams(player: playerHome)
populateTeams()
playerHome = ""
}
}
List{
ForEach(teams, id: \.self) { name in
Text(name.player ?? "")
}
.onDelete(perform: { indexSet in
indexSet.forEach { index in
let team = teams[index]
coreDM.deleteTeam(team: team)
populateTeams()
}
})
}
///works fine:
Text("Number of players: \(teams.count) ")
///// I need help.
Text("Players Name: \(???????????[0]) ")
}
HStack{
///? Picker("Player", selection: $allPlayersCIndex,
content: {
ForEach(teams, id: \.self) { name in
///?? Text(name.player ?? "")
}
}
)
}
I want to add an object to a local server and then see the updated list in my view.
I have a ViewModelClass that handles the REST requests:
class CupcakeViewModel : ObservableObject {
let objectWillChange = PassthroughSubject<CupcakeViewModel,Never>()
init() {
get()
}
var cupcakes : [Cupcake] = [Cupcake]() {
didSet {
objectWillChange.send(self)
}
}
static let url = URL(string: "http://localhost:1337/cupcakes")!
func get() {
URLSession.shared.dataTask(with: CupcakeViewModel.url) { (data, response, error) in
if let data = data {
do {
print(data)
let cupcakes = try JSONDecoder().decode([Cupcake].self, from: data)
DispatchQueue.main.async {
self.cupcakes = cupcakes
}
} catch {
print("ERROR")
}
}
}.resume()
}
func post(cupcake : Cupcake) {
AF.request(CupcakeViewModel.url, method: .post, parameters: cupcake, encoder: JSONParameterEncoder.default).responseDecodable { (response: DataResponse<Cupcake, AFError>) in
if let value = response.value {
DispatchQueue.main.async {
self.cupcakes.append(value)
}
}
}
}
}
and in my MainView I have:
struct CupcakesView: View {
#ObservedObject var VM = CupcakeViewModel()
#State var showed = false
var body: some View {
NavigationView {
List(VM.cupcakes,id:\.self) {cupcake in
Text(cupcake.gusto)
}.padding(.top,1)
.sheet(isPresented: $showed, content: {
AggiungiCupcake(showed: self.$showed)
})
.navigationBarItems(trailing:
Button(action: {
self.showed = true
}) {
Text("ADD")
}
)
}
}
}
struct AggiungiCupcake : View {
#State var gusto = ""
#State var prezzo : Float = 0
#Binding var showed : Bool
#ObservedObject var VM = CupcakeViewModel()
var body : some View {
VStack {
TextField("Gusto", text: $gusto)
TextField("Prezzo", value: $prezzo, formatter: NumberFormatter())
Button(action: {
let c = Cupcake(gusto: self.gusto, prezzo: self.prezzo)
self.VM.post(cupcake: c)
self.showed = false
}) {
Text("ADD")
}
}.padding(30)
}
}
Both the get and the post requests go fine and on my server everything is updated, but my view does not add the new Object in the list. I use AlamoFire (AF) for the post request.
Anyone can help?
I think the issue is probably that you are calling send on your publisher in a didSet observer, rather than in a willSet observer. As the publisher name implies, you need to do this before your changes are made.
Try changing:
didSet {
objectWillChange.send(self)
}
to this:
willSet {
objectWillChange.send(self)
}
I trying to learn the new SwiftUI coding technique. I would like to click a button that will add elements to an array that is a #State variable. The button is the buttonclick function. The array is the push_group_row / push_group_array. I get an error in the append statement.
Eventually the buttonclick will access a database to build an array with more row, but for now I just trying to add one row.
Code:
import SwiftUI
import Combine
var gordon: String = "xxxxxx"
struct Result: Codable {
let trackId: Int
let trackName: String
let collectionName: String
}
struct Response: Codable {
var results: [Result]
}
struct Pokemon: Identifiable {
let id: Int
let name: String
let type: String
let color: Color
}
struct push_group_row {
let id: Int
let code: String
let title: String
}
struct ContentView: View
{
#State private var results = [Result]()
#State var pokemonList = [
Pokemon(id: 0, name: "Charmander", type: "Fire", color: .red),
Pokemon(id: 1, name: "Squirtle", type: "Water", color: .blue),
Pokemon(id: 2, name: "Bulbasaur", type: "Grass", color: .green),
Pokemon(id: 3, name: "Pikachu", type: "Electric", color: .yellow),]
#State var push_group_array = [push_group_row(id: 0, code: "code12", title: "POAFire")]
var body: some View
{
NavigationView
{
VStack(alignment: . leading){
Button(action: {
// What to perform
self.buttonclick()
}) {
// How the button looks like
Text("clickme")
.background(Color.purple)
.foregroundColor(.white)
}
List(results, id: \.trackId)
{item in
NavigationLink(destination: DetailView(lm: String(item.trackId)))
{
VStack(alignment: .leading)
{
Text(String(item.trackId))
Text(item.trackName)
.font(.headline)
Text(item.collectionName)
Text(gordon)
}
}
}
List(self.pokemonList)
{ pokemon in
HStack
{
Text(pokemon.name)
Text(pokemon.type).foregroundColor(pokemon.color)
}
}
List(push_group_array, id: \.id)
{ pg_item in
HStack
{
Text(String(pg_item.id))
Text(pg_item.code)
}
}
.onAppear(perform: self.loaddata)
}
.navigationBarTitle("x")
.navigationBarItems(
trailing: Button(action: addPokemon, label: { Text("Add") }))
Spacer()
}
}
func addPokemon() {
if let randomPokemon = pokemonList.randomElement() {
pokemonList.append(randomPokemon)
}
}
// *************************** below is the add arrat code
func buttonclick() {
let newCode = "First"
let newTitle = "Second"
push_group_array.append(id: 1, code: newCode, title: newTitle)
}
func loaddata()
{
print("loaddata")
guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song")
else
{
print("Invalid URL")
return
}
var urlData: NSData?
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data
{
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
{
DispatchQueue.main.async
{
urlData = data as NSData?
self.results = decodedResponse.results
print(self.results)
print(urlData ?? "urlData_Defaultvalue")
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You need to push the object rather than 3 values
push_group_array.append(push_group_row(id: 1, code: newCode, title: newTitle))
I created a TabView with two tabs. One is Home and the other loads text and an image from NASA pic of the day API. When I change to the NASA pic of the day, I see "Loading data" until the data loads. Once the data is loaded, for some reason the tab switches back to the "Home" tab. After this bug happens, I can switch back and forth between the two tabs normally and everything is loaded. Why does the tab get switched back to the home tab? Thank you!!
APIImageView Code:
import SwiftUI
struct ApiImageView: View {
#ObservedObject var apiImage = ApiImage()
var body: some View {
Group {
if apiImage.dataHasLoaded {
VStack {
Text(apiImage.title!)
.font(.largeTitle)
Image(uiImage: apiImage.image!).resizable()
.cornerRadius(10)
.padding()
ScrollView(.vertical, showsIndicators: false) {
Text(apiImage.explanation!)
.font(.subheadline)
.padding()
}
}
} else {
Text("Loading Data")
}
}.onAppear {
self.apiImage.loadImageFromApi(urlString: "https://api.nasa.gov/planetary/apod?api_key=eaRYg7fgTemadUv1bQawGRqCWBgktMjolYwiRrHK")
}
}
}
struct ApiImageView_Previews: PreviewProvider {
static var previews: some View {
ApiImageView()
}
}
APIImage Code:
import SwiftUI
class ApiImage: ObservableObject {
#Published var dataHasLoaded = false
#Published var image: UIImage? = nil
#Published var title: String? = nil
#Published var explanation: String? = nil
}
extension ApiImage {
func loadImageFromApi(urlString: String) {
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: parseJsonObject)
task.resume()
}
func parseJsonObject(data: Data?, urlResponse: URLResponse?, error: Error?) {
guard error == nil else {
print("\(error!)")
return
}
guard let content = data else {
print("No data")
return
}
let json = try! JSONSerialization.jsonObject(with: content)
let jsonmap = json as! [String : Any]
let titleText = jsonmap["title"] as! String
let explanationText = jsonmap["explanation"] as! String
let urlString = jsonmap["url"] as! String
print("\(urlString)")
print("\(titleText)")
print("\(explanationText)")
DispatchQueue.main.async {
self.title = titleText
self.explanation = explanationText
}
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: setImageFromData)
task.resume()
}
func setImageFromData(data: Data?, urlResponse: URLResponse?, error: Error?) {
guard error == nil else {
print("\(error!)")
return
}
guard let content = data else {
print("No data")
return
}
DispatchQueue.main.async {
self.image = UIImage(data: content)
self.dataHasLoaded = true
}
}
}
MainTabView Code:
import SwiftUI
struct MainTabView: View {
var body: some View {
TabView {
CategoryHome()
.tabItem {
Image(systemName: "house.fill")
Text("Landmarks")
.tag(0)
}
ApiImageView()
.tabItem {
Image(systemName: "flame.fill")
Text("NASA Pic")
//.tag(1)
}
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
Maybe not directly a solution, but also important: it seems that the MainTabView is not entirely correct (the .tag() should be outside the .tabItem closure). This would be a correct version:
import SwiftUI
struct MainTabView: View {
var body: some View {
TabView {
CategoryHome()
.tabItem {
Image(systemName: "house.fill")
Text("Landmarks")
}.tag(0)
ApiImageView()
.tabItem {
Image(systemName: "flame.fill")
Text("NASA Pic")
}.tag(1)
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
Maybe this is already the solution; if not, I hope it is still helpful! :)