UIView to UIImage code is not working. It causing an runtime error - swift

I have to convert UIView to UIImage because I want to make a custom marker.
Currently, I'm making an app using Naver Map SDK.
Here is My Code.
let marker = NMFMarker()
marker.position = NMGLatLng(lat: lat, lng: lng)
let windowView = CustomInfoWindowView()
windowView.nameLabel.text = name
windowView.jobLabel.text = job
windowView.scoreLabel.text = "\(score)"
marker.iconImage = NMFOverlayImage(image: windowView.asImage())
marker.mapView = mapView
NMFOverlayImage only supported UIImage type.
Here is My full code.
class MapViewController: UIViewController {
let viewModel = MapViewModel()
let locations = BehaviorRelay<Void>(value: ())
let disposedBag = DisposeBag()
private let imageCell = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
let mapView = NMFMapView(frame: view.frame)
view.addSubview(mapView)
bind(mapView: mapView)
}
private func bind(mapView: NMFMapView) {
let input = MapViewModel.Input(getLocations: locations.asSignal(onErrorJustReturn: ()))
let output = viewModel.transform(input)
output.getLocations.asObservable().subscribe(onNext: { [unowned self] res in
if !res.isEmpty {
for i in 0..<res.count {
addMarker(
lat: res[i].address[1],
lng: res[i].address[0],
mapView: mapView,
name: res[i].name,
job: res[i].field,
score: res[i].level
)
}
}
}).disposed(by: disposedBag)
}
private func addMarker(lat: Double, lng: Double, mapView: NMFMapView, name: String, job: String, score: Double) {
let marker = NMFMarker()
marker.position = NMGLatLng(lat: lat, lng: lng)
let windowView = CustomInfoWindowView()
windowView.nameLabel.text = name
windowView.jobLabel.text = job
windowView.scoreLabel.text = "\(score)"
marker.iconImage = NMFOverlayImage(image: windowView.asImage())
marker.mapView = mapView
}
}
and this is my custom view code.
import UIKit
import SnapKit
import Then
class CustomInfoWindowView: UIView {
let customView = UIView().then {
$0.backgroundColor = .clear
}
let windowView = UIView().then {
$0.backgroundColor = R.color.mainColor()
$0.layer.cornerRadius = 10
}
let marker = UIImageView().then {
$0.image = R.image.marker()
}
let nameLabel = UILabel().then {
$0.font = .boldSystemFont(ofSize: 16)
$0.textColor = .white
}
let jobLabel = UILabel().then {
$0.font = .systemFont(ofSize: 12, weight: .medium)
$0.textColor = R.color.underLine()!
}
let starLogo = UIImageView().then {
$0.image = UIImage(systemName: "star.fill")
$0.tintColor = .systemYellow
}
let scoreLabel = UILabel().then {
$0.font = .boldSystemFont(ofSize: 14)
$0.textColor = .white
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpSubViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func setUpSubViews() {
self.addSubview(customView)
[windowView, marker].forEach({customView.addSubview($0)})
[nameLabel, jobLabel, starLogo, scoreLabel].forEach({self.addSubview($0)})
customView.snp.makeConstraints {
$0.width.equalTo(108)
$0.height.equalTo(100)
}
windowView.snp.makeConstraints {
$0.width.equalToSuperview()
$0.height.equalTo(64)
$0.top.equalTo(0)
}
marker.snp.makeConstraints {
$0.width.equalTo(20)
$0.height.equalTo(27)
$0.top.equalTo(windowView.snp.bottom).offset(10)
$0.centerX.equalTo(windowView.snp.centerX)
}
nameLabel.snp.makeConstraints {
$0.top.leading.equalTo(10)
$0.width.equalTo(10)
}
jobLabel.snp.makeConstraints {
$0.centerY.equalTo(nameLabel.snp.centerY)
$0.leading.equalTo(nameLabel.snp.trailing).offset(5)
}
starLogo.snp.makeConstraints {
$0.top.equalTo(nameLabel.snp.bottom).offset(5)
$0.leading.equalTo(nameLabel.snp.leading)
$0.width.height.equalTo(15)
}
scoreLabel.snp.makeConstraints {
$0.centerY.equalTo(starLogo.snp.centerY)
$0.leading.equalTo(starLogo.snp.trailing).offset(3)
}
}
}
and this is my UIView Extension code.
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { context in
layer.render(in: context.cgContext)
}
}
}
I Use Snapkit, Then, RxSwift, RxCocoa,NMapsMap.
please Help me.
When I run the code .asImage(), It had runtime error.
enter image description here

Related

custom Google Maps blue dot in Swift

What I want to achieve is to customize google maps blue dot with an svg icon,
with the icon moving when location is changed.
what I am doing:
struct GoogleMapsView: UIViewRepresentable {
#ObservedObject var locationManager = LocationManager.shared
#State var myLocationDot = GMSMarker()
#Binding var nearbyDrivers: [NearbyDriver]
init(nearbyDrivers: Binding<[NearbyDriver]>){
self._nearbyDrivers = nearbyDrivers
myLocationDot.position = locationManager.userLocation!.coordinate
}
func makeUIView(context: Context) -> GMSMapView {
let camera = GMSCameraPosition.camera(withLatitude: locationManager.userLocation!.coordinate.latitude, longitude: locationManager.userLocation!.coordinate.longitude, zoom: 15)
let mapView = GMSMapView(frame: CGRect.zero, camera: camera)
myLocationDot.position = CLLocationCoordinate2D(latitude: locationManager.userLocation!.coordinate.latitude, longitude: locationManager.userLocation!.coordinate.longitude)
let myLocationDotIcon = UIImage(named: "myLocationDotIcon.svg")
let myLocationDotIconImage = UIImageView(image: myLocationDotIcon!)
myLocationDotIconImage.frame = CGRect(x: 0, y: 0, width: 40, height: 50)
myLocationDot.iconView = myLocationDotIconImage
myLocationDot.zIndex = 1
myLocationDot.map = mapView
// MARK: - Load nearby drivers
if !nearbyDrivers.isEmpty {
for driver in nearbyDrivers {
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: driver.location.lat, longitude: driver.location.long)
let locationIcon = UIImage(named: "carAnnotation.png")
let locationIconImage = UIImageView(image: locationIcon!)
locationIconImage.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
marker.iconView = locationIconImage
marker.zIndex = 0
marker.map = mapView
}
}
//MARK: - MapStyle
do {
if let styleURL = Bundle.main.url(forResource: "style", withExtension: "json") {
mapView.mapStyle = try GMSMapStyle(contentsOfFileURL: styleURL)
} else {
NSLog("Unable to find style.json")
}
} catch {
NSLog("One or more of the map styles failed to load. \(error)")
}
return mapView
}
func updateUIView(_ uiView: GMSMapView, context: Context) {
uiView.isMyLocationEnabled = false
}
func makeCoordinator() -> Coordinator {
Coordinator(myLocationDot: $myLocationDot, owner: self)
}
class Coordinator: NSObject, GMSMapViewDelegate, CLLocationManagerDelegate {
#ObservedObject var locationManager = LocationManager.shared
#Binding var myLocationDot: GMSMarker
let owner: GoogleMapsView
init(myLocationDot: Binding<GMSMarker>, owner: GoogleMapsView) {
self.owner = owner
_myLocationDot = myLocationDot
}
func didTapMyLocationButton(for mapView: GMSMapView) -> Bool {
mapView.animate(toLocation: locationManager.userLocation!.coordinate)
return true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
myLocationDot.position = locations.last!.coordinate
}
}
}
The idea is that I need to change the blue dot appearance but leave the functionalities as they need to be.
For some reason I can't find any course on advanced google maps customization. if you could refer a guy that does the thing, that would be awesome.

Why is my alert controller not dismissing in my Swift app?

I have a custom view controller class which presents an alert controller when it receives a not connected notification from my network services class. I want to dismiss the alert controller when I receive a further connected notification from the network services class but I am unable to dismiss the presenting alert.
Here is my custom view controller class;
class CompanyPriceListVC: UITableViewController {
// MARK: - Properties
let companyStockSymbols = ["AAPL","AMZN", "MSFT", "GOOGL", "TSLA", "META","COINBASE:BTC", "BINANCE:BTCUSDT", "BINANCE:BNBBTC", "IC MARKETS:1", "IC MARKETS:2"]
let defaultStockInfo = StockInfo(tradeConditions: nil, price: 0.00, symbol: "-", timestamp: 0.0, volume: 0.0)
lazy var companyStockInfo = [StockInfo](repeating: defaultStockInfo, count: companyStockSymbols.count)
var companyDetailsVC:CompanyDetailsVC?
var tableRowSelected: Int?
let defaultPriceHistory = PriceHistory(previous: nil, relative: 0.0, absolute: 0.0)
lazy var priceHistory = [PriceHistory](repeating: defaultPriceHistory , count: companyStockSymbols.count)
var deviceOrientation: Orientation = .portrait
let activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.style = .large
activityIndicator.hidesWhenStopped = true
activityIndicator.color = .white
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.startAnimating()
return activityIndicator
}()
let isConnected = Notification.Name(rawValue: isConnectedNotificationKey)
let isNotConnected = Notification.Name(rawValue: isDisconnectedNotificationKey)
let alertController = UIAlertController(title: "Connectivity Error", message: "Network connection lost. Please check your WiFi settings, mobile data settings or reception coverage.", preferredStyle: .alert)
// MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
if UIDevice.current.orientation.isPortrait {
self.deviceOrientation = .portrait
self.tableView.register(CompanyStockCellPortrait.self, forCellReuseIdentifier: "CompanyStockCellPortrait")
} else {
self.deviceOrientation = .landscape
self.tableView.register(CompanyStockCellLandscape.self, forCellReuseIdentifier: "CompanyStockCellLandscape")
}
configureNavigationBar()
view.backgroundColor = .black
configureTableView()
configureConstraints()
createObservers()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
private func createObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(CompanyPriceListVC.dismissAndFetchStockInfo), name: isConnected, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(CompanyPriceListVC.notifyUserOnScreen(notification:)), name: isNotConnected, object: nil)
}
#objc private func fetchStockInfo() {
NetworkServices.sharedInstance.fetchStockInfo(symbols: companyStockSymbols, delegate: self)
}
#objc private func notifyUserOnScreen(notification: NSNotification) {
self.present(alertController, animated: true)
}
#objc public func dismissAndFetchStockInfo() {
print("DEBUG: isBeingPresented: \(alertController.isBeingPresented)")
if alertController.isBeingPresented {
self.alertController.dismiss(animated: true, completion: nil)
}
fetchStockInfo()
}
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
coordinator.animate(alongsideTransition: { (context) in
guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }
if windowInterfaceOrientation.isPortrait {
self.deviceOrientation = .portrait
self.tableView.register(CompanyStockCellPortrait.self, forCellReuseIdentifier: "CompanyStockCellPortrait")
self.tableView.reloadData()
} else {
self.deviceOrientation = .landscape
self.tableView.register(CompanyStockCellLandscape.self, forCellReuseIdentifier: "CompanyStockCellLandscape")
self.tableView.reloadData()
}
})
}
private var windowInterfaceOrientation: UIInterfaceOrientation? {
return self.view.window?.windowScene?.interfaceOrientation
}
// MARK: - TableView Data Source Methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return companyStockInfo.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch deviceOrientation {
case .portrait:
let tableViewCellPortrait = tableView.dequeueReusableCell(withIdentifier: "CompanyStockCellPortrait", for: indexPath) as! CompanyStockCellPortrait
tableViewCellPortrait.symbolValue.text = companyStockInfo[indexPath.row].symbol
var latestPrice = companyStockInfo[indexPath.row].price
latestPrice = round(latestPrice * 100.0) / 100.00
tableViewCellPortrait.priceValue.text = String(format: "%.2f", arguments:[latestPrice])
if let _ = priceHistory[indexPath.row].previous {
let absoluteDifference = priceHistory[indexPath.row].absolute
let relativeDifference = priceHistory[indexPath.row].relative
if absoluteDifference > 0.0 {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.up.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .systemGreen
tableViewCellPortrait.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellPortrait.relativePriceDiffValue.textColor = .systemGreen
tableViewCellPortrait.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellPortrait.absolutePriceDiffValue.textColor = .systemGreen
} else if absoluteDifference < 0.0 {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.down.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .systemRed
tableViewCellPortrait.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellPortrait.relativePriceDiffValue.textColor = .systemRed
tableViewCellPortrait.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellPortrait.absolutePriceDiffValue.textColor = .systemRed
} else {
tableViewCellPortrait.priceDirectionImageView.image = UIImage(systemName: "diamond.fill")
tableViewCellPortrait.priceDirectionImageView.tintColor = .white
tableViewCellPortrait.relativePriceDiffValue.text = "0.00%"
tableViewCellPortrait.relativePriceDiffValue.textColor = .white
tableViewCellPortrait.absolutePriceDiffValue.text = "(0.00)"
tableViewCellPortrait.absolutePriceDiffValue.textColor = .white
}
} else {
tableViewCellPortrait.priceDirectionImageView.image = UIImage()
tableViewCellPortrait.priceDirectionImageView.tintColor = .white
tableViewCellPortrait.relativePriceDiffValue.text = ""
tableViewCellPortrait.absolutePriceDiffValue.text = ""
}
return tableViewCellPortrait
case .landscape:
let tableViewCellLandscape = tableView.dequeueReusableCell(withIdentifier: "CompanyStockCellLandscape", for: indexPath) as! CompanyStockCellLandscape
tableViewCellLandscape.symbolValue.text = companyStockInfo[indexPath.row].symbol
var latestPrice = companyStockInfo[indexPath.row].price
latestPrice = round(latestPrice * 100.0) / 100.00
tableViewCellLandscape.priceValue.text = String(format: "%.2f", arguments:[latestPrice])
if let _ = priceHistory[indexPath.row].previous {
let absoluteDifference = priceHistory[indexPath.row].absolute
let relativeDifference = priceHistory[indexPath.row].relative
if absoluteDifference > 0.0 {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.up.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .systemGreen
tableViewCellLandscape.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellLandscape.relativePriceDiffValue.textColor = .systemGreen
tableViewCellLandscape.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellLandscape.absolutePriceDiffValue.textColor = .systemGreen
} else if absoluteDifference < 0.0 {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "arrowtriangle.down.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .systemRed
tableViewCellLandscape.relativePriceDiffValue.text = String(format: "%.2f%%", arguments: [relativeDifference])
tableViewCellLandscape.relativePriceDiffValue.textColor = .systemRed
tableViewCellLandscape.absolutePriceDiffValue.text = String(format: "(%.2f)", arguments: [absoluteDifference])
tableViewCellLandscape.absolutePriceDiffValue.textColor = .systemRed
} else {
tableViewCellLandscape.priceDirectionImageView.image = UIImage(systemName: "diamond.fill")
tableViewCellLandscape.priceDirectionImageView.tintColor = .white
tableViewCellLandscape.relativePriceDiffValue.text = "0.00%"
tableViewCellLandscape.relativePriceDiffValue.textColor = .white
tableViewCellLandscape.absolutePriceDiffValue.text = "(0.00)"
tableViewCellLandscape.absolutePriceDiffValue.textColor = .white
}
} else {
tableViewCellLandscape.priceDirectionImageView.image = UIImage()
tableViewCellLandscape.priceDirectionImageView.tintColor = .white
tableViewCellLandscape.relativePriceDiffValue.text = ""
tableViewCellLandscape.absolutePriceDiffValue.text = ""
}
return tableViewCellLandscape
}
}
// MARK: - TableView Delegate Methods
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
companyDetailsVC = CompanyDetailsVC(companyStockInfo: companyStockInfo[indexPath.row])
tableRowSelected = indexPath.row
navigationController?.pushViewController(companyDetailsVC!, animated: true)
}
// MARK: - Helper Functions
private func configureNavigationBar() {
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navigationController?.navigationBar.topItem?.title = "Trade Tracker"
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.prefersLargeTitles = true
}
private func configureTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .singleLine
tableView.separatorColor = .white
tableView.tableFooterView = UIView()
}
private func configureConstraints() {
tableView.addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
activityIndicator.topAnchor.constraint(equalTo: tableView.topAnchor, constant: 250)
])
tableView.layoutSubviews()
}
}
// MARK: - Network Service Delegate Method
extension CompanyPriceListVC: NetworkServicesDelegate {
func sendStockInfo(stocksInfo: [String : StockInfo]) {
DispatchQueue.main.async {
// Compute absolute and relative price differences
for (index, symbol) in self.companyStockSymbols.enumerated() {
if stocksInfo[symbol] != nil {
self.priceHistory[index].previous = self.companyStockInfo[index].price
self.companyStockInfo[index] = stocksInfo[symbol]!
var latestPrice = self.companyStockInfo[index].price
latestPrice = round(latestPrice * 100.0) / 100.00
if let previous = self.priceHistory[index].previous {
let previousPrice = round(previous * 100.0) / 100.0
var absoluteDifference = latestPrice - previousPrice
absoluteDifference = round(absoluteDifference * 100.0) / 100.0
self.priceHistory[index].absolute = absoluteDifference
var relativeDifference = 0.0
if previousPrice != 0.00 {
relativeDifference = (absoluteDifference / previousPrice) * 100.0
relativeDifference = round(relativeDifference * 100) / 100.0
}
self.priceHistory[index].relative = relativeDifference
}
}
}
self.tableView.reloadData()
self.activityIndicator.stopAnimating()
guard let rowSelected = self.tableRowSelected else { return }
self.companyDetailsVC!.companyStockInfo = self.companyStockInfo[rowSelected]
self.companyDetailsVC!.relativeDifference = self.priceHistory[rowSelected].relative
self.companyDetailsVC!.absoluteDifference = self.priceHistory[rowSelected].absolute
}
}
}
and here is my network services class;
import UIKit
import Starscream
import Network
protocol NetworkServicesDelegate: AnyObject {
func sendStockInfo(stocksInfo: [String: StockInfo])
}
final class NetworkServices {
static let sharedInstance = NetworkServices()
var request = URLRequest(url: FINNHUB_SOCKET_STOCK_INFO_URL!)
var socket: WebSocket!
public private(set) var isConnected = false
var stocksInfo: [String: StockInfo] = [:]
var socketResults: [String: [StockInfo]] = [:]
weak var delegate: NetworkServicesDelegate?
var stockSymbols: [String] = []
private init() {
request.timeoutInterval = 5
socket = WebSocket(request: request)
socket.delegate = self
}
private let queue = DispatchQueue.global()
private let monitor = NWPathMonitor()
public func startMonitoring() {
monitor.start(queue: queue)
self.monitor.pathUpdateHandler = { [weak self] path in
if path.status == .satisfied {
// connect the socket
self?.socket.connect()
} else {
// disconnect the socket
self?.socket.disconnect()
// post notification that socket is now disconnected
DispatchQueue.main.async {
let name = Notification.Name(rawValue: isDisconnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
}
}
}
}
public func stopMonitoring() {
monitor.cancel()
}
func fetchStockInfo(symbols: [String], delegate: CompanyPriceListVC) {
stockSymbols = symbols
self.delegate = delegate
for symbol in symbols {
let string = FINNHUB_SOCKET_MESSAGE_STRING + symbol + "\"}"
socket.write(string: string)
}
}
private func parseJSONSocketData(_ socketString: String) {
self.socketResults = [:]
self.stocksInfo = [:]
let decoder = JSONDecoder()
do {
let socketData = try decoder.decode(SocketData.self, from: socketString.data(using: .utf8)!)
guard let stockInfoData = socketData.data else { return }
for stockInfo in stockInfoData {
let symbol = stockInfo.symbol
if self.socketResults[symbol] == nil {
self.socketResults[symbol] = [StockInfo]()
}
self.socketResults[symbol]?.append(stockInfo)
}
for (symbol, stocks) in self.socketResults {
for item in stocks {
if self.stocksInfo[symbol] == nil {
self.stocksInfo[symbol] = item
} else if item.timestamp > self.stocksInfo[symbol]!.timestamp {
self.stocksInfo[symbol] = item
}
}
}
self.delegate?.sendStockInfo(stocksInfo: self.stocksInfo)
} catch {
print("DEBUG: error: \(error.localizedDescription)")
}
}
func fetchCompanyInfo(symbol: String, completion: #escaping (CompanyInfo?, UIImage?)->()) {
let urlString = FINNHUB_HTTP_COMPANY_INFO_URL_STRING + symbol + "&token=" + FINNHUB_API_TOKEN
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error fetching company info: \(error)")
}
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let companyInfo = try decoder.decode(CompanyInfo.self, from: data)
guard let logoURL = URL(string: companyInfo.logo) else { return }
let task = URLSession.shared.dataTask(with: logoURL) { data, response, error in
if let error = error {
print("Error fetching logo image: \(error)")
}
guard let data = data else { return }
guard let logoImage = UIImage(data: data) else { return }
completion(companyInfo, logoImage)
}
task.resume()
} catch {
print("Error decoding JSON: \(error)")
completion(nil, nil)
}
}
task.resume()
}
}
extension NetworkServices: WebSocketDelegate {
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(_):
self.isConnected = true
// post notification that socket is now connected
let name = Notification.Name(rawValue: isConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
case .disconnected(let reason, let code):
print("DEBUG: Got disconnected.")
self.isConnected = false
case .cancelled:
print("DEBUG: cancelled.")
// post notification that socket is not connected
case .reconnectSuggested(let suggestReconnect):
print("DEBUG: suggestReconnect = \(suggestReconnect)")
// post notification that socket is not connected
case .viabilityChanged(let viabilityChanged):
print("DEBUG: viabilityChange = \(viabilityChanged)")
case .error(let error):
print("DEBUG error: \(String(describing: error?.localizedDescription))")
case .text(let socketString):
parseJSONSocketData(socketString)
default:
break
}
}
}
I've tried querying the isBeingPresented property of the alert controller but it always tests as false even though I can see the alert controller is being presented.
You could do a check if the currently presented UIViewController is actually the alertController like this:
#objc public func dismissAndFetchStockInfo() {
if presentedViewController == alertController {
alertController.dismiss(animated: true, completion: nil)
}
fetchStockInfo()
}
This is because isBeingPresented is only valid inside the view[Will|Did][Disa|A]ppear methods.

How to properly place SCNNode on top of the QR Code?

I want to detect QR codes in the vertical plane and place a node on top of the detected QR. For QR detection, I used Vision framework and Arkit to place the nodes as below code. Whenever placing the node, it is not attached to the QR code and placed somewhere else.
Could someone help to figure out what I have done wrong?
class ViewController: UIViewController, ARSCNViewDelegate,ARSessionDelegate{
#IBOutlet var sceneView: ARSCNView!
var qrRequests = [VNRequest]()
var detectedDataAnchor: ARAnchor?
var processing = false
let configuration = ARWorldTrackingConfiguration()
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.delegate = self
self.sceneView.session.delegate = self
self.sceneView.session.run(configuration)
startQrCodeDetection()
}
func startQrCodeDetection() {
let request = VNDetectBarcodesRequest(completionHandler: self.requestHandler)
self.qrRequests = [request]
}
public func session(_ session: ARSession, didUpdate frame: ARFrame) {
DispatchQueue.global(qos: .userInitiated).async {
do {
if self.processing {
return
}
self.processing = true
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: frame.capturedImage,
options: [:])
try imageRequestHandler.perform(self.qrRequests)
} catch {
}
}
}
func requestHandler(request: VNRequest, error: Error?) {
if let results = request.results, let result = results.first as? VNBarcodeObservation {
guard let payload = result.payloadStringValue else {return}
var rect = result.boundingBox
let center = CGPoint(x: rect.midX, y: rect.midY)
DispatchQueue.main.async {
self.hitTestQrCode(center: center)
self.processing = false
}
} else {
self.processing = false
}
}
func hitTestQrCode(center: CGPoint) {
print("Hit Test")
if let hitTestResults = self.sceneView?.hitTest(center, types: [.featurePoint, .existingPlaneUsingExtent] ),
let hitTestResult = hitTestResults.first {
if let detectedDataAnchor = self.detectedDataAnchor,
let node = self.sceneView.node(for: detectedDataAnchor) {
node.transform = SCNMatrix4(hitTestResult.worldTransform)
} else {
self.detectedDataAnchor = ARAnchor(transform: hitTestResult.worldTransform)
self.sceneView.session.add(anchor: self.detectedDataAnchor!)
}
}
}
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
if self.detectedDataAnchor?.identifier == anchor.identifier {
let sphere = SCNSphere(radius: 0.02)
sphere.firstMaterial?.diffuse.contents = UIColor.red
let sphereNode = SCNNode(geometry: sphere)
sphereNode.transform = SCNMatrix4(anchor.transform)
return sphereNode
}
return nil
}
}

How to fix MTKView random fill color when rendering a new image

I'm working on a photo editor and I just switched from a UIImageView to an MTKView for performance reasons. It works great except for some reason on some images there is a red box. The image size changes and the red box gets bigger when displaying a new image.
I am using Mac Catalyst on this
This is my MTKView code
public let colorSpace = CGColorSpaceCreateDeviceRGB()
public lazy var commandQueue: MTLCommandQueue = {
[unowned self] in
if self.device == nil {
self.device = MTLCreateSystemDefaultDevice()
}
return self.device!.makeCommandQueue()!
}()
public lazy var ciContext: CIContext = {
[unowned self] in
if self.device == nil {
self.device = MTLCreateSystemDefaultDevice()
}
let context = CIContext(mtlDevice: self.device!, options: [CIContextOption.highQualityDownsample: true, CIContextOption.priorityRequestLow: false])
return context
}()
public override init(frame frameRect: CGRect, device: MTLDevice? = MTLCreateSystemDefaultDevice()) {
super.init(frame: frameRect,
device: device ?? MTLCreateSystemDefaultDevice())
if super.device == nil
{
fatalError("Device doesn't support Metal")
}
self.framebufferOnly = false
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.framebufferOnly = false
self.device = MTLCreateSystemDefaultDevice()
}
/// The image to display
public var image: CIImage?
{
didSet
{
renderImage()
}
}
public var orientation: CGImagePropertyOrientation? {
didSet {
renderImage()
}
}
func renderImage()
{
guard var
image = self.image,
let targetTexture = self.currentDrawable?.texture else
{
print("No texture/image")
return
}
self.drawableSize = image.extent.size
if let orientation = orientation {
image = image.oriented(orientation)
}
let commandBuffer = self.commandQueue.makeCommandBuffer()
let bounds = CGRect(origin: CGPoint.zero, size: self.drawableSize)
let originX = image.extent.origin.x
let originY = image.extent.origin.y
let scaleX = self.drawableSize.width / image.extent.width
let scaleY = self.drawableSize.height / image.extent.height
let scale = min(scaleX, scaleY)
let scaledImage = image
.transformed(by: CGAffineTransform(translationX: -originX, y: -originY))
.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
self.ciContext.render(scaledImage,
to: targetTexture,
commandBuffer: commandBuffer,
bounds: bounds,
colorSpace: self.colorSpace)
commandBuffer?.present(self.currentDrawable!)
commandBuffer?.commit()
self.draw()
self.releaseDrawables()
}

Swift animated circular progress bar

I have created a circular progress bar in Swift that animated over 1.5 seconds to value 1 when user hold on view. But I want to add a new viewcontroller when animation is done and restart my circular progressbar if user ended to early. Can someone help me?
Circulars progress bar is working with animation when user hold on view and stop at release.
class CounterView: UIView {
var bgPath: UIBezierPath!
var shapeLayer: CAShapeLayer!
var progressLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
bgPath = UIBezierPath()
self.simpleShape()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
bgPath = UIBezierPath()
self.simpleShape()
}
func simpleShape()
{
createCirclePath()
shapeLayer = CAShapeLayer()
shapeLayer.path = bgPath.cgPath
shapeLayer.lineWidth = 5
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.clear.cgColor
progressLayer = CAShapeLayer()
progressLayer.path = bgPath.cgPath
progressLayer.lineCap = kCALineCapRound
progressLayer.lineWidth = 5
progressLayer.fillColor = nil
progressLayer.strokeColor = UIColor.yellow.cgColor
progressLayer.strokeEnd = 0.0
self.layer.addSublayer(shapeLayer)
self.layer.addSublayer(progressLayer)
}
private func createCirclePath()
{
let x = self.frame.width/2
let y = self.frame.height/2
let center = CGPoint(x: x, y: y)
print(x,y,center)
bgPath.addArc(withCenter: center, radius: x/CGFloat(2), startAngle: CGFloat(0), endAngle: CGFloat(6.28), clockwise: true)
bgPath.close()
}
var animationCompletedCallback: ((_ isAnimationCompleted: Bool) -> Void)?
func setProgressWithAnimation(duration: TimeInterval, value: Float) {
CATransaction.setCompletionBlock {
if let callBack = self.animationCompletedCallback { callBack(true) }
}
CATransaction.begin()
let animation = CABasicAnimation (keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0
animation.toValue = value
animation.repeatCount = 1
animation.timingFunction = CAMediaTimingFunction (name: kCAMediaTimingFunctionLinear)
progressLayer.strokeEnd = CGFloat(value)
progressLayer.add(animation, forKey: "animateprogress")
CATransaction.commit()
}
func removeLayers() {
shapeLayer.removeAllAnimations()
shapeLayer.removeFromSuperlayer()
progressLayer.removeAllAnimations()
progressLayer.removeFromSuperlayer()
}
}
class ViewController: UIViewController {
#IBOutlet weak var counterView: CounterView!
#IBOutlet weak var holdView: UIView!
var isAnimationCompleted = false
override func viewDidLoad() {
super.viewDidLoad()
addLongPressGesture()
addCounterViewCallback()
}
#objc func longPress(gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.began {
// self.counterView.simpleShape()
self.counterView.setProgressWithAnimation(duration: 1.5, value: 1.0)
}
if gesture.state == UIGestureRecognizerState.ended {
if !isAnimationCompleted {
self.counterView.removeLayers()
}
}
}
func addLongPressGesture(){
let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gesture:)))
lpgr.minimumPressDuration = 0
self.holdView.addGestureRecognizer(lpgr)
}
private func addCounterViewCallback() {
counterView.animationCompletedCallback = { [weak self] (isCompleted) in
guard let weakSelf = self else {return}
weakSelf.isAnimationCompleted = isCompleted
weakSelf.addFlashView()
}
}
func addFlashView(){
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let resultViewController = storyBoard.instantiateViewController(withIdentifier: "ResultView") as! Flash
self.present(resultViewController, animated:true, completion:nil)
}
Add new viewcontroller when animation is done and restart animation if user release view and hold on it again.
Add a callback to know when animation is ended. And use CATransaction to know when animation is completed.
var animationCompletedCallback: (() -> Void)?
func setProgressWithAnimation(duration: TimeInterval, value: Float) {
CATransaction.setCompletionBlock {
if let callBack = animationCompletedCallback {
callBack()
}
}
CATransaction.begin()
let animation = CABasicAnimation (keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0
animation.toValue = value
animation.repeatCount = .infinity
animation.timingFunction = CAMediaTimingFunction (name: kCAMediaTimingFunctionLinear)
progressLayer.strokeEnd = CGFloat(value)
progressLayer.add(animation, forKey: "animateprogress")
CATransaction.commit()
}
And add this function after addLongPressGesture() in viewDidLoad() :
private func addCounterViewCallback() {
counterView.animationCompletedCallback = { [weak self] in
guard let weakSelf = self else {return}
weakSelf.addFlashView()
}
}
To remove layer use this:
func removeLayers() {
shapeLayer.removeAllAnimations()
shapeLayer.removeFromSuperlayer()
progressLayer.removeAllAnimations()
progressLayer.removeFromSuperlayer()
}
Update 1:
To remove animation if user stops pressing, you need can add on variable in callback like this :
var animationCompletedCallback: ((isAnimationCompleted: Bool) -> Void)?
So now callback in CounterView will be :
if let callBack = animationCompletedCallback { callBack(true) }
In your controller add one variable:
var isAnimationCompleted = false
Change addCounterViewCallback() :
private func addCounterViewCallback() {
counterView.animationCompletedCallback = { [weak self] (isCompleted) in
guard let weakSelf = self else {return}
weakSelf.isAnimationCompleted = isCompleted
weakSelf.addFlashView()
}
}
Now you can add one condition in your longPress():
if gesture.state == UIGestureRecognizerState.ended {
if !isAnimationCompleted {
//Call remove layers code
}
}
Update 2:
Add a variable in CounterView:
var isAnimationCompleted = true
Change callback like this :
CATransaction.setCompletionBlock {
if let callBack = self.animationCompletedCallback { callBack(isAnimationCompleted) }
}
In controller longPress() :
if gesture.state == UIGestureRecognizerState.ended {
if !isAnimationCompleted {
self.counterView.isAnimationCompleted = false
self.counterView.removeLayers()
}
}
Modify addCounterViewCallback() to this:
private func addCounterViewCallback() {
counterView.animationCompletedCallback = { [weak self] (isCompleted) in
guard let weakSelf = self else {return}
weakSelf.isAnimationCompleted = isCompleted
if isCompleted {
weakSelf.addFlashView()
}
}
}