Ok, so I've got
private(set) var circles: [MKCircle]!
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
findSzczyty(szczyty)
mapView.delegate = self
circles = szczyty.map {
MKCircle(center: $0.coordinate, radius: 200)
}
}
...
func findSzczyty(_ szczyty: [Szczyt]) {
for szczyt in szczyty {
let annotations = MKPointAnnotation()
annotations.title = szczyt.name
annotations.subtitle = szczyt.describtion
annotations.coordinate = CLLocationCoordinate2D(latitude:
szczyt.lattitude, longitude: szczyt.longtitude)
mapView.addAnnotation(annotations)
mapView.addOverlay(circles as! MKOverlay) // when I try to add it on the map I get an error when building - Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
}
}
Post
Replies
Boosts
Views
Activity
@OOPer maybe you have any tips and advices what to use in my next steps? I dont mean to write me a code, but some good advices. Thanks.
I dont need any code, just ideas and advices hot to deal with it :)
@Claude31 thanks for the tip with the style. It is the radio stream. Now, I've got nowPlaying data only once when the app is starting. I would like to change it and fetch data when the next song is played. My API is here in static JSON: https://admin.radiopromil.online/api/nowplaying_static/radio_promil.json and here the general one: https://admin.radiopromil.online/api/nowplaying/radio_promil - this one Im using in let urlString. Album arts are URL's and I would like to fetch them as an Image and show with song title and artists, there are more than 5000 songs, and the database kepps growing, so the array is not the solution for me. @OOPer You can find full code here: https://github.com/pawelzet/promil_test/blob/main/ViewController.swift . On the top you can see IBOutlets, coneccted to two labels and UIImageView object. The app is building, but the titles in labels does not change and I cant see album art in this UIImageView. Alson in the API there is a part duration: which show how many seconds the song have. Maybe thats the way I can solve fetching new data? Adding this duration of each song parameter + 15 more seconds for the jingle (which is playing between songs and it is not shown in the API). Thank you in advance for your help.
@OOPer here is the full code
import UIKit
import AVKit
import MediaPlayer
class ViewController: UIViewController, AVAudioPlayerDelegate {
var player : AVPlayer!
var dict = NSDictionary()
var isPlaying = false
let playImage = UIImage(named: "play.png")
let pauseImage = UIImage(named: "pause.png")
@IBOutlet weak var artist: UILabel!
@IBOutlet weak var songtitle: UILabel!
@IBOutlet weak var artUrl: UIImageView!
@IBAction func buttonPressed(_ sender: UIButton){
if isPlaying {
player.pause()
sender.setImage(playImage, for: .normal)
} else {
let url = "https://admin.radiopromil.online/radio/8000/radio.mp3"
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: [])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
player = AVPlayer(url: URL(string: url)!)
player.volume = 1.0
player.rate = 1.0
player.play()
sender.setImage(pauseImage, for: .normal)
}
isPlaying.toggle()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
overrideUserInterfaceStyle = .light
setupRemoteCommandCenter()
//Radio API endpoint title and artist labels
let urlString = "https://admin.radiopromil.online/api/nowplaying/radio_promil"
let url = URL(string: urlString)!
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
guard let data = data else {
print("data is nil")
return
}
let decoder = JSONDecoder()
do {
let radio = try decoder.decode(RadioAPI.self, from: data)
print(radio)
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: {
self.songtitle.text = radio.nowPlaying.song.title
self.artist.text = radio.nowPlaying.song.artist
//albumcover art section
if let artUrl = URL(string: radio.nowPlaying.song.art) {
//I need to load an image from `artUrl`
let imageDatatask = session.dataTask(with: artUrl) { imageData, imageResponse, imageError in
if let imageError = imageError {
print(imageError)
return
}
guard let imageData = imageData else {
print("image_data is nil")
return
}
//let albumArt = UIImage(data: imageData)
DispatchQueue.main.async {
let albumArt = UIImage(data: imageData)
let albumView = UIImageView(image: albumArt)
}
}
imageDatatask.resume()
}
})
}
catch {
print("Error Parsing JSON: \(error)")
}
}
dataTask.resume()
}
func setupRemoteCommandCenter() {
// Get the shared MPRemoteCommandCenter
MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyTitle: "Radio Promil"]
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.playCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.player.play()
return .success
}
commandCenter.pauseCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.player.pause()
return .success
}
}
}
@OOPer here is the full code:
import UIKit
import AVKit
import MediaPlayer
class ViewController: UIViewController, AVAudioPlayerDelegate {
var player : AVPlayer!
var dict = NSDictionary()
var isPlaying = false
let playImage = UIImage(named: "play.png")
let pauseImage = UIImage(named: "pause.png")
@IBOutlet weak var artist: UILabel!
@IBOutlet weak var songtitle: UILabel!
@IBOutlet weak var artUrl: UIImageView!
@IBAction func buttonPressed(_ sender: UIButton){
if isPlaying {
player.pause()
sender.setImage(playImage, for: .normal)
} else {
let url = "https://admin.radiopromil.online/radio/8000/radio.mp3"
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: [])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
player = AVPlayer(url: URL(string: url)!)
player.volume = 1.0
player.rate = 1.0
player.play()
sender.setImage(pauseImage, for: .normal)
}
isPlaying.toggle()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
overrideUserInterfaceStyle = .light
setupRemoteCommandCenter()
//Radio API endpoint title and artist labels
let urlString = "https://admin.radiopromil.online/api/nowplaying/radio_promil"
let url = URL(string: urlString)!
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
guard let data = data else {
print("data is nil")
return
}
let decoder = JSONDecoder()
do {
let radio = try decoder.decode(RadioAPI.self, from: data)
print(radio)
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: {
self.songtitle.text = radio.nowPlaying.song.title
self.artist.text = radio.nowPlaying.song.artist
//albumcover art section
if let artUrl = URL(string: radio.nowPlaying.song.art) {
//I need to load an image from `artUrl`
let imageDatatask = session.dataTask(with: artUrl) { imageData, imageResponse, imageError in
if let imageError = imageError {
print(imageError)
return
}
guard let imageData = imageData else {
print("image_data is nil")
return
}
//let albumArt = UIImage(data: imageData)
DispatchQueue.main.async {
let albumArt = UIImage(data: imageData)
let albumView = UIImageView(image: albumArt)
}
}
imageDatatask.resume()
}
})
}
catch {
print("Error Parsing JSON: \(error)")
}
}
dataTask.resume()
}
func setupRemoteCommandCenter() {
// Get the shared MPRemoteCommandCenter
MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyTitle: "Radio Promil"]
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.playCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.player.play()
return .success
}
commandCenter.pauseCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.player.pause()
return .success
}
}
}
Here is the shorter version of my code (without API parsing):
import UIKit
import AVKit
import MediaPlayer
class ViewController: UIViewController, AVAudioPlayerDelegate {
@IBAction func buttonPressed(_ sender: UIButton){
if isPlaying {
player.pause()
sender.setImage(playImage, for: .normal)
} else {
let url = "https://myradio.com/radio.mp3"
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: [])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
player = AVPlayer(url: URL(string: url)!)
player.volume = 1.0
player.rate = 1.0
player.play()
sender.setImage(pauseImage, for: .normal)
}
isPlaying.toggle()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
overrideUserInterfaceStyle = .light
setupRemoteTransportControls()
requestNowPlaying()
setupNowPlaying()
}
// Here is the API data downloading part, so i skipped it
//Command Center audio controls
func setupRemoteTransportControls() {
// Get the shared MPRemoteCommandCenter
let commandCenter = MPRemoteCommandCenter.shared()
// Add handler for Play Command
commandCenter.playCommand.addTarget { [unowned self] event in
if self.player.rate == 1.0 {
self.player.play()
return .success
}
return .commandFailed
}
// Add handler for Pause Command
commandCenter.pauseCommand.addTarget { [unowned self] event in
if self.player.rate == 1.0 {
self.player.pause()
return .success
}
return .commandFailed
}
}
func setupNowPlaying() {
// Define Now Playing Info
var nowPlayingInfo = [String : Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = "Test"
if let image = UIImage(named: "Default_albumart") {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { size in
return image
}
}
nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = true
// Set the metadata
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
func updateNowPlaying(isPause: Bool) {
// Define Now Playing Info
let nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo!
//nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime
//nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPause ? 0 : 1
// Set the metadata
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
//audio interruption
@objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
// Switch over the interruption type.
switch type {
case .began:
print("Interruption began")
case .ended:
// An interruption ended. Resume playback, if appropriate.
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
player.play()
} else {
// An interruption ended. Don't resume playback.
}
default: ()
}
}
}
Ok, so I've added this and it works with objc function:
@objc func didClickDetailDisclosure(button: UIButton) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "second_vc") as? SecondController else {
return
}
present(vc, animated: true)
}
and this in my mapView func:
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
But still I would like each annotation to open each VC. Thank you in advance for your help.
@Claude31 I did sth like this and it changes the icon, but it does not change the VC:
switch annotation.title!! {
case "One":
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "one")
case "Two":
annotationView.markerTintColor = UIColor.black
annotationView.glyphImage = UIImage(named: "two")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
What do you mean by setting a tag to each annotation? Is it impossible to do it this way like icon style? Thank you in advance for your help.
@Claude31 Should I add that kind of switch? If yes it is still showing only first VC:
switch annotation.title!! {
case "0-One":
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "one")
case "1-Two":
annotationView.markerTintColor = UIColor.black
annotationView.glyphImage = UIImage(named: "white_flag")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)// it should show the second VC but it still shows just this one
When I do this:
switch annotation.title {
case 0:
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "kopula")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
case 1:
annotationView.markerTintColor = UIColor.black
annotationView.glyphImage = UIImage(named: "white_flag")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
}
It shows me Expression pattern of type 'Int' cannot match values of type 'String??'
@Claude31 Hi, it only works with the number before like this and it does NOT change the tint color and image :
switch annotation.title {
case "0-One" :
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "photo")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
case "1-Two":
annotationView.markerTintColor = UIColor.black
annotationView.glyphImage = UIImage(named: "photo1")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
case .none:
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
case .some(_):
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
}
If I do case "One" without number before it dont get that. Is there any way to start with 1 instead of 0?
@Claude31 These is same code that Im running:
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
let initialLocation = CLLocation(latitude: 85.10786543576327, longitude: 11.03851472106171)
let locationManager = CLLocationManager()
struct Place {
let name: String
let description: String
let type: String
let lattitude: CLLocationDegrees
let longtitude: CLLocationDegrees
var coordinate: CLLocationCoordinate2D {
.init(latitude: lattitude, longitude: longtitude)
}
}
let location = CLLocation()
let places = [Place(name: "One", description: "One", type: "one", lattitude: 81.108187, longtitude: 12.075812),
Place(name: "Two", description: "Two", typ: "two", lattitude: 81.076187, longtitude: 11.000563),
Place(name: "Three", description: "Three", typ: "Three", lattitude: 81.076187, longtitude: 11.000563)]
[...]
func findPlace(_ miejsca: [Place]) {
for (i, place) in places.enumerated() {
let annotations = MKPointAnnotation()
annotations.title = String(i) + "-" + place.name
annotations.subtitle = place.description
annotations.coordinate = CLLocationCoordinate2D(latitude:
place.lattitude, longitude: place.longtitude)
mapView.addAnnotation(annotations)
}
}
@objc func didClickDetailDisclosure(button: UIButton) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "second_vc") as? SecondController else {
return
}
present(vc, animated: true)
}
@objc func didClickDetailDisclosure2(button: UIButton) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "third_vc") as? SecondController else {
return
}
present(vc, animated: true)
}
@objc func didClickDetailDisclosure3(button: UIButton) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "fourth_vc") as? SecondController else {
return
}
present(vc, animated: true)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else { return nil }
mapView.delegate = self
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))
let identifier = "identifier"
annotationView.canShowCallout = true
guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as? MKMarkerAnnotationView else { return nil }
let rightButton = UIButton(type: .detailDisclosure) // HERE IS THIS BUTTON, BUT I NEED EACH BUTTON FOR EACH ANNOTATION - THIS WAY IT SHOULD BE ONE FOR ALL OF THEM
rightButton.tag = annotation.hash
annotationView.canShowCallout = true
annotationView.rightCalloutAccessoryView = rightButton
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
let leftButton = UIButton(frame: CGRect(
origin: CGPoint.zero,
size: CGSize(width: 25, height: 25)))
leftButton.setBackgroundImage(#imageLiteral(resourceName: "nav"), for: .normal)
annotationView.leftCalloutAccessoryView = leftButton
leftButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
switch annotation.title!! {
case "0-One":
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "kopula")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
case "1-Two":
annotationView.markerTintColor = UIColor.black
annotationView.glyphImage = UIImage(named: "two")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
case "2-Three":
annotationView.markerTintColor = UIColor.red
annotationView.glyphImage = UIImage(named: "three")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure3(button:)), for: .touchUpInside)
default:
annotationView.markerTintColor = UIColor.darkGray
annotationView.glyphImage = UIImage(named: "place")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
}
return annotationView // Without this one the app shows different VC for each annotation, but without this it does not show markerTintColor and glyphImage.
}
}
@Claude31 Ok, let me explain:
This one works with couple of VCs, but it does not change the annotation color and image:
switch annotation.title {
case "0-One" :
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "one")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
case "1-Two":
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "two")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
case "2-Three":
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "three")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure3(button:)), for: .touchUpInside)
case .none:
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
case .some(_):
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
}
This one changes just the color and image in annotation, but does not change the VCs:
switch annotation.title!! {
case "0-One":
annotationView.markerTintColor = UIColor.gray
annotationView.glyphImage = UIImage(named: "kopula")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
case "1-Two":
annotationView.markerTintColor = UIColor.black
annotationView.glyphImage = UIImage(named: "two")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure2(button:)), for: .touchUpInside)
case "2-Three":
annotationView.markerTintColor = UIColor.red
annotationView.glyphImage = UIImage(named: "three")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure3(button:)), for: .touchUpInside)
default:
annotationView.markerTintColor = UIColor.darkGray
annotationView.glyphImage = UIImage(named: "place")
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
}
return annotationView
}
Yes, I have different viewcontrollers, I've tested them with normal buttons.
Ok, I've changed that, thanks
Ok, I found a problem - I've added
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(button:)), for: .touchUpInside)
before the switch, so program was compiling just this one instead of others in switch. Thank you @Claude31
@Claude31 one more question - how not to display enumerate titles? Now it shows on the map number and title: 0-One ; 1-Twoetc. I just Want to display names, as before. The names are not dynamic, so maybe better option is to use titles withou "tags"?