How to get a current volume of iPhone and change it while playing music using swift? - swift

In my app I need to change volume with slider, I've got current volume with
#IBOutlet weak var volumeChange: UISlider! {
didSet {
let audioSession = AVAudioSession.sharedInstance()
let volume : Float = audioSession.outputVolume
volumeChange.setValue(volume, animated: true)
}
}
How can I change it? Now I have this method
#IBAction func handleVolumeChange(_ sender: UISlider) {
player.volume = sender.value
}
This method doesn't work in this case. Is it possible to make it like for example in Music app: changing volume in app with changing volume on device?

To get the volume then you will need to use AVAudioSession.sharedInstance().outputVolume as you already have used.
In order to allow the user to control the volume you will want to have a look at MPVolumeView found in the Media Player framework. This component is able to make changes to the system volume.
It is very simple to use:
let volumeView = MPVolumeView(frame: volumeViewSize)
playerView.addSubview(volumeView)
You will want to use this class instead of using your UISlider instance. There are methods supplied that let you override the look and feel of the slider.
Additionally this class exposes a control which allows the user to choose the output route (iPhone, Airpods, Homepod, or Apple TV for example). You can choose to disable either the slider or the output route control so this gives you a fairly broad set of options with customising your user interface.
If you are working with a storyboard then you will need to add it in code. First create an empty UIView in the view controller's view on the storyboard and attach that to your view controller:
Once you've added that you will want something like the following in your view controller:
#IBOutlet var volumeSliderContainer: UIView!
private lazy var volumeView: MPVolumeView = {
let volumeView = MPVolumeView(frame: volumeSliderContainer.bounds)
volumeView.showsVolumeSlider = true
volumeView.showsRouteButton = true
volumeView.translatesAutoresizingMaskIntoConstraints = false
return volumeView
}()
override func viewDidLoad() {
super.viewDidLoad()
volumeSliderContainer.addSubview(volumeView)
NSLayoutConstraint.activate([
volumeView.widthAnchor.constraint(equalTo: volumeSliderContainer.widthAnchor),
volumeView.heightAnchor.constraint(equalTo: volumeSliderContainer.heightAnchor),
volumeView.centerXAnchor.constraint(equalTo: volumeSliderContainer.centerXAnchor),
volumeView.centerYAnchor.constraint(equalTo: volumeSliderContainer.centerYAnchor),
])
}
Now you will be able to interact with the MPVolumeView as you require.
Just watch out on the simulator, the component will not render the slider and you will only be able to see it when running your app on an actual device.

get current system volume -
var session = AVAudioSession.sharedInstance()
override func viewDidLoad() {
super.viewDidLoad()
if ((try? session.setActive(true)) != nil) {
volumeChange.setValue(session.outputVolume, animated: true)
}
}

Related

Can I change outlets using an array, instead of hard coding everything?

I have an app where a user can select a number of different buttons onscreen. When a user selects a button, it turns green and the text will be used in a later view. I am trying to make everything nice and swift by minimising the amount of code I am writing.
Every button is connected to the same action and their identity is determined by their tag. What I have done is created 2 arrays to track the card name and their on/off state. When a card is pressed the cardPressed function is called, this decides whether to turn the card green or white currently (it will do more later).
What I want to do is to perform the colour change in one line of code, instead of
cardOne.backgroundColor = UIColor.white
I want to do this [#1]
cardList[cardNumber].backgroundColor = UIColor.green
so that my outlet changes depending on the selection made. I would normally just have a massive switch statement that would read like so
switch cardList[cardNumber] {
case 0:
cardOne.backgroundColor = UIColor.green
case 1:
cardTwo.backgroundColor = UIColor.green
case 2:
cardThree.backgroundColor = UIColor.green
case So on so forth:
cardInfinity.......
default:
break
}
Obviously when I try to do [#1] I get an error because it is a string, not an outlet connection. What I would like to know, is there anyway to trick xcode into recognising it as an outlet, or better yet have a way to change the outlets I am acting upon in one line of code?
Hopefully I haven't rambled too much and you can understand my thought process! I have included all of the relevant code below, obviously it won't compile. If you have any ideas they would be appreciated, or if I'm being too optimistic and this isnt possible, just let me know :) for now I will be using a big switch statement! (maybe this is useful to me in the future!)
Thanks!
private let cardList = ["cardOne","cardTwo","cardThree"]
private var cardState = [false, false, false]
//Card functions
private func selectCard(cardNumber: Int){
cardState[cardNumber] = true
cardList[cardNumber].backgroundColor = UIColor.green
}
private func deselectCard(cardNumber: Int){
cardState[cardNumber] = false
//cardOne.backgroundColor = UIColor.white
}
//Decide which function to perform, based on the card information recieved
private func cardPressed(cardNumber: Int){
let selectedCardName = cardList[cardNumber]
let selectedCardState = cardState[cardNumber]
print("\(selectedCardName)")
print("\(selectedCardState)")
switch selectedCardState {
case true:
deselectCard(cardNumber: cardNumber)
case false:
selectCard(cardNumber: cardNumber)
}
}
//UI Connections
//Card button actions
#IBAction func buttonPressed(_ sender: UIButton) {
//Determine which button has been pressed
//let cardName = sender.currentTitle!
let cardSelection = sender.tag - 1
cardPressed(cardNumber: cardSelection)
}
//Card button outlets
#IBOutlet weak var cardOne: UIButton!
#IBOutlet weak var cardTwo: UIButton!
The solution lies in the wonderful world of object-oriented programming. Instead of using parallel arrays, you can create your own data type to group this data and behavior together.
If you created your own UIButton subclass, you could keep track of whether the button is selected with your own custom property, and make visual modifications as needed.
class CardButton: UIButton {
var isChosen: Bool = false {
didSet { backgroundColor = isChosen ? UIColor.green : UIColor.white }
}
}
If you set the buttons in the storyboard to be your new CardButton type, you can use their isChosen property in code.
Your buttonPressed function could look like this instead:
#IBAction func buttonPressed(_ sender: CardButton) {
sender.isChosen = !sender.isChosen
}
This would allow you to remove the majority of your existing code, since the data is stored inside each of your buttons.

OSX Application: how to make window maximized?

I am working on a mac application, and I like to make initial window be in maximized state, like when you are pressing green button with plus sign.
I don't want it to be full screen.
An app in its zoomed state is not the same thing as "maximized." The green plus icon indicates zoom, which means "the appropriate size for this content." In some applications that's the visible frame (as Eric D. discusses), but it can be almost anything. Try zooming a Safari window for instance.
Assuming you really want "maximized" and not "zoom", then Eric is on the right track, but it can be done better. First, you should use the window's screen if it has one. Also, you should not animate the window resize during launch (since that can look awkward on launch).
func applicationDidFinishLaunching(aNotification: NSNotification) {
if let screen = window.screen ?? NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true)
}
}
You may want to consider using a NSWindowController to manage this rather than putting it in the application delegate. In that case, you can put this in windowDidLoad. Window controllers are a pretty common tool in AppKit (as opposed to view controllers, which are not historically as common).
If you actually want zoom behavior, familiarize yourself with the the NSWindowDelegate method windowWillUseStandardFrame(_:defaultFrame:). You shouldn't generally call zoom(_:) directly on launch because that will animate, but whatever logic you do in the delegate should be used to compute your frame. Again, make sure to adjust your frame to live on the window's screen if it has one, rather than the main screen.
Ideally, you really should be honoring the last frame that the user used rather than forcing it to the visible frame. That's called frameAutosave in Cocoa if you want to research that more. A window controller will help you manage that somewhat automatically if you just set a autosave name in Interface Builder. (Though it's slightly complicated by needing to compute the frame on first launch to get the visible frame, so it won't be completely automatic.)
Do give some careful thought before making your default frame be the visible frame in any case. That can be really enormous on large monitors (there are still a lot of 30" Cinema displays out there, but even on a 27" it can be pretty overwhelming). Sometimes that's fine depending on your app, but I often find that it's worth defining a maximum initial size (while allowing the user to make it larger).
You can "zoom" a window to the max available space by using NSScreen's visibleFrame as the target frame. Let's say window is your NSWindow IBOutlet:
if let screen = NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true, animate: true)
}
For example, in the AppDelegate.swift:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
if let screen = NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true, animate: true)
}
}
in Swift 4.2:
class ViewController: NSViewController {
override func viewDidAppear() {
super.viewDidAppear()
view.window?.zoom(self) //bespread the screen
//view.window?.toggleFullScreen(self) //fullscreen
}
2020 | SWIFT 5.1:
use extension:
extension NSWindowController {
func maximize() { self.window?.zoom(self) }
}
just call maximize() of NSWindowController instance :)
Swift 5
If anyone's still having issues, trying calling the zoom function the main thread. Worked for me.
DispatchQueue.main.async {
self.view.window?.zoom(self)
}
Hi Guys I really appreciate your help.
I am working on a document based mac application. I put the code you provided in the makeWindowControllers() of Document class and it works like a charm.
Thank you very much. Here is the code I use.
override func makeWindowControllers() {
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateControllerWithIdentifier("Document Window Controller") as! NSWindowController
self.addWindowController(windowController)
if let screen = NSScreen.mainScreen() {
windowController.window?.setFrame(screen.visibleFrame, display: true, animate: true)
}
}
this code works well only on single-windowed application, but it's really easy to edit to work with multy-windowed application
usage to maximize and unmaximize window:
TheApp.maximized.toggle()
Source code
public class TheApp {
static var maximized: Bool {
get {
guard let visibleFrame = NSScreen.main?.visibleFrame,
let window = NSApp.mainWindow
else { return false }
return window.frame == visibleFrame
}
set { NSApp.mainWindow?.zoom(newValue) }
}
static var fullscreen: Bool {
get {
guard let screenFrame = NSScreen.main?.frame,
let window = NSApp.mainWindow
else { return false }
return window.frame == screenFrame
} set {
NSApp.mainWindow?.toggleFullScreen(newValue)
}
}
static var mimimized: Bool {
get { NSApp.mainWindow?.isMiniaturized ?? false }
set { NSApp?.mainWindow?.miniaturize(newValue) }
}
}

Simple NSPageController example throws an unknown subview warning and stops working

I'm trying to get a very basic NSPageController to work (in book mode, not history mode). It will successfully transition once, and then stop working.
I suspect I'm creating the NSImageViews I'm loading into it wrong, but I can't figure out how.
The storyboard has a the SamplePageController which holds in initial hard-coded NSImageView.
I suspect I'm missing something really obvious here, since all of the tutorial's I've found for NSPageController are in Objective C not swift, and tend to focus on the history view mode.
The code is:
import Cocoa
class SamplePageController: NSPageController, NSPageControllerDelegate {
private var images = [NSImage]()
#IBOutlet weak var Image: NSImageView!
//Gets an object from arranged objects
func pageController(pageController: NSPageController, identifierForObject object: AnyObject) -> String {
let image = object as! NSImage
let image_name = image.name()!
let temp = arrangedObjects.indexOf({$0.name == image_name})
return "\(temp!)"
}
func pageController(pageController: NSPageController, viewControllerForIdentifier identifier: String) -> NSViewController {
let controller = NSViewController()
let imageView = NSImageView(frame: Image.frame)
let intid = Int(identifier)
let intid_u = intid!
imageView.image = images[intid_u]
imageView.sizeToFit()
controller.view = imageView
return controller
// Does this eventually lose the frame since we're returning the new view and then not storing it and the original ImageView is long gone by then?
// Alternatively, are we not sizing the imageView appropriately?
}
override func viewDidLoad() {
super.viewDidLoad()
images.append(NSImage(named:"text")!)
images.append(NSImage(named:"text-2")!)
arrangedObjects = images
delegate = self
}
}
In this case your pageController.view is set to your window.contentView and that triggers the warning. What you need to do is add a subview in the window.contentView and have your pageController.view point to that instead.
The reason for the warning is that since NSPageController creates snapshots (views) of your content history, it will add them at the same level as your pageController.view to transition between them: that means it will try to add them to pageController.view.superview.
And if your pageController.view is set to window.contentView, you are adding subviews to the window.contentView.superview, which is not supported:
New since WWDC seed: NSWindow has never supported clients adding subviews to anything other than the contentView.
Some applications would add subviews to the contentView.superview (also known as the border view of the window). NSWindow will now log when it detects this scenario: "NSWindow warning: adding an unknown subview:".
Applications doing this will need to fix this problem, as it prevents new features on 10.10 from working properly. See titlebarAccessoryViewControllers for official API.

Swift - Cropping images *outside* of allowsEditing

I have a very very simple project set up that allows you to click a "browse photo" button. The user then selects a photo from their photo gallery, and it's displayed on a programmatically created UIImageView.
Works like a charm. However - I am missing key functionality that is required.
I need the user to be able to scale the image (via pinching and dragging) after it is displayed within the UIImageView. allowsEditing = true, lets the user crop before. I need similar functionality, however, allowing them to edit once it's on the main UI.
Help is appreciated. Please and thank you!!
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var imageViewLayer: CALayer{
return imageView.layer
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
imageViewLayer.contents = UIImage(named: "ss3.jpg")?.CGImage
imageViewLayer.contentsGravity = kCAGravityResizeAspect
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func newGesture(sender: AnyObject) {
imageViewLayer.transform = CATransform3DMakeScale(sender.scale, sender.scale, 1)
}
}
I did something similar a while back. I added the image to UIImageView's layer property, added gesture recognizer to the view and implemented the gesture call backs modifying the layer property and not the view. Adding the image to the UIImageView's layer did the trick. As a side note, I would like to add that every UIView is supported by CALayer class. It has a lot of methods and properties which help to dynamically change the view, which in your case will be done by gestures.
As an alternative, you can also use CALayer's hitTest method instead of implementing the call backs for gesture recognizers.
EDIT- Sample Code
You could do some thing like this:
#IBOutlet weak var imageView: UIImageView!
var imageViewLayer: CALayer{
return imageView.layer
}
In the viewDidLoad, set up the image
imageViewLayer.contents = UIImage(named: "CoreDataDemoApp")?.CGImage
imageViewLayer.contentsGravity = kCAGravityResizeAspect
Add pinch gesture to the imageview in storyboard (or programmatically) and in it's call back you could do something like this:
#IBAction func pinchGestureRecognized(sender: AnyObject) {
imageViewLayer.transform = CATransform3DMakeScale(sender.scale, sender.scale, 1)
}
Again this is just to give you an idea of how it could work and it is not the complete code. Hope this helps!
This is another way of doing it:
Stackoverflow link to related question

UILabel Swift/Storyboard returns nil

I created a navigation controller with a view controller inside, containing a label. This label should be changed, when the view controller is loaded on tapping a button.
class ViewController: UIViewController {
#IBOutlet weak var btnEmergency: UIButton!
override func viewWillAppear(animated: Bool) {
self.btnEmergency.backgroundColor = UIColor.redColor();
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnEmergencyTapped(sender: AnyObject) {
let emergencyViewController = storyboard?.instantiateViewControllerWithIdentifier("EmergencyViewController") as! EmergencyViewController
emergencyViewController.emergencyLabel?.text = "Notruf"
navigationController?.pushViewController(emergencyViewController, animated: true)
}
}
But the label text does not change when loaded. I am relatively new to SWIFT so I try to learn my ropes at the moment using optionals. So I guess the reason is, that the label is not yet instantiated when the view is being loaded on pressing the button. Therefore the default text is being shown and not the text I want to have appear on screen.
I am a little bit stuck at the moment, how to correctly change the label text, when this is an optional. So I guess I cannot expect the label to be there, when I call it. But how do I handle this correctly?
Instead of this, the correct way is to set a property of your emergencyViewController.
In your emergencyViewController viewDidLoad set your label text according to the property set previously.
Anything that you do between initialize of a viewController to viewDidLoad will not take effect.