Search code examples
arraysswiftarray-map

Why does mapping array expressible to itself produce error?


I'm experimenting with this:

var orientations1: UIInterfaceOrientationMask = [.portrait, .landscapeRight]
var orientations2: UIInterfaceOrientationMask = [.portrait, .landscapeRight].map { $0 }

orientations1 works fine, but orientation2 produces the following error:

Type of expression is ambiguous without more context

UIInterfaceOrientationMask conforms indirectly to ExpressibleByArrayLiteral protocol. How can .map { $0 } possibly change the contents of the array and what should I do to make it work?

UPDATE

Okay, so here's what I'm trying to do. I'm learning Swift and want to play with device orientation changing on runtime.

I have 4 switches:

@IBOutlet weak var switchPortrait: UISwitch!
@IBOutlet weak var switchLandscapeRight: UISwitch!
@IBOutlet weak var switchPortraitUpsideDown: UISwitch!
@IBOutlet weak var switchLandscapeLeft: UISwitch!

var orientationSwitches = [UISwitch]()
var orientations: UIInterfaceOrientationMask = [.portrait, .landscapeRight, .landscapeLeft, .portrait]

In viewDidLoad() method, I'm doing this:

switchPortrait.tag  = Int(UIInterfaceOrientationMask.portrait.rawValue)
switchLandscapeRight.tag = Int(UIInterfaceOrientationMask.landscapeRight.rawValue)
switchPortraitUpsideDown.tag   = Int(UIInterfaceOrientationMask.portraitUpsideDown.rawValue)
switchLandscapeLeft.tag  = Int(UIInterfaceOrientationMask.landscapeLeft.rawValue)

orientationSwitches = [switchPortrait, switchLandscapeRight, switchPortraitUpsideDown, switchLandscapeLeft]

for switchElement in orientationSwitches {
    switchElement.isOn = orientations.contains(UIInterfaceOrientationMask(rawValue: UInt(switchElement.tag)))
}

So now every switch UI element has its tag set to appropriate orientation mask's raw value. Also, every switch is on or off, according to initial state of orientations.

On switching action, I'm trying to update orientations like this:

@IBAction func orientationSwitched(_ sender: UISwitch) {
    orientations = orientationSwitches.filter({ $0.isOn }).map({ UIInterfaceOrientationMask(rawValue: UInt($0.tag)) }) // doesn't work
}

But it doesn't work. And my supportedInterfaceOrientations looks simply like this:

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return orientations
}

I'm able to do it via insert() method for each UI switch element, but I want to learn if that can be done in more elegant way, by mapping.


Solution

  • In Swift, literals are typeless and their types are inferred depending on the contexts.

    In your case, if Swift inferred the type of the array literal as Array< UIInterfaceOrientationMask>, you can apply .map{ $0 }, but the result also becomes Array<UIInterfaceOrientationMask> and cannot be converted to UIInterfaceOrientationMask. (See below.)

    If Swift inferred the type of the array literal as UIInterfaceOrientationMask, then map is not available.

    Thus, Swift cannot find any types matching to your declaration, which causes Type of expression is ambiguous without more context.


    As you have been suggested in the comments of another thread, ExpressibleByArrayLiteral is used by the compiler to interpret the array literal according to the context. It does not mean arrays can automatically be converted to the type conforming to ExpressibleByArrayLiteral.


    For UPDATE

    As you see, UIInterfaceOrientationMask has no usual initializer taking an Array. So using insert is one steady way. Steady code would be more elegant than seemingly smart but fragile or hard-to-read codes.

    If you insist on using seemingly smart code, you can write something like this:

    @IBAction func orientationSwitched(_ sender: UISwitch) {
        orientations = orientationSwitches
            .filter{$0.isOn}
            .map{UIInterfaceOrientationMask(rawValue: UInt($0.tag))}
            .reduce([]) {$0.union($1)}
    }