Hi everyone, I'm currently working on a project w...
# compose-ios
k
Hi everyone, I'm currently working on a project where I need to detect device orientation changes in my iOS app. I've implemented a class similar to the one in the JetBrains example, where I observe
UIDeviceOrientationDidChangeNotification
and have a method
orientationDidChange
to handle the changes. However, it seems that
orientationDidChange
is never called, and I'm not sure why. I've checked that I've properly registered the observer and selector, but still no luck. Any insights or suggestions on why this might be happening would be greatly appreciated! Thanks in advance for your help!
actual class ScreenOrientationObserver {
private val currentOrientation = mutableStateOf(ScreenOrientation.Portrait)
init {
class OrientationListener : NSObject() {
@Suppress("UNUSED_PARAMETER")
@ObjCAction
fun orientationDidChange(arg: NSNotification) {
val newOrientation = when (UIDevice.currentDevice.orientation) {
UIDeviceOrientation.UIDeviceOrientationPortrait,
UIDeviceOrientation.UIDeviceOrientationPortraitUpsideDown -> ScreenOrientation.Portrait
UIDeviceOrientation.UIDeviceOrientationLandscapeLeft,
UIDeviceOrientation.UIDeviceOrientationLandscapeRight -> ScreenOrientation.Landscape
else -> currentOrientation.value
}
currentOrientation.value = newOrientation
}
}
val listener = OrientationListener()
val notificationName = platform.UIKit.UIDeviceOrientationDidChangeNotification
NSNotificationCenter.defaultCenter.addObserver(
observer = listener,
selector = NSSelectorFromString(
OrientationListener::orientationDidChange.name + ":"
),
name = notificationName,
``object` = null`
)
}
actual val orientation: State<ScreenOrientation> = currentOrientation
}
actual fun createScreenOrientationObserver(): ScreenOrientationObserver {
`return ScreenOrientati`onObserver() }
o
Your listener needs to be kept on a hard reference, because subscription only adds a weak ref and it gets garbage collected
k
Thanks for lloking into my problem, i made these changes but orientationDidChange still never called :
Copy code
actual class ScreenOrientationObserver {
    private val currentOrientation = mutableStateOf(ScreenOrientation.Portrait)
    private val listener: OrientationListener = OrientationListener(currentOrientation)

    init {
        val notificationName = platform.UIKit.UIDeviceOrientationDidChangeNotification
        NSNotificationCenter.defaultCenter.addObserver(
            observer = listener,
            selector = NSSelectorFromString(
                OrientationListener::orientationDidChange.name + ":"
            ),
            name = notificationName,
            `object` = null
        )
    }

    actual val orientation: State<ScreenOrientation> = currentOrientation
}

actual fun createScreenOrientationObserver(): ScreenOrientationObserver {
    return ScreenOrientationObserver()
}

class OrientationListener(private val currentOrientation: MutableState<ScreenOrientation>) : NSObject() {
    @Suppress("UNUSED_PARAMETER")
    @ObjCAction
    fun orientationDidChange(arg: NSNotification) {
        val newOrientation = when (UIDevice.currentDevice.orientation) {
            UIDeviceOrientation.UIDeviceOrientationPortrait,
            UIDeviceOrientation.UIDeviceOrientationPortraitUpsideDown -> ScreenOrientation.Portrait
            UIDeviceOrientation.UIDeviceOrientationLandscapeLeft,
            UIDeviceOrientation.UIDeviceOrientationLandscapeRight -> ScreenOrientation.Landscape
            else -> currentOrientation.value
        }
        currentOrientation.value = newOrientation
    }
}
o
I’m not an expert in iOS, but it looks like you may need to call beginGeneratingDeviceOrientationNotifications on a current device:
You must call this method before attempting to get orientation data from the device. This method enables the device’s accelerometer hardware and begins the delivery of acceleration events to the device. The device subsequently uses these events to post orientationDidChangeNotification notifications when the device orientation changes and to update the orientation property.
k
Nice found but still doesnt work :
Copy code
actual class ScreenOrientationObserver {
    private val currentOrientation = mutableStateOf(ScreenOrientation.Portrait)
    private val listener: OrientationListener

    init {
        UIDevice.currentDevice.beginGeneratingDeviceOrientationNotifications()
        listener = OrientationListener(currentOrientation)
        val notificationName = platform.UIKit.UIDeviceOrientationDidChangeNotification
        NSNotificationCenter.defaultCenter.addObserver(
            observer = listener,
            selector = NSSelectorFromString(
                OrientationListener::orientationDidChange.name + ":"
            ),
            name = notificationName,
            `object` = null
        )
    }

    actual val orientation: State<ScreenOrientation> = currentOrientation

    // This method should be called when the observer is no longer needed
    fun stopObserving() {
        UIDevice.currentDevice.endGeneratingDeviceOrientationNotifications()
        NSNotificationCenter.defaultCenter.removeObserver(listener)
    }
}

actual fun createScreenOrientationObserver(): ScreenOrientationObserver {
    return ScreenOrientationObserver()
}

class OrientationListener(private val currentOrientation: MutableState<ScreenOrientation>) : NSObject() {
    @Suppress("UNUSED_PARAMETER")
    @ObjCAction
    fun orientationDidChange(arg: NSNotification) {
        println("orientationDidChange called")
        val newOrientation = when (UIDevice.currentDevice.orientation) {
            UIDeviceOrientation.UIDeviceOrientationPortrait,
            UIDeviceOrientation.UIDeviceOrientationPortraitUpsideDown -> ScreenOrientation.Portrait
            UIDeviceOrientation.UIDeviceOrientationLandscapeLeft,
            UIDeviceOrientation.UIDeviceOrientationLandscapeRight -> ScreenOrientation.Landscape
            else -> currentOrientation.value
        }
        currentOrientation.value = newOrientation
    }
}
i dont understand why on jetbrains exemple (https://github.com/JetBrains/compose-multiplatform/blob/0319db1d06dd65f09a12e1a91b[…]d/src/iosMain/kotlin/example/imageviewer/view/CameraView.ios.kt)it works and not on my code
o
Well, I don’t know, but let’s do a few wild checks: • make sure
ScreenOrientationObserver
itself is retained on a hard ref • make sure you don’t have rotation lock enabled on a device :)
k
Yes it what i done roation performs well bu on ios is not detected :
Copy code
val screenOrientationObserver = remember { createScreenOrientationObserver() }
val orientation = remember { screenOrientationObserver.orientation }
Text(text = "Orientation : " + orientation.value.name)
o
You might need to add this to
Info.plist
https://github.com/JetBrains/compose-multiplatform/blob/0319db1d06dd65f09a12e1a91b5de758fa2d2883/examples/imageviewer/iosApp/iosApp/Info.plist#L39
Copy code
<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
k
Thanks again for your time , these declarations were already present (plus same ones for ipad) ,
o
Well, I’m out of ideas, sorry
k
It's okay. Thank you again
d
Do you have this project in open source ?
k
I was creating a sample project for you, and it was working on it . Seeems like the problem came from Voyager library with Screen class , really dont understand why but i found a way to make it working .. Thanks again to have took time to look on this issue
o
Could you share what did you do to make it work? Someone searching for similar problem later will thank you :)
k
i'm not so sure ... Maybe its the changes you made me do it. One thing its sure it's this code works :
Copy code
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import kotlinx.cinterop.ObjCAction
import platform.Foundation.NSNotification
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSSelectorFromString
import platform.UIKit.UIDevice
import platform.UIKit.UIDeviceOrientation
import platform.darwin.NSObject

actual class ScreenOrientationObserver {
    private val currentOrientation = mutableStateOf(ScreenOrientation.Portrait)
    private val listener: OrientationListener

    init {
        UIDevice.currentDevice.beginGeneratingDeviceOrientationNotifications()
        listener = OrientationListener(currentOrientation)
        val notificationName = platform.UIKit.UIDeviceOrientationDidChangeNotification
        NSNotificationCenter.defaultCenter.addObserver(
            observer = listener,
            selector = NSSelectorFromString(
                OrientationListener::orientationDidChange.name + ":"
            ),
            name = notificationName,
            `object` = null
        )
    }

    actual val orientation: State<ScreenOrientation> = currentOrientation

    // This method should be called when the observer is no longer needed
    fun stopObserving() {
        UIDevice.currentDevice.endGeneratingDeviceOrientationNotifications()
        NSNotificationCenter.defaultCenter.removeObserver(listener)
    }
}

actual fun createScreenOrientationObserver(): ScreenOrientationObserver {
    return ScreenOrientationObserver()
}

class OrientationListener(private val currentOrientation: MutableState<ScreenOrientation>) : NSObject() {
    @Suppress("UNUSED_PARAMETER")
    @ObjCAction
    fun orientationDidChange(arg: NSNotification) {
        println("orientationDidChange called")
        val newOrientation = when (UIDevice.currentDevice.orientation) {
            UIDeviceOrientation.UIDeviceOrientationPortrait,
            UIDeviceOrientation.UIDeviceOrientationPortraitUpsideDown -> ScreenOrientation.Portrait
            UIDeviceOrientation.UIDeviceOrientationLandscapeLeft,
            UIDeviceOrientation.UIDeviceOrientationLandscapeRight -> ScreenOrientation.Landscape
            else -> currentOrientation.value
        }
        currentOrientation.value = newOrientation
    }
}
with in commonMain :
Copy code
expect fun createScreenOrientationObserver(): ScreenOrientationObserver


enum class ScreenOrientation {
    Landscape,
    Portrait,
}

expect class ScreenOrientationObserver() {
    val orientation: State<ScreenOrientation>
}
and for usage :
Copy code
val screenOrientationObserver = remember { createScreenOrientationObserver() }
val orientation = remember { screenOrientationObserver.orientation }

if (orientation.value == ScreenOrientation.Landscape) {}
for androidMain :
Copy code
actual class ScreenOrientationObserver  {
    private val currentOrientation = mutableStateOf(ScreenOrientation.Portrait)

    init {
        val orientation = MainApp.appContext.resources.configuration.orientation // appContext is an android Context
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            currentOrientation.value = ScreenOrientation.Landscape
        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            currentOrientation.value = ScreenOrientation.Portrait
        }
    }

    actual val orientation: State<ScreenOrientation> = currentOrientation
}

actual fun createScreenOrientationObserver(): ScreenOrientationObserver {
    return ScreenOrientationObserver()
}