How to display MKCompassButton with swiftui - mapkit

I'm using swiftui and I would like to display the compass button.
The map portion of my code is derived from this tutorial:
https://www.morningswiftui.com/blog/build-mapview-app-with-swiftui
I've looked at sample code to display the compass on the map but I have not been able to find an example that I can get working with my swiftui code.

Actually compass is showing, but only when you try to turn your map. If you want to see compass button for all the time, you can add your own button in makeUIView func:
struct RootMapView: View {
var body: some View {
MapView()
}
}
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
let map = MKMapView()
map.showsCompass = false // hides current compass, which shows only on map turning
let compassBtn = MKCompassButton(mapView: map)
compassBtn.frame.origin = CGPoint(x: 20, y: 20) // you may use GeometryReader to replace it's position
compassBtn.compassVisibility = .visible // compass will always be on map
map.addSubview(compassBtn)
return map
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
}

Related

How to differentiate new and previous notifications

I have an API that returns notifications if any activity in account. I am working first time with notifications. I am displaying these notifications in to UITableView. If user clicks on any notification from the table it redirects to the referral view and delete that notification from the Table list.
Now what I want is, if user leave the notification view with for example 4 notification listed and if 2 new notifications are added to the API then those 2 should have grey background colour and other 4 should have white background colour when user returns to the notificationView.
It is all coming from same API so I am very confuse how to accomplish this result. Its kind of Facebook notifications you can say. Do I need to make any changes to API side which can indicate me if any new notifications are added or is there anything else I am missing ? I know there is push notifications available but I am not using it here. Will it solve the issue if I use it? I am very confused. Please, any suggestion will be much appreciated.
Update: Here is my code :
struct UserNotification {
let id : Int?
let timeStamp : String?
let message : String?
init(json:[String:Any]) {
self.id = (json["Id"] as? Int)
self.timeStamp = (json["TimeStamp"] as? String)
self.message = (json["Message"] as? String)
}
}
var notificationsArray : [UserNotification] = []
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.FetchNotifications()
}
func FetchNotifications(){
URLSession.shared.dataTask(with: jsonUrlString!) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonData = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String:Any]] {
self.notificationsArray.removeAll()
for notifications in jsonData {
let details = UserNotification(json: notifications)
self.notificationsArray.append(details)
}
}
} catch {
print(error.localizedDescription)
}
if (response as? HTTPURLResponse) != nil {
DispatchQueue.main.async {
self.notificationTableView.reloadData()
}
}
}.resume()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let Cell = self.notificationTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomNotificationCell
// Cell.backgroundColor = UIColor(red: 245/255, green: 245/255, blue: 245/255, alpha: 1)
let temp = notificationsArray[indexPath.item]
// Setting id, time and message to labels
}
Well, since there is no API to support this, you cannot have this info cross device / multiscreen (if a user will open the notification in one device you will not be able to tell this this on another device without some other form communicating).
Need to save the info for a future app session
You can use UserDefaults.standard to save the state for a future session on the same device.
Create a notificationsStatus dictionary with the format [Id:read]. Read it when starting a new session and write in it the new updates statuses.
Save:
var notificationsStatus = ["101":true,"102":false]
let defaults:UserDefaults = UserDefaults.standard
defaults.set(notificationsStatus, forKey: "notifications")
Read:
notificationsStatus = defaults.value(forKey: "notifications")
and then use the read/unread to color the cells accordingly
Need to save the info for the same session
Use some structure where you save the state of a notification.
This can be done in multiple ways but the main idea is to create an object that doesn't get deallocated when leaving the controller and manipulate the date in it.

Sprite button and icon hover animation

I'm trying to create an effect over my custom buttons (SKSpriteNodes) and other UI objects that are also SKSpriteNode subclasses so that when the mouse hovers over them, they expand slightly to indicate that the user is hovering over them. Once the mouse leaves the vicinity of the sprite, the sprite should go back to normal size.
I initially tried this with the mouseMoved method, but undoing the scaling effect is proving to be an issue. Is there a better way to handle this maybe in the subclasses themselves? Ideas?
One way to do this is to simply keep track of the currently hovered node (if any). Something like this.
I've added a method to recursively search a nodes parents to try and find a suitable hoverable parent. SKSpriteNode in my example, but Producer in your case unless I'm mistaken.
class GameScene: SKScene {
var hoverNode: SKSpriteNode?
//Setup
override func mouseMoved(theEvent: NSEvent) {
let location = theEvent.locationInNode(self)
if let node = findHoverable(self.nodeAtPoint(location)) {
if (node == hoverNode) { return }
node.removeAllActions()
node.runAction(SKAction.scaleTo(1.2, duration: 0.5))
hoverNode = node
} else {
if let hoverNode = hoverNode {
hoverNode.removeAllActions()
hoverNode.runAction(SKAction.scaleTo(1.0, duration: 0.5))
self.hoverNode = nil
}
}
}
private func findHoverable(node: SKNode) -> SKSpriteNode? {
if let node = node as? SKSpriteNode {
return node
}
if let parent = node.parent {
if let parent = parent as? SKSpriteNode {
return parent
} else {
return findHoverable(parent)
}
} else {
return nil
}
}
}

setStatusBarHidden is deprecated in iOS 9.0

I am upgrading my code from iOS 8 to iOS 9. I have a code snippet in my program
[[UIApplication applicationName] setStatusBarHidden:YES];.
I am getting the warning "setStatusBarHidden is deprecated in iOS 9.0, Use -[UIViewController prefersStatusBarHidden". If I just replace 'setStatusBarHidden' with 'prefersStatusBarHidden', I get 'instance method not found'.
Can someone please suggest me how to solve this problem?
Add below code to your view controller..
- (BOOL)prefersStatusBarHidden {
return NO;
}
Note :
If you change the return value for this method, call the
setNeedsStatusBarAppearanceUpdate method.
For childViewController, To specify that a child view controller
should control preferred status bar hidden/unhidden state, implement
the childViewControllerForStatusBarHidden method.
prefersStatusBarHidden is available from iOS 7+.
Use this in Your UIViewController class
var isHidden = true{
didSet{
self.setNeedsStatusBarAppearanceUpdate()
}
}
override var prefersStatusBarHidden: Bool {
return isHidden
}
If you change the return value for this method, call the
setNeedsStatusBarAppearanceUpdate() method. To specify that a child
view controller should control preferred status bar hidden/unhidden
state, implement the childViewControllerForStatusBarHidden method.
you have to add method in yourViewController.m
- (BOOL)prefersStatusBarHidden {
return NO;
}
Swift 3.1 Xcode 8.2.1
Change in info.plist the row View controller-based status bar appearance and set it to NO
In your target settings tick "Hide Status bar"
Both steps are required
Here is my swift code for setting status bar hidden and style.
extension UIViewController {
public var privateStatusBarHidden: Bool {
return statusBarHidden
}
public var privateStatusBarStyle: UIStatusBarStyle {
return statusBarStyle
}
public func setStatusBarHidden(hidden: Bool, animated: Bool = false) {
statusBarHidden = hidden
if animated {
UIView.animate(withDuration: 0.25, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
} else {
self.setNeedsStatusBarAppearanceUpdate()
}
}
public func setStatusBar(style: UIStatusBarStyle) {
statusBarStyle = style
self.setNeedsStatusBarAppearanceUpdate()
}
public static func swizzleStatusBarHiddenPropertyForViewController() {
var original = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.prefersStatusBarHidden))
var changeling = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.privateStatusBarHidden))
method_exchangeImplementations(original, changeling)
original = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.preferredStatusBarStyle))
changeling = class_getInstanceMethod(UIViewController.self, #selector(getter: UIViewController.privateStatusBarStyle))
method_exchangeImplementations(original, changeling)
original = class_getClassMethod(UIViewController.self, #selector(UIViewController.swizzleStatusBarHiddenPropertyForViewController))
changeling = class_getClassMethod(UIViewController.self, #selector(UIViewController.emptyFunction))
method_exchangeImplementations(original, changeling)
}
#objc private static func emptyFunction() {}
}
Usage
in lauching function
UIViewController.swizzleStatusBarHiddenPropertyForViewController()
for hide/show statusBar, in UIViewController
. self.setStatusBar(hidden: true/false)
Swift 3 with Xcode 8.3.3
1) Add a row in you Info.plist.
2) In your ViewController ViewDidLoad() override add:
UIApplication.shared.isStatusBarHidden = true

Moving object to location of touch

How can I move the 'player' position to be where the user touches the screen? I want them to be able to move it constantly by tapping different places. This is what I have so far but it's not working:
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: "Spaceship")
override func didMoveToView(view: SKView) {
// 2
backgroundColor = SKColor.whiteColor()
// 3
player.position = CGPoint(x: size.width/2, y: size.height/2)
// 4
addChild(player)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let player = SKSpriteNode(imageNamed:"Spaceship")
player.position = location
}
}
In the line let player = SKSpriteNode(imageNamed:"Spaceship") in touchesBegan you are creating a new instance of the spaceship node.
This node is not in the scene and setting the position of the new node does not change the position of the existing player node.
Just try resetting the position of you player node instead.
Try
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch?
if let location = touch?.locationInNode(self)
{
player.position = location
}
}

Swift unique property in multiple instances

I know that theoretically it's possible to create multiple instances of the same class with a property that would have a different value for each instance.
The thing is, I can't make it happen.
Each time I'm creating a new instance, it gets the property's value of the other instances, and when I'm changing one value for an instance, it changes the other's too.
So my guess is that I'm doing something wrong (obviously), like accessing the class property value instead of the instance property value... Here's the code.
class CustomUIImageView: UIImageView {
var someParameter: Bool = false // This is the property I want to be different in each version of the instance.
}
class ClassSiege: UIViewController, UIGestureRecognizerDelegate {
var myView: CustomUIImageView! //the instance declaration.
// I use this gesture recognizer to find out the value of the instance I'm tapping on.
func handleTap (sender: UITapGestureRecognizer) {
print("value of someParameter \(self.myView.someParameter)")
}
func handlePan(recognizer: UIPanGestureRecognizer) {
let iv: UIView! = recognizer.view
let translation = recognizer.translationInView(self.view)
iv.center.x += translation.x
iv.center.y += translation.y
recognizer.setTranslation(CGPointZero, inView: self.view)
var centerBoardX = BlackBoard.center.x // 'Blackboard' is a fixed image on the screen.
var centerBoardY = BlackBoard.center.y
var centerRondX = iv.center.x
var centerRondY = iv.center.y
if centerRondY - centerBoardY < 100 {
self.myView.someParameter = true // If the distance between myView and the blackboard is under 100 I want the instance's property to become true.
} else {
self.myView.someParameter = false // On the other hand, if the distance is greater than 100, I want it to be false.
}
}
// When the user pushes a button, it triggers this method that creates a new instance of myView and add it to the screen.
#IBAction func showContent(sender: AnyObject) {
// some code...
// Here I'm creating the instance of the view and I give it the gesture recognizer parameters. I don't think that relevant to the issue, so I'm not adding the code.
}
}
So clearly that's not the good way to do it, but what's wrong, and how can it be solved?
Basing my answer on your related question.
If what you want to achieve is initializing a property with a value that you provide, just add a new parameter to the initializer. If for instance you are using the initializer with a CGRect passed in, then you can implement an initializer like this:
class CustomUIImageView : UIImageView {
let someParameter : Bool
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(frame: CGRect, someParameter: Bool) {
self.someParameter = someParameter
super.init(frame: frame)
}
}
I hope that this is what you are looking for - let me know otherwise.
I've found the solution, and if you've been facing the same issu, here's how to deal with it.
The secret is to downcast the recognizer.view to take the parameter of the subclass CustomUIImageView.
here's how :
func handleTap (sender: UITapGestureRecognizer) {
println("value of someParameter \(self.myView.someParameter)") //I use this gesture recognizer to find out the value of the instance I'm tapping on.
}
func handlePan(recognizer:UIPanGestureRecognizer) {
let iv : UIView! = recognizer.view
let translation = recognizer.translationInView(self.view)
iv.center.x += translation.x
iv.center.y += translation.y
recognizer.setTranslation(CGPointZero, inView: self.view)
var centerBoardX = BlackBoard.center.x //blackboard is a fixed image on the screen.
var centerBoardY = BlackBoard.center.y
var centerRondX = iv.center.x
var centerRondY = iv.center.y
var myParameter = recognizer.view as CustomUIImageView //<- this is the key point. Downcasting let you access the custom subclass parameters of the object that is currently moved
if centerRondY - centerBoardY < 100 {
myParameter.someParameter = true //so now I'm really changing the parameter's value inside the object rather than changing a global var like I did before.
} else {
myParameter.someParameter = false
}
}
//when user pushes a button, it triggers this func that creates a new instance of myView and add it to the screen.
#IBAction func showContent(sender: AnyObject) {
some code...
//here I'm creating the instance of the view and I give it the gesture recognizer parameters. I don't think that relevant to the issue, so I'm not adding the code.
}