Forum Archive

UI.DatePicker countdown display D,H,M,S

Robert_Tompkins

Hey guys,

I was playing around with date picker so I could create a quick program to calculate the time until a future date for my 7 year old daughter. She always asks us “how long until my birthday??” So I made her a program that will tell her the Days, Hours, Minutes, Seconds until the date she selected in the ui.DatePicker.

Currently, she selects a date and it outputs the delta into a textView(4 of them, one for each: D,H,M,S), which get updated in real-time (Spams the terminal if debug flag is true, but I’m cool with that).

What I would like to do is replace the textViews with another DatePicker.
Unfortunately, none of the modes available display all fields: D,H,M,S.

Is there a way for me to do this?
Ideally, I’d like a copy of the source code for DatePicker so I could resize the width of the segments to be smaller(so it all fits) and then create additional segments to display D,H,M,S and rotate each dial in real-time.

Here is my working code as of right now:

import ui
import console
import time
import datetime
from time import *
from objc_util import *
from datetime import timedelta#, datetime

def run_async(func):
    from threading import Thread
    from functools import wraps

    @wraps(func)
    def async_func(*args, **kwargs):
        func_hl = Thread(target = func, args = args, kwargs = kwargs)
        func_hl.start()
        return func_hl

    return async_func

birthday = [2021, 2, 21]

# TODO: ##### MAIN VIEW #####
birthdayView = ui.View()
birthdayView.background_color = 'black'
birthdayView.x = 0
birthdayView.y = 0
birthdayView.width = 100
birthdayView.height = 100
birthdayView.flex = "LRTB"
#########################

# TODO: ##### DATE PICKER #####
dateSelector = ui.DatePicker()
dateSelector.center = (birthdayView.width / 2, birthdayView.height / 2)
dateSelector.x = birthdayView.center[0]
dateSelector.y = birthdayView.center[1]
dateSelector.background_color = "#AAA"
dateSelector.mode = 1
#########################

# TODO: ##### DAYS OUTPUT FIELD #####
daysOutputField = ui.TextView()
daysOutputField.x = birthdayView.center[0] - 15
daysOutputField.y = birthdayView.center[1] + 300
daysOutputField.width = 350
daysOutputField.height = 50
daysOutputField.background_color = "#000"
daysOutputField.text_color = "#F00"
daysOutputField.font = "<system>", 18
#########################

# TODO: ##### HOURS OUTPUT FIELD #####
hoursOutputField = ui.TextView()
hoursOutputField.x = birthdayView.center[0] - 15
hoursOutputField.y = birthdayView.center[1] + 355
hoursOutputField.width = 350
hoursOutputField.height = 50
hoursOutputField.background_color = "#000"
hoursOutputField.text_color = "#F00"
hoursOutputField.font = "<system>", 22
#########################

# TODO: ##### MINUTES OUTPUT FIELD #####
minutesOutputField = ui.TextView()
minutesOutputField.x = birthdayView.center[0] - 15
minutesOutputField.y = birthdayView.center[1] + 410
minutesOutputField.width = 350
minutesOutputField.height = 50
minutesOutputField.background_color = "#000"
minutesOutputField.text_color = "#F00"
minutesOutputField.font = "<system>", 26
#########################

# TODO: ##### SECONDS OUTPUT FIELD #####
secondsOutputField = ui.TextView()
secondsOutputField.x = birthdayView.center[0] - 15
secondsOutputField.y = birthdayView.center[1] + 465
secondsOutputField.width = 350
secondsOutputField.height = 50
secondsOutputField.background_color = "#000"
secondsOutputField.text_color = "#F00"
secondsOutputField.font = "<system>", 30
#########################

#import datetime
#from time import localtime, time, gmtime, mktime, struct_time

# TODO: ##### DEBUG FLAG #####
debugMode = 1
#########################

@run_async
def calculateTimeUntilBirthday(sender):
    global birthday
    global dateSelector
    global countdownTimer
    global daysOutputField
    global hoursOutputField
    global minutesOutputField
    global secondsOutputField

    while birthdayView.on_screen:
        currentTime = datetime.datetime.now()
        birthdayTime = dateSelector.date.replace(hour = 0, minute = 0, second = 0)
        birthdayString = f"{birthdayTime.month}/{birthdayTime.day}/{birthdayTime.year}"
        timeUntilBirthday = (birthdayTime) - (currentTime)

        seconds = timeUntilBirthday.seconds
        hours = seconds / 3600

        minutesLeft = hours - int(hours)
        minutesLeftMinutes = minutesLeft * 60

        secondsLeft = minutesLeftMinutes - int(minutesLeftMinutes)
        secondsLeftSeconds = secondsLeft * 60
        secondsLeftSeconds = round(secondsLeftSeconds, 2)

        daysOutput = f"Days: {timeUntilBirthday.days}"
        daysOutputField.text = daysOutput

        hoursOutput = f"Hours: {int(hours)}"
        hoursOutputField.text = hoursOutput

        minutesOutput = f"Minutes: {int(minutesLeftMinutes)}"
        minutesOutputField.text = minutesOutput

        secondsOutput = f"Seconds: {int(secondsLeftSeconds)}"
        secondsOutputField.text = secondsOutput


        if debugMode:
            print(f"Current Time: {currentTime}")
            print(f"Birthday Time: {birthdayTime}")
            print(f"birthdayString: {birthdayString}")
            print(f"timeUntilBirthday: {timeUntilBirthday}")
            print(f"seconds: {seconds}")
            print(f"hours: {hours}")
            print(f"minutesLeft: {minutesLeft}")
            print(f"minutesLeftMinutes: {minutesLeftMinutes}")
            print(f"secondsLeft: {secondsLeft}")
            print(f"secondsLeftSeconds: {secondsLeftSeconds}")
            print(f"secondsLeftSeconds: {secondsLeftSeconds}")

            print(f"Time Until Birthday: {birthdayString}\n Days: {timeUntilBirthday.days}\n  Hours: {int(hours)}\n   Minutes: {int(minutesLeftMinutes)}\n    Seconds: {int(secondsLeftSeconds)}")


#birthdayView.add_subview(countdownTimer)

birthdayView.add_subview(dateSelector)
birthdayView.add_subview(daysOutputField)
birthdayView.add_subview(hoursOutputField)
birthdayView.add_subview(minutesOutputField)
birthdayView.add_subview(secondsOutputField)

dateSelector.action = calculateTimeUntilBirthday
birthdayView.present(hide_title_bar = True) 
cvp

@Robert_Tompkins try this

# coding: utf-8
from   math import pi,cos,sin
from   objc_util import *
import ui

#===================== delegate of UIPickerView: begin =====================
def pickerView_numberOfRowsInComponent_(self, cmd, picker_view, component):
    return(len(ObjCInstance(picker_view).data))

def numberOfComponentsInPickerView_(self, cmd, picker_view):
    return 1

def rowSize_forComponent_(self, cmd, picker_view, component):
    return ObjCInstance(picker_view).myRowWidth

def pickerView_rowHeightForComponent_(self, cmd, picker_view, component):
    return ObjCInstance(picker_view).myRowHeight

def pickerView_didSelectRow_inComponent_(self, cmd, picker_view, row, component):
    UIPickerView = ObjCInstance(picker_view)
    UIPickerView.selectedRow = row
    return

def pickerView_viewForRow_forComponent_reusingView_(self, cmd, picker_view, row, component,view_ptr):
    UIPickerView = ObjCInstance(picker_view)
    if view_ptr == None:
      #view = ObjCClass('UILabel').alloc().init()
      #view.setText_(UIPickerView.data[row])
      uiview = ui.Label()
      uiview.alignment = ui.ALIGN_CENTER
      uiview.text_color = 'red'
      uiview.text = UIPickerView.data[row]
      view = ObjCInstance(uiview)
      view.setClipsToBounds_(True)
      if UIPickerView.horiz:
        a = pi/2
        rot = CGAffineTransform(cos(a),-sin(a),sin(a),cos(a),0,0)
        view.setTransform_(rot, restype=None, argtypes=[CGAffineTransform])
    else:
      view = ObjCInstance(view_ptr)
    return view.ptr

methods = [
    numberOfComponentsInPickerView_, pickerView_numberOfRowsInComponent_, rowSize_forComponent_, pickerView_rowHeightForComponent_, pickerView_didSelectRow_inComponent_, pickerView_viewForRow_forComponent_reusingView_]

protocols = ['UIPickerViewDataSource', 'UIPickerViewDelegate']

UIPickerViewDataSourceAndDelegate = create_objc_class(
    'UIPickerViewDataSourceAndDelegate', methods=methods, protocols=protocols)
#===================== delegate of UIPickerView: end =======================

class MyUIPickerView(ui.View):
    def __init__(self, data, horiz=False,**kwargs):
        super().__init__(**kwargs)

        UIPickerView = ObjCClass('UIPickerView')
        self._picker_view = UIPickerView.alloc().initWithFrame_(ObjCInstance(self).bounds()).autorelease()
        ObjCInstance(self).addSubview_(self._picker_view)
        self.delegate_and_datasource = UIPickerViewDataSourceAndDelegate.alloc().init().autorelease()
        self._picker_view.delegate = self.delegate_and_datasource
        self._picker_view.dataSource = self.delegate_and_datasource

        self._picker_view.myRowWidth = self.width
        self._picker_view.myRowHeight = 32
        self._picker_view.data = data
        self._picker_view.horiz = horiz

        if horiz:
          a = -pi/2
          rot = CGAffineTransform(cos(a),-sin(a),sin(a),cos(a),0,0)
          self._picker_view.setTransform_(rot, restype=None, argtypes=[CGAffineTransform])


    def layout(self):
        self._picker_view.frame = ObjCInstance(self).bounds()

def main():
    mv = ui.View()
    mv.frame = (0,0,500,500)
    mv.background_color = 'white'
    days = []
    for i in range(0,100):
      days.append(str(i))
    pvd = MyUIPickerView(days, frame=(50,50,80,200))
    mv.add_subview(pvd)
    hours = []
    for i in range(0,24):
      hours.append(str(i))
    pvh = MyUIPickerView(hours,frame=(150,50,80,200))
    mv.add_subview(pvh)
    mins = []
    for i in range(0,60):
      mins.append(str(i))
    pvm = MyUIPickerView(mins, frame=(250,50,80,200))
    mv.add_subview(pvm)
    secs = []
    for i in range(0,60):
      secs.append(str(i))
    pvs = MyUIPickerView(secs, frame=(350,50,80,200))
    mv.add_subview(pvs)

    mv.present('sheet')

if __name__ == '__main__':
    main()

Robert_Tompkins

@cvp

Sweet, looks like all the bits n pieces are there!
I haven’t played around with objc_util much, so it will take some trial and error to get what I want.

However, I see that module used all over the place, and likely for good reason.
So I will play with it and once I get a feel for it, will see where else I can use it.

Thanks!

cvp

@Robert_Tompkins if you need some help, don't hesitate to ask

Robert_Tompkins

@cvp
I need some help ;)

Been struggling because I have not used objc previously. So syntax and how it’s being converted on the fly to be used to access objects, etc.. is brand new to me.

I have been trying to play with it via adjusting my screen brightness using one of the built in objc examples:

from objc_util import *
from time import sleep
def run_async(func):
    from threading import Thread
    from functools import wraps

    @wraps(func)
    def async_func(*args, **kwargs):
        func_hl = Thread(target = func, args = args, kwargs = kwargs)
        func_hl.start()
        return func_hl
    return async_func

@on_main_thread
def setScreenBrightness():
    UIScreen = ObjCClass('UIScreen')
    screen = UIScreen.mainScreen()
    #print(f"UIScreen.get_names(): {UIScreen.get_names()}")
    for x in range(1):
        brightnessVar = 1.0
        for x in range(995):
            screen.setBrightness(brightnessVar)
            brightnessVar -= 0.001
            sleep(0.0001)
            #print(f"screen.brightness(): {screen.brightness()}")
        for x in range(995):
            if screen.brightness() < 1.0:
                screen.setBrightness(brightnessVar)
                brightnessVar += 0.001
                sleep(0.0001)
setScreenBrightness()

However, what would help is being able to see a list of ‘objects’ I can get/set attributes for.
For example:

UIScreen = ObjCClass('UIScreen')
    screen = UIScreen.mainScreen() 

What is UIScreen? I assume we are letting objc create a class/do conversions/do its magic to allow us to access/modify properties of it. But what other strings/object classes are available? Is there something like ‘UIFlashlight’?

Of course, my attempts to print out a list of ‘things’ to try and understand structure, etc.. have resulted in a HUGE list being returned.. But for me to be able to learn this stuff, I get the most out of playing around with things, tweaking, printing out what’s there, but I can’t seem to do this. But again, I don’t even know where to begin haha.

Any resources, tips you have send them my way! But as far as the countdown code you gave me; that will work perfectly. Once I understand the basics I will be rolling through the rest.

JonB

Searching apple docs is useful, as it let's you know what is in each class.

https://github.com/jsbain/objc_hacks/blob/master/objc_browser.py
Can be useful for exploring undocumented classes -- it lists all classes available on your device at the moment (might not include frameworks that haven't been loaded yet), then let's you see the method names. (And then let's you highjack the method invocations to log the input types,for methods called elsewhere)

When reading apple api docs, be sure to set the little switch to look at objc, not swift. Then you can usually figure out the python version of the name by replacing the colons with underscores.

ObjCClass (classname) returns a class object,not an instance. Usually you will want to either use .new() to get an instance, or .alloc().init(), or maybe .alloc().initwithSomeParam_() if the constructor takes arguments. Calling obj=ObjCClass(classname).alloc(), then using autocomplete in the console by typing obj, then a period,then start typing,you can see what options you have. Some objects have a shared singleton that you use-- for instance UIApplication.sharedApplication(). Browsing using autocomplete in the console is useful for discovering that sort of thing.

Robert_Tompkins

@JonB
Sweet, I actually have been browsing the Apple developer docs, specially UIKit stuff, because that is what I keep finding examples of and I believe that’s what OMZ based the UI elements on.

I managed to find some stuff there, as well as created a method to help investigate objects, etc.

def investigateItem(item):
    mysteriousItem = item
    mysteriousInfo = dir(mysteriousItem)
    for x in mysteriousInfo:
        print(f"{x}")

#setScreenBrightness()
UIScreen = ObjCClass('UIScreen')
screen = UIScreen.mainScreen()
investigateItem(screen) 

However, I totally forgot about using the console... That is a game changer, thanks!
I’ll check out the other info you mentioned after I play a bit via console. Is it weird that I’m excited right now? Haha. Using the console to interact/access/view attributes, etc. is going to be crazy useful.

cvp

@Robert_Tompkins Usually, I only do "print(dir(screen))"..

cvp

@Robert_Tompkins obviously too late because here we just wake up.

screen.setBrightness_(brightnessVar)

See the underscore