How to show directions in SwiftUI Map() - swiftui

I am using Map in SwiftUI 3 and Xcode 13+
import SwiftUI
import CoreLocation
import CoreLocationUI
import MapKit
Map(coordinateRegion: $mapViewModel.region,
interactionModes: [.all],
showsUserLocation: true,
userTrackingMode: .constant(.none),
annotationItems: annotations) { place in
MapAnnotation(coordinate: place.placemark.location!.coordinate) {
MapPinView()
}
}
.tint(.blue)
.environmentObject(mapViewModel)
.edgesIgnoringSafeArea(.all)
.onAppear {
mapViewModel.checkLocationServicesIsEnabled()
}
So, here I am using Map(...)
Is there any example for showing a route between 2 points?
Thanks

From this tutorial https://www.youtube.com/watch?v=H6pmm62axCg
You have the sources here: https://github.com/apatronl/YouTube/blob/main/Directions/Directions/ContentView.swift
And here is the relevant code snippet:
let request = MKDirections.Request()
request.source = MKMapItem(placemark: p1)
request.destination = MKMapItem(placemark: p2)
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { response, error in
guard let route = response?.routes.first else { return }
mapView.addAnnotations([p1, p2])
mapView.addOverlay(route.polyline)
mapView.setVisibleMapRect(
route.polyline.boundingMapRect,
edgePadding: UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20),
animated: true)
self.directions = route.steps.map { $0.instructions }.filter { !$0.isEmpty }
}

Related

In SwiftUI, how do you filter a list on a subview when passing data through a NavigationLink?

I am new to SwiftUI and programming in general. I am trying to pass data and create navigation between different views in my app.
For my data model, I am using MVVM format even though my data is entirely static right now. I have two data models that I am trying to connect via enumeration: CategoryModel and SubCategoryModel (see below).
CategoryModel:
import Foundation
import SwiftUI
enum Category: String, CaseIterable, Identifiable {
var id: String { self.rawValue }
case explicit = "Explicit"
case adventure = "Adventure"
case artists = "Artists"
case holidays = "Holidays"
case movies = "Movies"
case people = "People"
case tasks = "Tasks"
case feelings = "Feelings"
case lifestyle = "Lifesytle"
case party = "Party"
case sports = "Sports"
}
//Root data -> set up data structure
//make Identifiable for ForEach looping in other views
struct CategoryModel: Identifiable {
let id = UUID().uuidString
let category: Category
let categoryTitle: String
let description: String
let isPurchase: Bool
let categoryImage: Image
}
class CategoryViewModel: ObservableObject {
#Published var gameCategories: [CategoryModel] = CategoryModel.all
#Published var filteredPurchase: [CategoryModel] = []
init() {
filterByPurchase()
}
func filterByPurchase() {
filteredPurchase = gameCategories.filter({ $0.isPurchase })
}
}
extension CategoryModel {
static let all = [
CategoryModel(
category: .explicit,
categoryTitle: "Adults Only",
description: "For those spicy, intimate moments.",
isPurchase: true,
categoryImage: Image(uiImage: #imageLiteral(resourceName: "Explicit"))
),
CategoryModel(
category: .adventure,
categoryTitle: "Call of the Wild",
description: "[Insert description here]",
isPurchase: false,
categoryImage: Image(uiImage: #imageLiteral(resourceName: "Adventure"))
),
CategoryModel(
category: .artists,
categoryTitle: "By the Artist",
description: "[Insert description here]",
isPurchase: false,
categoryImage: Image(uiImage: #imageLiteral(resourceName: "Artists"))
),
]
}
SubCategoryModel:
import Foundation
import SwiftUI
//Root data -> set up data structure
//make Identifiable for ForEach looping in other views
struct SubCategoryModel: Identifiable {
let id = UUID().uuidString
let category: Category
let subcategory: String
let subtitle: String
let subdescription: String
let instruction: String
let cardImage: Image
}
class SubCategoryViewModel: ObservableObject {
#Published var gameSubCategories: [SubCategoryModel] = SubCategoryModel.allSubs
#Published var filteredCategory: [SubCategoryModel] = []
#Published var categoryType: Category = .explicit
init() {
filterByCategory()
}
func filterByCategory() {
DispatchQueue.global(qos: .userInteractive).async {
let results = self.gameSubCategories
.lazy
.filter { item in
return item.category == self.categoryType
}
DispatchQueue.main.async {
self.filteredCategory = results.compactMap({ item in
return item
})
}
}
}
}
extension SubCategoryModel {
static let allSubs = [
SubCategoryModel(
category: .explicit,
subcategory: "Significant Other",
subtitle: "Bedroom Eyes",
subdescription: "[Insert sub-description here]",
instruction: "Instructions:\n \n1) Each player pick a song\n2) Be funny, genuine, or a maverick\n3) Enjoy the trip down memory lane",
cardImage: Image(uiImage: #imageLiteral(resourceName: "Explicit"))
),
SubCategoryModel(
category: .explicit,
subcategory: "Dating",
subtitle: "First Date",
subdescription: "[Insert sub-description here]",
instruction: "Instructions:\n \n1) Each player pick a song\n2) Be funny, genuine, or a maverick\n3) Enjoy the trip down memory lane",
cardImage: Image(uiImage: #imageLiteral(resourceName: "Explicit"))
),
SubCategoryModel(
category: .adventure,
subcategory: "Hiking",
subtitle: "Bedroom Eyes",
subdescription: "[Insert sub-description here]",
instruction: "Instructions:\n \n1) Each player pick a song\n2) Be funny, genuine, or a maverick\n3) Enjoy the trip down memory lane",
cardImage: Image(uiImage: #imageLiteral(resourceName: "Adventure"))
),
]
}
My goal is to click on a card from the CategoryView screen and navigate to the SubCategoryView via a navigation link. I want the SubCategoryView to show a filtered list of subcategories based on the category selected on the CategoryView screen.
CategoryView to SubCategoryView GIF
CategoryLoop code snippet:
struct CategoryLoop: View {
let categories: [CategoryModel]
var body: some View {
ZStack {
ScrollView {
VStack {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150), spacing: 20)], spacing: 20) {
ForEach(categories) { item in
NavigationLink(destination: SubCategoryView()
.navigationTitle(item.category.rawValue)) {
CategoryCard(category: item)
}
}
}
Based on the code in my CategoryLoop file, what is the best/easiest way to pass my model data and filter the list on the SubCategoryView? I am having trouble figuring out how to use the enumeration. Is it possible to write a function that would update #Published var categoryType: Category = .explicit (see SubCategoryModel) based on the card clicked in the LazyVGrid? Also, if you have suggestion on how to better organise my data models, please let me know.

How Can I Solve 'Notifications not accepted. You can turn them on later under your iOS settings' Issue?

I'm using onesignal for push notifications and it was working well until the ios 14 upgrade. When I build the same app without any changes on Xcode 12, I got this warning in the console.
Notifications not accepted. You can turn them on later under your iOS
settings
There was no problem on iOS 13, it happened when I update to iOS 14.
AppDelegate.swift
import UIKit
import CoreData
import Firebase
import GoogleMobileAds
import OneSignal
import UserNotifications
import SDWebImageWebPCoder
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, OSPermissionObserver, OSSubscriptionObserver {
var window: UIWindow?
var shortcutItemToProcess: UIApplicationShortcutItem?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
GADMobileAds.sharedInstance().start(completionHandler: nil)
let onesignalInitSettings = [kOSSettingsKeyAutoPrompt: false, kOSSettingsKeyInAppLaunchURL: false]
OneSignal.initWithLaunchOptions(launchOptions,
appId: "my key is here",
handleNotificationAction: nil,
settings: onesignalInitSettings)
OneSignal.inFocusDisplayType = OSNotificationDisplayType.notification
OneSignal.promptForPushNotifications(userResponse: { accepted in
print("User accepted notifications: \(accepted)")
})
// Add your AppDelegate as an obsserver
OneSignal.add(self as OSPermissionObserver)
OneSignal.add(self as OSSubscriptionObserver)
registerForPushNotifications()
let WebPCoder = SDImageWebPCoder.shared
SDImageCodersManager.shared.addCoder(WebPCoder)
return true
}
func registerForPushNotifications() {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.delegate = self
let readAction = UNNotificationAction(identifier: "oku", title: "Haberi Oku", options: [.foreground])
let closeAction = UNNotificationAction(identifier: "kapat", title: "Kapat", options: [])
let category = UNNotificationCategory(identifier: "etkilesim", actions: [readAction, closeAction], intentIdentifiers: [], options: [])
notificationCenter.setNotificationCategories([category])
}
func onOSPermissionChanged(_ stateChanges: OSPermissionStateChanges!) {
if stateChanges.from.status == OSNotificationPermission.notDetermined {
if stateChanges.to.status == OSNotificationPermission.authorized {
print("Thanks for accepting notifications!")
} else if stateChanges.to.status == OSNotificationPermission.denied {
print("Notifications not accepted. You can turn them on later under your iOS settings.")
}
}
}
func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges!) {
if !stateChanges.from.subscribed && stateChanges.to.subscribed {
print("Subscribed for OneSignal push notifications!")
}
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
// Grab a reference to the shortcutItem to use in the scene
if let shortcutItem = options.shortcutItem {
shortcutItemToProcess = shortcutItem
}
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
var postId:String = ""
var postType:String = ""
if let custom = response.notification.request.content.userInfo["custom"] as? NSDictionary{
if let a = custom["a"] as? NSDictionary{
if let id = a["id"] as? String{
postId = id
}
if let type = a["rights"] as? String{
postType = type
}
}
}
if response.actionIdentifier == "oku" {
if postId != ""{
DispatchQueue.main.async(execute: {
NotificationCenter.default.post(name: NSNotification.Name("Detail"), object: nil, userInfo: ["id": postId, "type": postType])
})
completionHandler()
}
}else if response.actionIdentifier == "kapat" {
print("KAPAT")
completionHandler()
} else {
if postId != ""{
DispatchQueue.main.async(execute: {
NotificationCenter.default.post(name: NSNotification.Name("Detail"), object: nil, userInfo: ["id": postId, "type": postType])
})
completionHandler()
}
}
}
}
I solved it! :) If your app name contains non-English characters, change your Product Name under Build Settings and build it again, that's it :)
Settings
Then you can change 'Bundle display name' in info.plist.

Define global variable with Alamofire + SwiftyJSON

I'm using Alamofire for requests and i'm using swiftyjson for json parsing.
I need define global variables for other view controllers.
I have this code:
import UIKit
import Alamofire
import SwiftyJSON
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
struct Settings {
static var registration_url = String();
static var login_url = String();
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Alamofire.request("http://localhost/settings.php").responseJSON { response in
if response.result.isSuccess {
let json = JSON(data: response.data!);
Settings.registration_url = json["registration_url"].stringValue;
Settings.login_url = json["login_url"].stringValue;
} else {
Settings.registration_url = "http://localhost/register.php";
Settings.login_url = "http://localhost/login.php";
}
}
print(Settings.registration_url);
print(Settings.login_url)
return true
}
}
I'm checking debug window, and print(Settings.registration_url); is looks blank
Why?
Thanks. Sorry for my poor english.
Your Settings structure is not global. You need to define it outside the class for it to be global:
struct Settings {
static var registration_url = String();
static var login_url = String();
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Alamofire.request("http://localhost/settings.php").responseJSON { response in
if response.result.isSuccess {
let json = JSON(data: response.data!);
Settings.registration_url = json["registration_url"].stringValue;
Settings.login_url = json["login_url"].stringValue;
} else {
Settings.registration_url = "http://localhost/register.php";
Settings.login_url = "http://localhost/login.php";
}
}
print(Settings.registration_url);
print(Settings.login_url)
return true
}
}

How do I count the MenuItems when testing Reacts material-ui IconMenu?

I would like to check the number of MenuItems in the popover of the IconMenu when it gets clicked but I'm not sure how to access the 'PopoverDefaultAnimation' or am not sure about the best way to do this. Any help would be appreciated. When I try to console.log the menuitems, the result is []
My JSX file,
import React, {PropTypes} from 'react'
/** material-ui **/
import IconMenu from 'material-ui/IconMenu'
import IconButton from 'material-ui/IconButton'
import MenuItem from 'material-ui/MenuItem'
import Divider from 'material-ui/Divider'
import Help from 'material-ui/svg-icons/action/help-outline'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
export default class MndyHelp extends React.Component{
constructor(props) {
//console.log('Main: constructor()');
super(props);
}
static childContextTypes = {
muiTheme: React.PropTypes.object
}
getChildContext() {
return {
muiTheme: getMuiTheme()
}
}
render(){
var urlLink = "https://www.google.com";
return(
<IconMenu
iconButtonElement={
<IconButton style={ {padding: 0, width: "auto", height: "auto", right: 44, top: 4 } } iconStyle={{ height: 30, width: 30, fill: "#304954"}}><Help/></IconButton>}>
<MenuItem onTouchTap={() => {window.open(urlLink, '_blank');}} primaryText='Item1'/>
<MenuItem onTouchTap={() => {window.open(urlLink, '_blank');}} primaryText='Item2'/>
</IconMenu>
);
}
}
My unit test
import React from 'react'
import {renderIntoDocument,
scryRenderedDOMComponentsWithTag,
scryRenderedComponentsWithType,
Simulate
} from 'react-addons-test-utils'
import chai from 'chai'
import ReactDOM from 'react-dom'
import IconButton from 'material-ui/IconButton'
import IconMenu from 'material-ui/IconMenu'
import MenuItem from 'material-ui/MenuItem'
import Popover from 'material-ui/Popover';
import Help from 'material-ui/svg-icons/action/help-outline'
import injectTapEventPlugin from 'react-tap-event-plugin';
var should = chai.should(),
expect = chai.expect;
import MndyHelp from './MndyHelp.jsx';
describe('<MndyHelp/>', () => {
injectTapEventPlugin();
it('should have 2 menuItems', () => {
var domElement = renderIntoDocument(<MndyHelp/>),
buttons = scryRenderedComponentsWithType(domElement,IconButton),
firstButton = ReactDOM.findDOMNode(buttons[0]);
Simulate.touchTap(firstButton);
var popOver = scryRenderedComponentsWithType(domElement,Popover);
var menuItem = scryRenderedComponentsWithType(domElement,MenuItem);
//make sure popover is open i.e. true
expect(popOver[0].props.open).to.equal(true);
//Make sure menu items exist
console.log(menuItem); //----> this prints [] instead of the menuitems
expect(menuItem.length).to.not.equal(0);
expect(menuItem.length).to.equal(2);
});
});

Famo.us Timbre app Scrollview

I'm new to Famo.us and I am trying to expand on the Timbre sample app by adding a scrollview to the PageView where the image would be (in the _createBody function). In other words, I'm trying to add a feed similar to Facebook or Tango, etc. I found two pieces of code online that's been working with (links below). I get no errors on the console log, yet the scrollview won't display, so I'm not sure what I am missing. Your guidance is much appreciated (would also love to know if there is a better way). Finally, this is my first post ever on StackOverflow, so please let me know if I can expose my issue in a better fashion.
Links I have been using for guidance:
StackOverflowFamo.us swipe on scrollview
JSFiddle
/*** AppView.js ***/
define(function(require, exports, module) {
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var StateModifier = require('famous/modifiers/StateModifier');
var Easing = require('famous/transitions/Easing');
var Transitionable = require('famous/transitions/Transitionable');
var GenericSync = require('famous/inputs/GenericSync');
var MouseSync = require('famous/inputs/MouseSync');
var TouchSync = require('famous/inputs/TouchSync');
GenericSync.register({'mouse': MouseSync, 'touch': TouchSync});
var PageView = require('views/PageView');
var MenuView = require('views/MenuView');
var StripData = require('data/StripData');
function AppView() {
View.apply(this, arguments);
this.menuToggle = false;
this.pageViewPos = new Transitionable(0);
_createPageView.call(this);
_createMenuView.call(this);
_setListeners.call(this);
_handleSwipe.call(this);
}
AppView.prototype = Object.create(View.prototype);
AppView.prototype.constructor = AppView;
AppView.DEFAULT_OPTIONS = {
openPosition: 276,
transition: {
duration: 300,
curve: 'easeOut'
},
posThreshold: 138,
velThreshold: 0.75
};
function _createPageView() {
this.pageView = new PageView();
this.pageModifier = new Modifier({
transform: function() {
return Transform.translate(this.pageViewPos.get(), 0, 0);
}.bind(this)
});
this._add(this.pageModifier).add(this.pageView);
}
function _createMenuView() {
this.menuView = new MenuView({ stripData: StripData });
var menuModifier = new StateModifier({
transform: Transform.behind
});
this.add(menuModifier).add(this.menuView);
}
function _setListeners() {
this.pageView.on('menuToggle', this.toggleMenu.bind(this));
}
function _handleSwipe() {
var sync = new GenericSync(
['mouse', 'touch'],
{direction : GenericSync.DIRECTION_X}
);
this.pageView.pipe(sync);
sync.on('update', function(data) {
var currentPosition = this.pageViewPos.get();
if(currentPosition === 0 && data.velocity > 0) {
this.menuView.animateStrips();
}
this.pageViewPos.set(Math.max(0, currentPosition + data.delta));
}.bind(this));
sync.on('end', (function(data) {
var velocity = data.velocity;
var position = this.pageViewPos.get();
if(this.pageViewPos.get() > this.options.posThreshold) {
if(velocity < -this.options.velThreshold) {
this.slideLeft();
} else {
this.slideRight();
}
} else {
if(velocity > this.options.velThreshold) {
this.slideRight();
} else {
this.slideLeft();
}
}
}).bind(this));
}
AppView.prototype.toggleMenu = function() {
if(this.menuToggle) {
this.slideLeft();
} else {
this.slideRight();
this.menuView.animateStrips();
}
};
AppView.prototype.slideLeft = function() {
this.pageViewPos.set(0, this.options.transition, function() {
this.menuToggle = false;
}.bind(this));
};
AppView.prototype.slideRight = function() {
this.pageViewPos.set(this.options.openPosition, this.options.transition, function() {
this.menuToggle = true;
}.bind(this));
};
module.exports = AppView;
});
/*** PageView.js ***/
define(function(require, exports, module) {
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
var Transform = require('famous/core/Transform');
var StateModifier = require('famous/modifiers/StateModifier');
var HeaderFooter = require('famous/views/HeaderFooterLayout');
var ImageSurface = require('famous/surfaces/ImageSurface');
var Scrollview = require('famous/views/Scrollview');
function PageView() {
View.apply(this, arguments);
_createBacking.call(this);
_createLayout.call(this);
_createHeader.call(this);
_createBody.call(this);
_setListeners.call(this);
}
PageView.prototype = Object.create(View.prototype);
PageView.prototype.constructor = PageView;
PageView.DEFAULT_OPTIONS = {
headerSize: 44
};
function _createBacking() {
var backing = new Surface({
properties: {
backgroundColor: 'black',
boxShadow: '0 0 20px rgba(0,0,0,0.5)'
}
});
this.add(backing);
}
function _createLayout() {
this.layout = new HeaderFooter({
headerSize: this.options.headerSize
});
var layoutModifier = new StateModifier({
transform: Transform.translate(0, 0, 0.1)
});
this.add(layoutModifier).add(this.layout);
}
function _createHeader() {
var backgroundSurface = new Surface({
properties: {
backgroundColor: 'black'
}
});
this.hamburgerSurface = new ImageSurface({
size: [44, 44],
content : 'img/hamburger.png'
});
var searchSurface = new ImageSurface({
size: [232, 44],
content : 'img/search.png'
});
var iconSurface = new ImageSurface({
size: [44, 44],
content : 'img/icon.png'
});
var backgroundModifier = new StateModifier({
transform : Transform.behind
});
var hamburgerModifier = new StateModifier({
origin: [0, 0.5],
align : [0, 0.5]
});
var searchModifier = new StateModifier({
origin: [0.5, 0.5],
align : [0.5, 0.5]
});
var iconModifier = new StateModifier({
origin: [1, 0.5],
align : [1, 0.5]
});
this.layout.header.add(backgroundModifier).add(backgroundSurface);
this.layout.header.add(hamburgerModifier).add(this.hamburgerSurface);
this.layout.header.add(searchModifier).add(searchSurface);
this.layout.header.add(iconModifier).add(iconSurface);
}
function _createBody() {
var surfaces = [];
this.scrollview = new Scrollview();
var temp;
for (var i = 0; i < 30; i++) {
temp = new Surface({
size: [undefined, 80],
content: 'Surface: ' + (i + 1),
properties: {
textAlign: 'left',
lineHeight: '80px',
borderTop: '1px solid #000',
borderBottom: '1px solid #fff',
backgroundColor: '#ffff00',
fontFamily: 'Arial',
backfaceVisibility: 'visible',
paddingLeft: '10px'
}
});
temp.pipe(this.scrollview);
surfaces.push(temp);
}
this.scrollview.sequenceFrom(surfaces);
this.bodyContent = new Surface({
size: [undefined, undefined],
properties: {
backgroundColor: '#f4f4f4'
}
});
this.layout.content.add(this.bodyContent);
}
function _setListeners() {
this.hamburgerSurface.on('click', function() {
this._eventOutput.emit('menuToggle');
}.bind(this));
//this.bodyContent.pipe(this._eventOutput);
this.scrollview.pipe(this._eventOutput);
}
module.exports = PageView;
});
You need to add this.scrollview to your layout.content element on the page. Put this in place of this.bodyContent. layout.content is the node for the content of the page.
//this.layout.content.add(this.bodyContent);
this.layout.content.add(this.scrollview);