MapKit custom annotation view causing crashes - cocoa-touch

I created a custom AnnotaionView which displays a number.
The code causing the most problem seems to be in the layout.
class NumberAnnotationView: MKAnnotationView {
private var __number: Int
private var textLayer: CALayer!
private var shapeLayer: CAShapeLayer!
var number: Int {
get {
return __number
}
set {
__number = newValue
self.layer.setNeedsDisplay()
}
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
__number = 0
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
self.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
self.isOpaque = false
shapeLayer = CAShapeLayer()
shapeLayer.contentsScale = UIScreen.main.scale
shapeLayer.zPosition = 0
textLayer = CALayer()
textLayer.contentsScale = UIScreen.main.scale
textLayer.zPosition = 0
self.layer.addSublayer(shapeLayer)
self.layer.addSublayer(textLayer)
self.layer.contentsScale = UIScreen.main.scale
self.layer.delegate = self
self.layer.setNeedsDisplay()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ layer: CALayer, in ctx: CGContext) {
if textLayer != nil && layer == textLayer {
UIGraphicsPushContext(ctx)
let textBounds = "\(number)".nsString.boundingRect(with: textLayer.frame.size, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [
NSFontAttributeName: UIFont.systemFont(ofSize: 14)
], context: nil)
textLayer.frame = CGRect(x: (layer.frame.size.width - textBounds.width) / 2, y: (layer.frame.size.height - textBounds.height) / 2, width: textBounds.width, height: textBounds.height)
"\(number)".nsString.draw(in: textLayer.frame, withAttributes: [
NSFontAttributeName: UIFont.systemFont(ofSize: 14)
])
UIGraphicsPopContext()
} else if shapeLayer != nil && layer == shapeLayer {
UIGraphicsPushContext(ctx)
let path = UIBezierPath(roundedRect: layer.frame, cornerRadius: 5.0)
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 2.0
UIGraphicsPopContext()
} else if layer == self.layer {
shapeLayer.setNeedsDisplay()
textLayer.setNeedsDisplay()
}
}
override func layoutSublayers(of layer: CALayer) {
if (layer == self.layer) {
shapeLayer.frame = layer.bounds
textLayer.frame = layer.bounds
shapeLayer.delegate = self
textLayer.delegate = self
}
}
}
It crashes constantly, and gave me a puzzling stack...
#0 0x000000010d3b294e in -[UIGestureEnvironment _queueGestureRecognizersForResetIfFinished:] ()
#1 0x000000010d3b28ed in -[UIGestureEnvironment _cancelGestureRecognizers:] ()
#2 0x000000010ce93803 in -[UIApplication _cancelGestureRecognizersForView:] ()
#3 0x000000010cf2e832 in -[UIView(Hierarchy) _willMoveToWindow:] ()
#4 0x000000010cf2f3d5 in __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke ()
#5 0x000000010cf2f2fe in -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] ()
..........
#40240 0x000000010cf2f3f3 in __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke ()
#40241 0x000000010cf2f2fe in -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] ()
#40242 0x000000010cf2f3f3 in __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke ()
#40243 0x000000010cf2f2fe in -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] ()
#40244 0x000000010cf2d31f in __UIViewWillBeRemovedFromSuperview ()
#40245 0x000000010cf2cf32 in -[UIView(Hierarchy) removeFromSuperview] ()
#40246 0x000000010c9e21e0 in -[MKAnnotationContainerView removeAnnotationView:] ()
#40247 0x000000010c872391 in -[MKMapView removeAnnotationRepresentation:] ()
#40248 0x000000010c930438 in -[MKAnnotationManager _removeRepresentationForAnnotation:fromCull:] ()
#40249 0x000000010c92f519 in -[MKAnnotationManager updateVisibleAnnotations] ()
#40250 0x000000010c862fe5 in -[MKMapView _didChangeRegionMidstream:] ()
#40251 0x000000010c867e24 in -[MKMapView mapLayer:didChangeRegionAnimated:] ()
#40252 0x000000011340ff38 in __58-[VKMapCameraController zoom:withPoint:completionHandler:]_block_invoke.132 ()
#40253 0x00000001133bcd30 in -[VKAnimation stopAnimation:] ()
#40254 0x00000001133bd0f6 in -[VKTimedAnimation stopAnimation:] ()
#40255 0x00000001133bd1d5 in -[VKTimedAnimation onTimerFired:] ()
#40256 0x00000001133e09e9 in -[VKScreenCanvas animateWithTimestamp:] ()
#40257 0x00000001133e0610 in -[VKScreenCanvas updateWithTimestamp:] ()
#40258 0x000000011339b3dc in -[VKMapView onTimerFired:] ()
#40259 0x00000001137b2c69 in -[GGLDisplayLink _displayLinkFired:] ()
#40260 0x000000010cbffbd5 in CA::Display::DisplayLinkItem::dispatch(unsigned long long) ()
#40261 0x000000010cbffa95 in CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) ()
#40262 0x000000010f5ed964 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ ()
#40263 0x000000010f5ed5f3 in __CFRunLoopDoTimer ()
#40264 0x000000010f5ed17a in __CFRunLoopDoTimers ()
#40265 0x000000010f5e4f01 in __CFRunLoopRun ()
#40266 0x000000010f5e4494 in CFRunLoopRunSpecific ()
#40267 0x0000000112b93a6f in GSEventRunModal ()
#40268 0x000000010ce7ef34 in UIApplicationMain ()
#40269 0x000000010b6af59f in main

The solution turns out to have no relation to Map Kit though.
See here, it seems that while it worked for a while, an object should not be delegate to multiple CALayers.
Subclassing CALayer and implementing custom drawing on the layer itself eventually solved the problem.

Related

iOS14: no UIControl working anymore in a UIKit Modal ViewController

Using iOS14.0.1, Swift5.3, Xcode12.0.1,
I am desperate with this problem:
With iOS12 and iOS13, my App was working nicely.
But now with iOS14, no UIButton, no UISwitch, no segue - nothing is working in my Modal ViewController.
I have no idea what Apple changed in UIKit for this problem to occur ???? !!!!
Please help!
My Modal ViewController is presented as follows:
#objc func settingsBtnPressed(_ sender: UIButton) {
let settingsVC = SettingsViewController()
settingsVC.backDelegate = self
let navController = UINavigationController(rootViewController: settingsVC)
navController.navigationBar.barStyle = .black
navController.navigationBar.tintColor = .white
navController.navigationBar.prefersLargeTitles = true
navController.navigationBar.backgroundColor = UIColor.clear
navController.presentationController?.delegate = self
self.present(navController, animated: true)
}
class SettingsViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellID"
init() {
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 1)
super.init(collectionViewLayout: collectionViewFlowLayout)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// ...
collectionView.register(SettingsCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! SettingsCollectionViewCell
cell.setup(titlesFirstSection: titlesFirstSection, iconsFirstSection: iconsFirstSection, onStatesFirstSection: onStatesFirstSection, )
return cell
}
}
class SettingsCollectionViewCell: UICollectionViewCell {
var SettingsStackView = UIStackView()
var attries: UICollectionViewLayoutAttributes!
var myContentHeight: CGFloat = 0.0
var firstSettingsSectionView: MenuSwitchListSectionView?
let elementHeight: CGFloat = 44.0
let separatorLineThickness: CGFloat = 1.0
let sectionViewBackgroundColor = ImageConverter.UIColorFromRGB(0x2C2C2E, alpha: 1.0)
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup(titlesFirstSection: [String],
iconsFirstSection: [UIImage?]?,
onStatesFirstSection: [Bool]) {
myContentHeight = UIScreen.main.bounds.height - 120
initFirstSettingsSectionView(titles: titlesFirstSection, icons: iconsFirstSection, onStates: onStatesFirstSection)
SettingsStackView = VerticalStackView(arrangedSubviews: [
UIView(),
firstSettingsSectionView! as UIView,
UIView()
], spacing: 10.0, alignment: .center )
contentView.addSubview(SettingsStackView)
contentView.isUserInteractionEnabled = true
setConstraints(nrOfItemsFirstSection: titlesFirstSection.count)
}
fileprivate func initFirstSettingsSectionView(titles: [String], icons: [UIImage?]?, onStates: [Bool]) {
firstSettingsSectionView = MenuSwitchListSectionView(frame: .zero,
elementHeight: elementHeight,
separatorLineThickness: separatorLineThickness,
backgroundColor: sectionViewBackgroundColor,
titles: titles,
icons: icons,
onStates: onStates
)
firstSettingsSectionView?.switchMutatedDelegate = self
firstSettingsSectionView?.assignDelegate()
}
fileprivate func setConstraints(nrOfItemsFirstSection: Int) {
contentView.translatesAutoresizingMaskIntoConstraints = false
SettingsStackView.translatesAutoresizingMaskIntoConstraints = false
firstSettingsSectionView?.translatesAutoresizingMaskIntoConstraints = false
SettingsStackView.anchor(top: safeAreaLayoutGuide.topAnchor, leading: contentView.leadingAnchor, bottom: nil, trailing: contentView.trailingAnchor)
let totalHeightFirstSection: CGFloat = elementHeight * CGFloat(nrOfItemsFirstSection) + separatorLineThickness * CGFloat(nrOfItemsFirstSection - 1)
firstSettingsSectionView?.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
firstSettingsSectionView?.heightAnchor.constraint(equalToConstant: totalHeightFirstSection).isActive = true
firstSettingsSectionView?.widthAnchor.constraint(equalToConstant: contentView.bounds.width - 16.0).isActive = true
}
class MenuSwitchElementStackView: UIStackView {
weak var switchMutatedDelegate: SwitchMutatedDelegate?
let imgViewWidth: CGFloat = 23.0
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(frame: CGRect, text: String, icon: UIImage?, onState: Bool, switchID: Int) {
self.init(frame: frame)
composeStackView(text: text, icon: icon, onState: onState, switchID: switchID)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func composeStackView(text: String, icon: UIImage?, onState: Bool, switchID: Int) {
// StackView properties
axis = .horizontal
alignment = .center
spacing = 23.0
var iconImgView: UIImageView?
// create Icon
if let icon = icon {
iconImgView = UIImageView()
iconImgView?.image = icon
iconImgView?.contentMode = .scaleAspectFit
}
// create Title
let titleLabel = UILabel()
titleLabel.font = AppConstants.Font.ListSectionElementTitleFont
titleLabel.text = text
titleLabel.textColor = .white
titleLabel.tintColor = .white
titleLabel.heightAnchor.constraint(equalToConstant: 19.0).isActive = true
// create choice TextLbl
let mySwitch = UISwitch()
mySwitch.tag = switchID
mySwitch.setOn(onState, animated: false)
mySwitch.addTarget(self, action: #selector(MenuSwitchElementStackView.switchChanged(_:)), for: UIControl.Event.valueChanged)
// text stackView creation
if let iconImgView = iconImgView {
addArrangedSubview(iconImgView)
}
addArrangedSubview(titleLabel)
addArrangedSubview(mySwitch)
// set Constraints
setConstraints(iconImgView, titleLabel, mySwitch)
}
#objc func switchChanged(_ mySwitch: UISwitch) {
switchMutatedDelegate?.switchChanged(isOn: mySwitch.isOn, switchID: mySwitch.tag)
}
fileprivate func setConstraints(_ iconImgView: UIImageView?, _ titleLabel: UILabel, _ mySwitch: UISwitch) {
translatesAutoresizingMaskIntoConstraints = false
iconImgView?.translatesAutoresizingMaskIntoConstraints = false
titleLabel.translatesAutoresizingMaskIntoConstraints = false
mySwitch.translatesAutoresizingMaskIntoConstraints = false
iconImgView?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0).isActive = true
iconImgView?.widthAnchor.constraint(equalToConstant: 24.0).isActive = true
iconImgView?.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
mySwitch.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
mySwitch.leadingAnchor.constraint(equalTo: trailingAnchor, constant: -64.0).isActive = true
}
}
class MenuSwitchListSectionView: UIView {
var sectionElementsStackView: MenuSwitchListSectionWithElementsStackView?
weak var switchMutatedDelegate: SwitchMutatedDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(frame: CGRect,
elementHeight: CGFloat,
separatorLineThickness: CGFloat,
backgroundColor: UIColor,
titles: [String],
icons: [UIImage?]?,
onStates: [Bool]) {
self.init(frame: frame)
composeView(elementHeight: elementHeight, separatorLineThickness: separatorLineThickness, backgroundColor: backgroundColor, titles: titles, icons: icons, onStates: onStates)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func assignDelegate() {
self.sectionElementsStackView?.switchMutatedDelegate = switchMutatedDelegate
sectionElementsStackView?.assignDelegate()
}
fileprivate func composeView(elementHeight: CGFloat, separatorLineThickness: CGFloat, backgroundColor: UIColor, titles: [String], icons: [UIImage?]?, onStates: [Bool]) {
// create background View
let totalHeight: CGFloat = elementHeight * CGFloat(titles.count) + separatorLineThickness * CGFloat(titles.count - 1)
let backGroundView = ListSectionBackgroundView(frame: .zero, height: totalHeight, backgroundColor: backgroundColor)
// create stackView
sectionElementsStackView = MenuSwitchListSectionWithElementsStackView(frame: CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: totalHeight), elementHeight: elementHeight, separatorLineThickness: separatorLineThickness, titles: titles, icons: icons, onStates: onStates)
// add background and stackView to self
addSubview(backGroundView)
addSubview(sectionElementsStackView!)
// set Constraints
translatesAutoresizingMaskIntoConstraints = false
backGroundView.translatesAutoresizingMaskIntoConstraints = false
sectionElementsStackView?.translatesAutoresizingMaskIntoConstraints = false
backGroundView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
backGroundView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
backGroundView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
backGroundView.heightAnchor.constraint(equalToConstant: totalHeight).isActive = true
sectionElementsStackView?.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
sectionElementsStackView?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0).isActive = true
sectionElementsStackView?.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10.0).isActive = true
}
}
class MenuSwitchListSectionWithElementsStackView: UIStackView {
weak var switchMutatedDelegate: SwitchMutatedDelegate?
var elementStackView: MenuSwitchElementStackView?
var elements: [MenuSwitchElementStackView]?
var titles: [String]? // needed for gesture callback
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(frame: CGRect,
elementHeight: CGFloat,
separatorLineThickness: CGFloat,
titles: [String],
icons: [UIImage?]?,
onStates: [Bool]) {
self.init(frame: frame)
self.titles = titles // needed for gesture callback
composeStackView(elementHeight: elementHeight, separatorLineThickness: separatorLineThickness, titles: titles, icons: icons, onStates: onStates)
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func assignDelegate() {
if let elements = elements {
for element in elements {
element.switchMutatedDelegate = switchMutatedDelegate
}
}
}
fileprivate func composeStackView(elementHeight: CGFloat, separatorLineThickness: CGFloat, titles: [String], icons: [UIImage?]?, onStates: [Bool]) {
// stackView properties
axis = .vertical
alignment = .leading
spacing = 0.0
// create stackView elements
elements = (0..<titles.count).map { (idx) -> MenuSwitchElementStackView in
if icons?.count ?? 0 > 0 {
elementStackView = MenuSwitchElementStackView(frame: .zero, text: titles[idx], icon: icons?[idx], onState: onStates[idx], switchID: idx)
} else {
elementStackView = MenuSwitchElementStackView(frame: .zero, text: titles[idx], icon: nil, onState: onStates[idx], switchID: idx)
}
elementStackView?.isUserInteractionEnabled = true
elementStackView?.tag = idx
return elementStackView!
}
var singleLines = [UIView]()
let lineThickness: CGFloat = separatorLineThickness
// compose the stackView
if let elements = elements {
for (idx, view) in elements.enumerated() {
// add element to stackView
addArrangedSubview(view)
if idx < titles.count - 1 {
// add single Line to stackView
let singleLineView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: lineThickness))
singleLineView.backgroundColor = ImageConverter.UIColorFromRGB(0x606060, alpha: 1.0)
singleLines.append(singleLineView)
addArrangedSubview(singleLineView)
}
}
}
// set constraints
translatesAutoresizingMaskIntoConstraints = false
if let elements = elements {
for (idx, element) in elements.enumerated() {
element.translatesAutoresizingMaskIntoConstraints = false
element.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
element.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
element.heightAnchor.constraint(equalToConstant: elementHeight).isActive = true
if idx < titles.count - 1 {
singleLines[idx].translatesAutoresizingMaskIntoConstraints = false
singleLines[idx].topAnchor.constraint(equalTo: element.bottomAnchor).isActive = true
singleLines[idx].heightAnchor.constraint(equalToConstant: lineThickness).isActive = true
if icons?.count ?? 0 > 0 {
if let _ = icons?[idx] {
singleLines[idx].leadingAnchor.constraint(equalTo: leadingAnchor, constant: 39.0).isActive = true
}
} else {
singleLines[idx].leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
}
singleLines[idx].trailingAnchor.constraint(equalTo: trailingAnchor, constant: 9.0).isActive = true
}
}
}
}
}
class ListSectionBackgroundView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(frame: CGRect, height: CGFloat = 60.0, backgroundColor: UIColor) {
self.init(frame: frame)
composeView(height: height, backgroundColor: backgroundColor)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func composeView(height: CGFloat, backgroundColor: UIColor) {
// View properties
self.backgroundColor = backgroundColor
self.layer.cornerRadius = 10.0
self.layer.masksToBounds = true
self.clipsToBounds = true
// set Constraints
setConstraints(height)
}
fileprivate func setConstraints(_ height: CGFloat) {
self.translatesAutoresizingMaskIntoConstraints = false
self.heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
I finally found a solution.
It was the UIView-extension "anchor" in my code that no longer works under iOS14 for some reason.
Here I describe the problem more precisely...
Therefore, if I use the extension and do the following - then any UIControls in my ViewHierarchy do not work (i.e. no target-actions, switch-didChanges, etc will no longer kick-in)
SettingsStackView.anchor(top: safeAreaLayoutGuide.topAnchor, leading: contentView.leadingAnchor, bottom: contentView.bottomAnchor, trailing: contentView.trailingAnchor)
However, if I do it without the UIView-extension, then everything works fine:
SettingsStackView.translatesAutoresizingMaskIntoConstraints = false
SettingsStackView.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor).isActive = true
SettingsStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
SettingsStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
SettingsStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
Maybe somebody can tell me why this new behaviour came up with iOS14 ???

Without Bridging to ObjectiveC, Can We Get the Coordinates of a Tap Solely in SwiftUI?

I have the following code:
struct MyLocationMap: View {
#EnvironmentObject var userData: UserData
#State var annotationArray: [MyAnnotation] = [MyAnnotation(coordinates: CLLocationCoordinate2D(latitude: CurrentLocation().coordinates?.latitude ?? CLLocationCoordinate2D().latitude, longitude: CurrentLocation().coordinates?.longitude ?? CLLocationCoordinate2D().longitude), type: .waypoint)]
var body: some View {
MakeMapView(annotationArray: annotationArray)
.gesture(
LongPressGesture(minimumDuration: 1)
.onEnded { _ in
print("MapView pressed!\n--------------------------------------\n")
//Handle press here
})
}
}
import SwiftUI
import CoreLocation
import MapKit
struct MakeMapView : UIViewRepresentable {
typealias UIViewType = MKMapView
let annotationArray: [MyAnnotation]?
func makeUIView(context: UIViewRepresentableContext<MakeMapView>) -> MKMapView{
MKMapView()
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.showsUserLocation = true
if let coordinates = CurrentLocation().coordinates {
// updateAnnotations(from: mapView)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
mapView.showsUserLocation = true
mapView.showsCompass = true
mapView.showsScale = true
mapView.mapType = .satellite
let span = MKCoordinateSpan(latitudeDelta: 0.002, longitudeDelta: 0.002)
let region = MKCoordinateRegion(center: coordinates, span: span)
mapView.setRegion(region, animated: true)
})
}
}
What I am having trouble with is implementing func convert(_ point: CGPoint, toCoordinateFrom view: UIView?) -> CLLocationCoordinate2D to obtain the lat/lon of the gesture solely using SwiftUI/Combine. Is it possible? If not, can I implement it with what I have?
I have reviewed the post at
How to handle touch gestures in SwiftUI in Swift UIKit Map component?
and
Add single pin to Mapkit with SwiftUI
Once I have the coordinates, dropping the pin is straightforward, but I can't wrap my head around getting the coordinates.
Thanks.
A DragGesture with no minimumDistance activates immediately and has location and startLocation in its onChanged listener, you can use that to grab location like so:
struct GesturesView: View {
#State private var location: CGPoint = .zero
var body: some View {
let drag = DragGesture(minimumDistance: 0).onChanged {
self.location = $0.startLocation
}
let longPress = LongPressGesture().onEnded { _ in
self.doSomething(with: self.location)
}
let gesture = longPress.simultaneously(with: drag)
return Rectangle()
.frame(width: 300, height: 300)
.padding()
.gesture(gesture)
}
func doSomething(with location: CGPoint) {
print("Pressed at", location)
}
}

Swiftui how to use MKOverlayRenderer?

I want draw a route on the map.
but struct without using delegate.
struct MapView : UIViewRepresentable {
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
}
how can I do?
You need to specify a delegate if you want mapView(_:rendererFor:) to be called:
struct MapView: UIViewRepresentable {
#Binding var route: MKPolyline?
let mapViewDelegate = MapViewDelegate()
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
view.delegate = mapViewDelegate // (1) This should be set in makeUIView, but it is getting reset to `nil`
view.translatesAutoresizingMaskIntoConstraints = false // (2) In the absence of this, we get constraints error on rotation; and again, it seems one should do this in makeUIView, but has to be here
addRoute(to: view)
}
}
private extension MapView {
func addRoute(to view: MKMapView) {
if !view.overlays.isEmpty {
view.removeOverlays(view.overlays)
}
guard let route = route else { return }
let mapRect = route.boundingMapRect
view.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10), animated: true)
view.addOverlay(route)
}
}
class MapViewDelegate: NSObject, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.fillColor = UIColor.red.withAlphaComponent(0.5)
renderer.strokeColor = UIColor.red.withAlphaComponent(0.8)
return renderer
}
}
Used like so:
struct ContentView : View {
#State var route: MKPolyline?
var body: some View {
MapView(route: $route)
.onAppear {
self.findCoffee()
}
}
}
private extension ContentView {
func findCoffee() {
let start = CLLocationCoordinate2D(latitude: 37.332693, longitude: -122.03071)
let region = MKCoordinateRegion(center: start, latitudinalMeters: 2000, longitudinalMeters: 2000)
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "coffee"
request.region = region
MKLocalSearch(request: request).start { response, error in
guard let destination = response?.mapItems.first else { return }
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: start))
request.destination = destination
MKDirections(request: request).calculate { directionsResponse, _ in
self.route = directionsResponse?.routes.first?.polyline
}
}
}
}
Yielding:

My Swift framework not working in ObjectiveC but works fine in Swift

I have created a Swift framework that creating a button programmatically, and the action of the button is showing a webView and close it, my framework works fine in Swift project but in Objective C it just create button but it is not taking action and does not show my webView,
Note: I am getting the button configuration from server via JSON using SwiftyJSON
below is the my Swift Class in addition to SwiftyJSON in the framework project
import Foundation
import UIKit
import WebKit
#objc public class predictionButton : NSObject {
public override init(){
}
var buttonConfigApi = "XXXXXXXXX"
var buttonTextColor = String()
var buttonText = String()
var buttonBackgroundColor = String()
var buttonVisibility = Bool()
var indexURL = String()
var authURL = String()
var module = String()
var buttonAlignment = String()
var globalURL = String()
var webViewX = CGFloat()
var webViewY = CGFloat()
var webViewXiWidth = CGFloat()
var webViewHeight = CGFloat()
#objc public var globalView = UIView()
var parseFlag = Bool()
var buttonAlignmentValue = String()
#objc public func addButtonPredict(view: UIView, phone: String, token: String){
parseJSON()
sleep(5)
var phonTok = "{'a':\(phone),'b':\(token)}"
let utf8str = phonTok.data(using: String.Encoding.utf8)
let data = (phonTok).data(using: String.Encoding.utf8)
let base64 = data!.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
var finalURL = String()
self.globalView = view
finalURL = "\(self.indexURL)?d=\(base64)"
self.globalURL = finalURL
let button = UIButton(frame: buttonPosition(view: view, alignValue: buttonAlignment))
print((self.globalURL))
button.backgroundColor = hexStringToUIColor(hex: self.buttonBackgroundColor)
print("global color value \(buttonBackgroundColor)")
button.setTitle("\(self.buttonText)", for: .normal)
button.setTitleColor(hexStringToUIColor(hex: self.buttonTextColor), for: .normal)
let buttonFontSize = 15
let buttonTitleSize = (buttonText as NSString).size(withAttributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: CGFloat(buttonFontSize + 1))])
button.frame.size.height = buttonTitleSize.height * 2
button.frame.size.width = buttonTitleSize.width * 1.5
view.addSubview(button)
button.tag = 5
button.addTarget(self, action: #selector(nest), for: .touchUpInside)
}
#objc func nest (){
let frame = webViewposition(view: self.globalView)
let webView = WKWebView(frame: frame)
webView.load(NSURLRequest(url: NSURL(string: globalURL)! as URL) as URLRequest)
webView.contentMode = .scaleAspectFit
webView.tag = 100
if let viewWithTag = self.globalView.viewWithTag(100) {
viewWithTag.removeFromSuperview()
}else{
print("No!")
self.globalView.addSubview(webView)
}
}
#objc public func parseJSON(){
print("flag")
let requestURL: NSURL = NSURL(string: buttonConfigApi)!
let urlRequest: URLRequest = URLRequest(url: requestURL as URL)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest as URLRequest) {
(data, response, error) -> Void in
if data != nil {
do
{
let readableJSON = try JSON(data: data!, options: JSONSerialization.ReadingOptions.mutableContainers)
let Name = readableJSON["data"]["buttonText"].stringValue as String!
let buttonTextColor = readableJSON["data"]["buttonTextColorCode"].stringValue as String!
let buttonBackgroundColorCode = readableJSON["data"]["buttonBackgroundColorCode"].stringValue as String!
let visibility = readableJSON["data"]["visibility"].boolValue
let indexURL = readableJSON["data"]["indexURL"].stringValue as String!
let authURL = readableJSON["data"]["authURL"].stringValue as String!
let module = readableJSON["data"]["module"].stringValue as String!
let alignment = readableJSON["data"]["alignment"].stringValue as String!
self.buttonText = Name!
self.buttonTextColor = buttonTextColor!
self.buttonBackgroundColor = buttonBackgroundColorCode!
self.buttonAlignment = self.alignText(text: alignment!)
self.indexURL = indexURL!
self.authURL = authURL!
self.module = module!
print("\(Name!) \n \(buttonTextColor!) \n \(buttonBackgroundColorCode!) \n \(indexURL!) \n \(alignment!)")
print("done")
print("Color value:\(buttonBackgroundColorCode!)")
} catch {
print(error)
}
}
}
task.resume()
}
#objc func alignText (text: String) -> String {
var alignText = String()
let newString = text.replacingOccurrences(of: "_", with: "",
options: .literal, range: nil)
return newString.lowercased()
}
#objc func hexStringToUIColor (hex:String) -> UIColor {
var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (cString.hasPrefix("#")) {
cString.remove(at: cString.startIndex)
}
if ((cString.count) != 6) {
print("became gray")
return UIColor.gray
}
var rgbValue:UInt32 = 0
Scanner(string: cString).scanHexInt32(&rgbValue)
return UIColor(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}
#objc func webViewposition(view: UIView) -> CGRect {
var frame = CGRect()
if UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft {
frame = CGRect(x: (view.frame.width) - (view.frame.width / 4) - 120 , y: 20 , width: view.frame.width / 3 , height: view.frame.height - 60 )
} else if UIDevice.current.orientation == UIDeviceOrientation.landscapeRight {
frame = CGRect(x: (view.frame.width) - (view.frame.width / 4) - 120, y: 20 , width: view.frame.width / 3 , height: view.frame.height - 60 )
} else if UIDevice.current.orientation == UIDeviceOrientation.portrait {
frame = CGRect(x: 0, y: 110, width: view.frame.width, height: view.frame.height - 60 )
} else if UIDevice.current.orientation == UIDeviceOrientation.portraitUpsideDown {
frame = CGRect(x: 0, y: 100, width: view.frame.width, height: view.frame.height - 60 )
}
return frame
}
#objc func buttonPosition(view: UIView, alignValue: String)-> CGRect {
var position = CGRect()
switch (alignValue){
case "lefttop":
print("button top left")
position = CGRect(x: 25, y: 50, width: 100, height: 100)
case "righttop":
print("button top right")
position = CGRect(x: view.frame.width - 150, y: 50, width: 100, height: 100)
case "letbottom":
print("button bottom left")
position = CGRect(x: 25, y: view.frame.height - 150, width: 100, height: 100)
case "rightbottom":
print("button bottom right")
position = CGRect(x: view.frame.width - 25, y: view.frame.height - 150, width: 100, height: 100)
default:
position = CGRect(x: 25, y: 50, width: 100, height: 100)
}
return position
}
}
extension UIColor {
convenience init(red: Int, green: Int, blue: Int) {
assert(red >= 0 && red <= 255, "Invalid red component")
assert(green >= 0 && green <= 255, "Invalid green component")
assert(blue >= 0 && blue <= 255, "Invalid blue component")
self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) /
255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
}
convenience init(rgb: Int) {
self.init(
red: (rgb >> 16) & 0xFF,
green: (rgb >> 8) & 0xFF,
blue: rgb & 0xFF
)
}
}
extension String {
var numberValue:NSNumber? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter.number(from: self)
}
}
and I have created a Objective-C and add the built framework in it and its .m file code is below :
#import "ViewController.h"
#import myFrameWork;
#import <myFrameWork/myFrameWork-Swift.h>
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
predictionButton *btn = [[predictionButton alloc] init];
[btn addButtonPredictWithView:self.view phone:#"XXXXXXX"
token:#"XXXXXXXXXXXXXXXXXXXXXXXXXXX"];
}
#end
Note: this framework works fine in Swift project
Here is working swift code:
import UIKit
import myFrameWork
import WebKit
class ViewController: UIViewController {
var button = predictionButton()
override func viewDidLoad() {
super.viewDidLoad()
button.addButtonPredict(view: self.view, phone: "XXXXXXXXXX", token: "XXXXXXXXXXXXXXXXXXXXXXXXX")
}

MPNowPlayingInfoCenter : What is the best way to set MPMediaItemArtwork from an Url?

All methods I found to set MPMediaItemArtwork of MPNowPlayingInfoCenter are with local images.
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage: [UIImage imageNamed:#"myimage"];
But I need to set this from an imageURL
Currently i use this...
UIImage *artworkImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.currentTrack.imageUrl]]];
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage: artworkImage];
[self.payingInfoCenter setValue:albumArt forKey:MPMediaItemPropertyArtwork];
Any idea?
That's my best solution:
- (void)updateControlCenterImage:(NSURL *)imageUrl
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSMutableDictionary *songInfo = [NSMutableDictionary dictionary];
UIImage *artworkImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageUrl]];
if(artworkImage)
{
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage: artworkImage];
[songInfo setValue:albumArt forKey:MPMediaItemPropertyArtwork];
}
MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
infoCenter.nowPlayingInfo = songInfo;
});
}
/!\ if you've already setted the MPNowPlayingInfoCenter , get it or all other values will be overridden
let mpic = MPNowPlayingInfoCenter.default()
DispatchQueue.global().async {
if let urlString = yourUrlString, let url = URL(string:urlString) {
if let data = try? Data.init(contentsOf: url), let image = UIImage(data: data) {
let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { (_ size : CGSize) -> UIImage in
return image
})
DispatchQueue.main.async {
mpic.nowPlayingInfo = [
MPMediaItemPropertyTitle:"Title",
MPMediaItemPropertyArtist:"Artist",
MPMediaItemPropertyArtwork:artwork
]
}
}
}
}
That worked for me in iOS 11, swift 4
Here's a function that sets up a media session with image on the lock screen and control center:
(This code was a modified version of #NickDK's answer)
func setupNowPlaying(title: String, albumArtwork: String, artist:String, isExplicit: Bool, rate: Float, duration: Any) {
let url = URL.init(string: albumArtwork)!
let mpic = MPNowPlayingInfoCenter.default()
DispatchQueue.global().async {
if let data = try? Data.init(contentsOf: url), let image = UIImage(data: data) {
let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { (_ size : CGSize) -> UIImage in
return image
})
DispatchQueue.main.async {
mpic.nowPlayingInfo = [
MPMediaItemPropertyTitle: title,
MPMediaItemPropertyArtist: artist,
MPMediaItemPropertyArtwork:artwork,
MPMediaItemPropertyIsExplicit: isExplicit,
MPNowPlayingInfoPropertyPlaybackRate: soundManager.audioPlayer?.rate ?? 0,
MPMediaItemPropertyPlaybackDuration: CMTimeGetSeconds(soundManager.audioPlayer?.currentItem?.asset.duration ?? CMTime(seconds: 0, preferredTimescale: 0))
]
}
}
}
}
Usage:
setupNowPlaying(
title: "Pull up at the mansion",
albumArtwork: "https://static.wixstatic.com/media/89b4e7_5f29de0db68c4d888065b0f03d393050~mv2.png/v1/fill/w_512,h_512/ImageTitle.png",
artist: "DJ bon26",
isExplicit: true
)
Full usage:
import MediaPlayer
class SoundManager : ObservableObject {
var audioPlayer: AVPlayer?
func playSound(sound: String){
if let url = URL(string: sound) {
self.audioPlayer = AVPlayer(url: url)
}
}
}
struct ContentView: View {
#State var song1 = false
#StateObject private var soundManager = SoundManager()
var body: some View {
Image(systemName: song1 ? "pause.circle.fill": "play.circle.fill")
.font(.system(size: 25))
.padding(.trailing)
.onTapGesture {
playSound(
url: "https://static.wixstatic.com/mp3/0fd70b_8d4e15117ff0458792a6a901c6dddc6b.mp3",
title: "Pull up at the mansion",
albumArtwork: "https://static.wixstatic.com/media/89b4e7_5f29de0db68c4d888065b0f03d393050~mv2.png/v1/fill/w_512,h_512/ImageTitle.png",
artist: "DJ bon26",
isExplicit: true
)
}
}
func playSound(url: String, title: String, albumArtwork: String, artist: String, isExplicit: Bool) {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options:
.init(rawValue: 0))
try AVAudioSession.sharedInstance().setActive(true)
soundManager.playSound(sound: url)
song1.toggle()
if song1{
soundManager.audioPlayer?.play()
setupNowPlaying(
title: title,
albumArtwork: albumArtwork,
artist: artist,
isExplicit: isExplicit
)
UIApplication.shared.beginReceivingRemoteControlEvents()
MPNowPlayingInfoCenter.default().playbackState = .playing
} else {
soundManager.audioPlayer?.pause()
}
}catch{
print("Something came up")
}
}
}
I believe this is very useful,
Brendan Okey-iwobi